package logging import ( "io" "log/slog" "os" "time" ) // Logger wraps slog.Logger with convenience methods and standard fields. type Logger struct { *slog.Logger config Config } // New creates a new Logger from the given config. func New(cfg Config) *Logger { return NewWithWriter(cfg, os.Stdout) } // NewWithWriter creates a new Logger writing to the given writer. func NewWithWriter(cfg Config, w io.Writer) *Logger { var handler slog.Handler opts := &slog.HandlerOptions{ Level: cfg.Level.SlogLevel(), AddSource: cfg.AddSource, } if cfg.Format == FormatText { handler = slog.NewTextHandler(w, opts) } else { handler = slog.NewJSONHandler(w, opts) } // Wrap with redacting handler if enabled if cfg.RedactEnabled { handler = NewRedactingHandler(handler) } return &Logger{ Logger: slog.New(handler), config: cfg, } } // With returns a new Logger with the given attributes. func (l *Logger) With(args ...any) *Logger { return &Logger{ Logger: l.Logger.With(args...), config: l.config, } } // WithComponent returns a new Logger with the component field set. func (l *Logger) WithComponent(name string) *Logger { return l.With(FieldComponent, name) } // WithHandler returns a new Logger with the handler field set. func (l *Logger) WithHandler(name string) *Logger { return l.With(FieldHandler, name) } // WithService returns a new Logger with the service field set. func (l *Logger) WithService(name string) *Logger { return l.With(FieldService, name) } // WithWorker returns a new Logger with the worker field set. func (l *Logger) WithWorker(name string) *Logger { return l.With(FieldWorker, name) } // WithAdapter returns a new Logger with the adapter field set. func (l *Logger) WithAdapter(name string) *Logger { return l.With(FieldAdapter, name) } // WithRequestID returns a new Logger with the request_id field set. func (l *Logger) WithRequestID(id string) *Logger { return l.With(FieldRequestID, id) } // WithProjectID returns a new Logger with the project_id field set. func (l *Logger) WithProjectID(id string) *Logger { return l.With(FieldProjectID, id) } // WithUserID returns a new Logger with the user_id field set. func (l *Logger) WithUserID(id string) *Logger { return l.With(FieldUserID, id) } // WithError returns a new Logger with the error field set. // Always use "error" as the field name, never "err" or "e". func (l *Logger) WithError(err error) *Logger { if err == nil { return l } return l.With(FieldError, err.Error()) } // WithOperation returns a new Logger with the operation field set. func (l *Logger) WithOperation(name string) *Logger { return l.With(FieldOperation, name) } // Timed returns a function that logs the duration when called. // Usage: defer log.Timed("operation_name")() func (l *Logger) Timed(operation string) func() { start := time.Now() return func() { duration := time.Since(start) l.Info("operation completed", FieldOperation, operation, FieldDuration, duration.Milliseconds(), ) } } // TimedWithLevel returns a function that logs the duration at the specified level. // Usage: defer log.TimedWithLevel("operation_name", LevelDebug)() func (l *Logger) TimedWithLevel(operation string, level Level) func() { start := time.Now() return func() { duration := time.Since(start) msg := "operation completed" args := []any{FieldOperation, operation, FieldDuration, duration.Milliseconds()} switch level { case LevelDebug: l.Debug(msg, args...) case LevelInfo: l.Info(msg, args...) case LevelWarn: l.Warn(msg, args...) case LevelError: l.Error(msg, args...) } } } // Slog returns the underlying slog.Logger for compatibility. func (l *Logger) Slog() *slog.Logger { return l.Logger } // Config returns the logger's configuration. func (l *Logger) Config() Config { return l.config }