2026-05-25 16:26:47 +03:00
|
|
|
package logging
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"io"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Closer func() error
|
|
|
|
|
|
|
|
|
|
type jsonLineHandler struct {
|
|
|
|
|
mu *sync.Mutex
|
|
|
|
|
out io.Writer
|
|
|
|
|
attrs []slog.Attr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func New(dataDir string) (*slog.Logger, Closer, error) {
|
|
|
|
|
logDir := filepath.Join(dataDir, "logs")
|
|
|
|
|
if err := os.MkdirAll(logDir, 0o755); err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
path := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log")
|
|
|
|
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handler := &jsonLineHandler{
|
|
|
|
|
mu: &sync.Mutex{},
|
|
|
|
|
out: io.MultiWriter(os.Stdout, file),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return slog.New(handler), file.Close, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *jsonLineHandler) Enabled(context.Context, slog.Level) bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *jsonLineHandler) Handle(_ context.Context, record slog.Record) error {
|
|
|
|
|
entry := map[string]any{
|
|
|
|
|
"date": record.Time.Format("2006-01-02"),
|
|
|
|
|
"time": record.Time.Format("15:04:05"),
|
|
|
|
|
"source": "app",
|
|
|
|
|
"code": 0,
|
|
|
|
|
"severity": severity(record.Level),
|
|
|
|
|
"log": record.Message,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, attr := range h.attrs {
|
|
|
|
|
applyAttr(entry, attr)
|
|
|
|
|
}
|
|
|
|
|
record.Attrs(func(attr slog.Attr) bool {
|
|
|
|
|
applyAttr(entry, attr)
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
line, err := json.Marshal(entry)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.mu.Lock()
|
|
|
|
|
defer h.mu.Unlock()
|
|
|
|
|
_, err = h.out.Write(append(line, '\n'))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *jsonLineHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
|
|
|
next := &jsonLineHandler{
|
|
|
|
|
mu: h.mu,
|
|
|
|
|
out: h.out,
|
|
|
|
|
attrs: append([]slog.Attr{}, h.attrs...),
|
|
|
|
|
}
|
|
|
|
|
next.attrs = append(next.attrs, attrs...)
|
|
|
|
|
return next
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *jsonLineHandler) WithGroup(string) slog.Handler {
|
|
|
|
|
return h
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func applyAttr(entry map[string]any, attr slog.Attr) {
|
|
|
|
|
attr.Value = attr.Value.Resolve()
|
|
|
|
|
entry[attr.Key] = attr.Value.Any()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func severity(level slog.Level) string {
|
|
|
|
|
switch {
|
|
|
|
|
case level >= slog.LevelError:
|
2026-05-25 16:52:57 +03:00
|
|
|
return "error"
|
2026-05-25 16:26:47 +03:00
|
|
|
case level >= slog.LevelWarn:
|
2026-05-25 16:52:57 +03:00
|
|
|
return "warn"
|
2026-05-25 16:26:47 +03:00
|
|
|
case level <= slog.LevelDebug:
|
2026-05-25 16:52:57 +03:00
|
|
|
return "dev"
|
2026-05-25 16:26:47 +03:00
|
|
|
default:
|
2026-05-25 16:52:57 +03:00
|
|
|
return "dev"
|
2026-05-25 16:26:47 +03:00
|
|
|
}
|
|
|
|
|
}
|