package logz import ( "context" "errors" "log/slog" "os" "code.nochebuena.dev/einherjar/contracts/errs" "code.nochebuena.dev/einherjar/contracts/logging" ) var _ logging.Logger = (*slogLogger)(nil) // New returns a Logger configured by cfg. func New(cfg Config) logging.Logger { w := cfg.Writer if w == nil { w = os.Stdout } handlerOpts := &slog.HandlerOptions{Level: cfg.Level} var handler slog.Handler if cfg.JSON { handler = slog.NewJSONHandler(w, handlerOpts) } else { handler = slog.NewTextHandler(w, handlerOpts) } base := slog.New(handler) if len(cfg.StaticArgs) > 0 { base = base.With(cfg.StaticArgs...) } return &slogLogger{logger: base} } type slogLogger struct { logger *slog.Logger } func (l *slogLogger) Debug(msg string, args ...any) { l.logger.Debug(msg, args...) } func (l *slogLogger) Info(msg string, args ...any) { l.logger.Info(msg, args...) } func (l *slogLogger) Warn(msg string, args ...any) { l.logger.Warn(msg, args...) } func (l *slogLogger) Error(msg string, err error, args ...any) { args = enrichErrorAttrs(err, args) if err != nil { args = append(args, slog.Any("error", err)) } l.logger.Error(msg, args...) } func (l *slogLogger) With(args ...any) logging.Logger { return &slogLogger{logger: l.logger.With(args...)} } func (l *slogLogger) WithContext(ctx context.Context) logging.Logger { if ctx == nil { return l } newLogger := l.logger modified := false if id, ok := ctx.Value(ctxRequestIDKey{}).(string); ok && id != "" { newLogger = newLogger.With(slog.String("request_id", id)) modified = true } if fields, ok := ctx.Value(ctxExtraFieldsKey{}).(map[string]any); ok { for k, v := range fields { newLogger = newLogger.With(k, v) } modified = true } if !modified { return l } return &slogLogger{logger: newLogger} } // enrichErrorAttrs appends error_code and context fields from err when err // satisfies errs.CodedError or errs.ContextualError from contracts. // Returns attrs unchanged if err is nil or satisfies neither interface. func enrichErrorAttrs(err error, attrs []any) []any { if err == nil { return attrs } var ec errs.CodedError if errors.As(err, &ec) { attrs = append(attrs, "error_code", ec.ErrorCode()) } var ectx errs.ContextualError if errors.As(err, &ectx) { for k, v := range ectx.ErrorContext() { attrs = append(attrs, k, v) } } return attrs }