// Package applog provides geniusrun's structured JSON logging: a // log/slog-based logger writing to stdout, plus context helpers so a // logger enriched in one layer (e.g. internal/api's HTTP middleware, // attaching a request_id) is picked up by another (e.g. internal/garmin's // wrapper-call logging) without either package depending on the other. package applog import ( "context" "io" "log/slog" "strings" ) // NewLogger builds a JSON-handler *slog.Logger writing to w at the given // level ("debug"|"info"|"warn"|"error", case-insensitive; anything else // defaults to info). func NewLogger(level string, w io.Writer) *slog.Logger { return slog.New(slog.NewJSONHandler(w, &slog.HandlerOptions{Level: parseLevel(level)})) } func parseLevel(level string) slog.Level { switch strings.ToLower(level) { case "debug": return slog.LevelDebug case "warn": return slog.LevelWarn case "error": return slog.LevelError default: return slog.LevelInfo } } type ctxKey struct{} // WithLogger returns a context carrying logger, retrievable via FromContext. func WithLogger(ctx context.Context, logger *slog.Logger) context.Context { return context.WithValue(ctx, ctxKey{}, logger) } // FromContext returns the logger stashed by WithLogger, or slog.Default() // if none was -- callers (e.g. the background incremental-sync loop, or // tests that don't bother injecting one) always get a working logger, // never nil. func FromContext(ctx context.Context) *slog.Logger { if logger, ok := ctx.Value(ctxKey{}).(*slog.Logger); ok { return logger } return slog.Default() }