package logger import ( "context" "io" "log/slog" "os" "time" ) type Logger struct { inner *slog.Logger quiet bool logReads bool logFile string } type entry struct { Level slog.Level `json:"level"` Method string `json:"method"` Path string `json:"path"` Table string `json:"table,omitempty"` RecordID string `json:"record_id,omitempty"` Status int `json:"status"` Duration string `json:"duration"` Timestamp string `json:"timestamp"` } func New(quiet, logReads bool, logFile string) (*Logger, error) { var w io.Writer = os.Stdout if logFile != "" { f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err } w = io.MultiWriter(os.Stdout, f) } var h slog.Handler if quiet { h = slog.NewTextHandler(io.Discard, nil) if logFile != "" { h = slog.NewJSONHandler(w, &slog.HandlerOptions{Level: slog.LevelInfo}) } } else { h = slog.NewTextHandler(w, &slog.HandlerOptions{Level: slog.LevelInfo}) } return &Logger{ inner: slog.New(h), quiet: quiet, logReads: logReads, logFile: logFile, }, nil } func (l *Logger) Log(ctx context.Context, method, path string, status int, dur time.Duration) { isRead := method == "GET" || method == "HEAD" || method == "OPTIONS" if isRead && !l.logReads { return } l.inner.LogAttrs(ctx, slog.LevelInfo, "request", slog.String("method", method), slog.String("path", path), slog.Int("status", status), slog.String("duration", dur.Round(time.Microsecond).String()), ) } func (l *Logger) Info(msg string, args ...any) { l.inner.Info(msg, args...) } func (l *Logger) Error(msg string, args ...any) { l.inner.Error(msg, args...) } func (l *Logger) Debug(msg string, args ...any) { l.inner.Debug(msg, args...) }