chore(api-gateway): merge review docs and update dependencies
This commit is contained in:
77
services/push-gateway/internal/observability/logger.go
Normal file
77
services/push-gateway/internal/observability/logger.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Package observability provides slog-based structured logging for push-gateway.
|
||||
//
|
||||
// The logger emits JSON in production and a human-readable console format in
|
||||
// development (DEV_MODE=true). Trace correlation is handled by attaching a
|
||||
// trace_id attribute from the OpenTelemetry span context when present.
|
||||
package observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// ctxKey is an unexported type so context values cannot collide with callers.
|
||||
type ctxKey struct{}
|
||||
|
||||
// logger is the process-wide *slog.Logger initialized by InitLogger.
|
||||
var logger *slog.Logger
|
||||
|
||||
// InitLogger configures the global slog logger. In DevMode a console handler
|
||||
// with colored levels is used; otherwise a JSON handler at Info level is used.
|
||||
// The logger is tagged with service=push-gateway on every emitted record.
|
||||
func InitLogger(devMode bool) *slog.Logger {
|
||||
var handler slog.Handler
|
||||
opts := &slog.HandlerOptions{
|
||||
Level: slog.LevelInfo,
|
||||
}
|
||||
if devMode {
|
||||
opts.Level = slog.LevelDebug
|
||||
handler = slog.NewTextHandler(os.Stdout, opts)
|
||||
} else {
|
||||
handler = slog.NewJSONHandler(os.Stdout, opts)
|
||||
}
|
||||
logger = slog.New(handler).With("service", "push-gateway")
|
||||
slog.SetDefault(logger)
|
||||
return logger
|
||||
}
|
||||
|
||||
// Logger returns the process-wide logger initialized by InitLogger. When Init
|
||||
// has not been called a fallback JSON logger is returned so callers never
|
||||
// receive a nil logger.
|
||||
func Logger() *slog.Logger {
|
||||
if logger == nil {
|
||||
logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)).With("service", "push-gateway")
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// WithContext returns a copy of ctx carrying l so it can later be retrieved
|
||||
// via FromContext.
|
||||
func WithContext(ctx context.Context, l *slog.Logger) context.Context {
|
||||
return context.WithValue(ctx, ctxKey{}, l)
|
||||
}
|
||||
|
||||
// FromContext returns the logger stored in ctx. When an OpenTelemetry span is
|
||||
// active the returned logger is decorated with a trace_id field so log lines
|
||||
// can be correlated to traces. If no logger was stored in ctx the
|
||||
// process-wide logger is returned (never nil).
|
||||
func FromContext(ctx context.Context) *slog.Logger {
|
||||
l, ok := ctx.Value(ctxKey{}).(*slog.Logger)
|
||||
if !ok || l == nil {
|
||||
l = Logger()
|
||||
}
|
||||
if sc := trace.SpanContextFromContext(ctx); sc.HasTraceID() {
|
||||
return l.With("trace_id", sc.TraceID().String())
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Now returns the current UTC time in RFC3339 format. Convenience helper for
|
||||
// log field construction.
|
||||
func Now() string {
|
||||
return time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
Reference in New Issue
Block a user