feat(logging): structured JSON logger with request_id context
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user