package logger import ( "context" "log/slog" "os" "go.opentelemetry.io/otel/trace" ) type contextKey string const requestIDKey contextKey = "request_id" // New creates a logger with the specified format (json or text) and level. func New(format string, level string) *slog.Logger { var handler slog.Handler logLevel := parseLevel(level) opts := &slog.HandlerOptions{ Level: logLevel, AddSource: true, // Add file:line info for debugging } if format == "json" { handler = slog.NewJSONHandler(os.Stdout, opts) } else { handler = slog.NewTextHandler(os.Stdout, opts) } return slog.New(handler) } // parseLevel converts a string level to slog.Level. func parseLevel(level string) slog.Level { switch level { case "debug": return slog.LevelDebug case "info": return slog.LevelInfo case "warn": return slog.LevelWarn case "error": return slog.LevelError default: return slog.LevelInfo } } // WithRequestID adds a request ID to the context for tracing. func WithRequestID(ctx context.Context, requestID string) context.Context { return context.WithValue(ctx, requestIDKey, requestID) } // FromContext extracts the request ID from context, or returns empty string. func FromContext(ctx context.Context) string { if id, ok := ctx.Value(requestIDKey).(string); ok { return id } return "" } // LogAttrsWithTrace adds trace context to log attributes for correlation. func LogAttrsWithTrace(ctx context.Context, attrs ...any) []any { spanCtx := trace.SpanFromContext(ctx).SpanContext() if spanCtx.IsValid() { attrs = append(attrs, slog.String("trace_id", spanCtx.TraceID().String()), slog.String("span_id", spanCtx.SpanID().String()), ) } return attrs }