// Package logging configures the process-wide structured logger from // environment-driven settings. package logging import ( "context" "crypto/rand" "encoding/hex" "fmt" "log/slog" "os" "strings" ) // ParseLevel maps a textual level name to a slog.Level. func ParseLevel(name string) (slog.Level, error) { switch strings.ToLower(strings.TrimSpace(name)) { case "debug": return slog.LevelDebug, nil case "info": return slog.LevelInfo, nil case "warn", "warning": return slog.LevelWarn, nil case "error": return slog.LevelError, nil default: return 0, fmt.Errorf("invalid log level %q (want debug, info, warn or error)", name) } } // ParseFormat normalizes a format name. An empty name defaults to JSON. func ParseFormat(name string) (string, error) { switch strings.ToLower(strings.TrimSpace(name)) { case "", "json": return "json", nil case "text": return "text", nil default: return "", fmt.Errorf("invalid log format %q (want json or text)", name) } } // NewHandler builds a slog handler writing to stdout in the requested format. // An empty format defaults to JSON. func NewHandler(format string, level slog.Level) (slog.Handler, error) { f, err := ParseFormat(format) if err != nil { return nil, err } opts := &slog.HandlerOptions{Level: level} if f == "text" { return slog.NewTextHandler(os.Stdout, opts), nil } return slog.NewJSONHandler(os.Stdout, opts), nil } type requestIDKey struct{} // WithRequestID stores a request identifier in the context. func WithRequestID(ctx context.Context, id string) context.Context { return context.WithValue(ctx, requestIDKey{}, id) } // RequestID returns the request identifier stored in the context, or "". func RequestID(ctx context.Context) string { if id, ok := ctx.Value(requestIDKey{}).(string); ok { return id } return "" } // NewRequestID returns a fresh random request identifier. func NewRequestID() string { var b [16]byte if _, err := rand.Read(b[:]); err != nil { return "unknown" } return hex.EncodeToString(b[:]) } // ValidRequestID reports whether an incoming identifier is safe to reuse. func ValidRequestID(id string) bool { if id == "" || len(id) > 128 { return false } for _, r := range id { switch { case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9': case r == '-', r == '_', r == '.': default: return false } } return true }