diff --git a/internal/logging/logger.go b/internal/logging/logger.go new file mode 100644 index 0000000..cca8515 --- /dev/null +++ b/internal/logging/logger.go @@ -0,0 +1,63 @@ +// Package logging configures the structured JSON logger used across the service. +package logging + +import ( + "context" + "log/slog" + "os" +) + +type ctxKey int + +const requestIDKey ctxKey = iota + +// New returns a slog.Logger that writes JSON to stdout at the given level. +// Valid levels: "debug", "info", "warn", "error". Unknown levels default to info. +func New(level string) *slog.Logger { + var lvl slog.Level + switch level { + case "debug": + lvl = slog.LevelDebug + case "warn": + lvl = slog.LevelWarn + case "error": + lvl = slog.LevelError + default: + lvl = slog.LevelInfo + } + + handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: lvl, + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + return slog.Attr{Key: "time", Value: a.Value} + } + if a.Key == slog.MessageKey { + return slog.Attr{Key: "event", Value: a.Value} + } + return a + }, + }) + return slog.New(handler) +} + +// WithRequestID stores the request ID in the context for downstream loggers. +func WithRequestID(ctx context.Context, id string) context.Context { + return context.WithValue(ctx, requestIDKey, id) +} + +// RequestIDFrom returns the request ID stored in ctx, or empty string if absent. +func RequestIDFrom(ctx context.Context) string { + if v, ok := ctx.Value(requestIDKey).(string); ok { + return v + } + return "" +} + +// FromContext returns a logger pre-bound with the request_id from ctx (if any). +func FromContext(ctx context.Context, base *slog.Logger) *slog.Logger { + if id := RequestIDFrom(ctx); id != "" { + return base.With("request_id", id) + } + return base +}