// Package logging собирает slog-логгер по настройкам из конфига. package logging import ( "io" "log/slog" "os" "strings" "time" ) // New возвращает slog-логгер с указанным уровнем и форматом ("json"|"text"). func New(level, format string) *slog.Logger { return NewTo(os.Stdout, level, format) } // NewErr — тот же логгер, что New, но в stderr. // // Нужен командам CLI: stdout у них занят отчётом человеку, и лог в том же // потоке сделал бы отчёт неразбираемым — а именно его человек перенаправляет в // файл и читает глазами. func NewErr(level, format string) *slog.Logger { return NewTo(os.Stderr, level, format) } // NewTo — общая форма: приёмник вывода параметром, как у log.New и // slog.NewTextHandler. New и NewErr — тонкие обёртки над ней; отдельные имена // существуют ради читаемости места вызова, а не ради разного поведения. func NewTo(w io.Writer, level, format string) *slog.Logger { opts := &slog.HandlerOptions{Level: parseLevel(level), ReplaceAttr: utcTime} var handler slog.Handler if strings.EqualFold(format, "text") { handler = slog.NewTextHandler(w, opts) } else { handler = slog.NewJSONHandler(w, opts) } return slog.New(handler) } // NewStderr — JSON-логгер в stderr для фатальных ошибок старта, когда основной // логгер ещё не собран (конфиг не прочитан). Уровень и формат брать неоткуда, // поэтому они фиксированы — этим он и отличается от NewErr. func NewStderr() *slog.Logger { return NewTo(os.Stderr, "info", "json") } // utcTime приводит метку времени записи к UTC: однозначный порядок событий и // лексикографическая сортировка. func utcTime(groups []string, a slog.Attr) slog.Attr { if len(groups) == 0 && a.Key == slog.TimeKey { if t, ok := a.Value.Any().(time.Time); ok { a.Value = slog.TimeValue(t.UTC()) } } return a } func parseLevel(level string) slog.Level { switch strings.ToLower(level) { case "debug": return slog.LevelDebug case "warn", "warning": return slog.LevelWarn case "error": return slog.LevelError default: return slog.LevelInfo } }