|
1 | 1 | package logger |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "context" |
5 | | - "fmt" |
6 | | - "io" |
7 | 4 | "log/slog" |
8 | 5 | "os" |
| 6 | + "sync" |
9 | 7 | ) |
10 | 8 |
|
11 | | -// fieldLogger is a Logger implementation backed by log/slog |
12 | | -type fieldLogger struct { |
13 | | - logger *slog.Logger |
14 | | - attrs []slog.Attr |
15 | | -} |
16 | | - |
17 | | -// SetOutput sets the output writer for the logger. |
18 | | -func (s *fieldLogger) SetOutput(w io.Writer) { |
19 | | - opts := &slog.HandlerOptions{} |
20 | | - s.logger = slog.New(slog.NewTextHandler(w, opts)) |
21 | | -} |
22 | | - |
23 | | -func (s *fieldLogger) WithField(key string, value any) FieldLogger { |
24 | | - return s.WithFields(map[string]any{key: value}) |
25 | | -} |
26 | | - |
27 | | -func (s *fieldLogger) WithFields(m map[string]any) FieldLogger { |
28 | | - attrs := make([]slog.Attr, 0, len(m)+len(s.attrs)) |
29 | | - attrs = append(attrs, s.attrs...) |
30 | | - for k, v := range m { |
31 | | - attrs = append(attrs, slog.Any(k, v)) |
32 | | - } |
33 | | - return &fieldLogger{ |
34 | | - logger: s.logger, |
35 | | - attrs: attrs, |
36 | | - } |
37 | | -} |
| 9 | +var ( |
| 10 | + defaultLogger FieldLogger |
| 11 | + defaultLoggerOnce sync.Once |
| 12 | +) |
38 | 13 |
|
39 | | -func (s *fieldLogger) log(level slog.Level, msg string, args ...any) { |
40 | | - if len(args) > 0 { |
41 | | - msg = fmt.Sprintf(msg, args...) |
| 14 | +// NewLogger based on the specified log level, defaults to "debug". |
| 15 | +// See `New` for more details. |
| 16 | +func NewLogger(level string) FieldLogger { |
| 17 | + lvl, err := ParseLevel(level) |
| 18 | + if err != nil { |
| 19 | + lvl = DebugLevel |
42 | 20 | } |
43 | | - s.logger.Log(context.Background(), level, msg, s.attrsToAny()...) |
| 21 | + return New(lvl) |
44 | 22 | } |
45 | 23 |
|
46 | | -func (s *fieldLogger) attrsToAny() []any { |
47 | | - result := make([]any, len(s.attrs)) |
48 | | - for i, a := range s.attrs { |
49 | | - result[i] = a |
50 | | - } |
51 | | - return result |
| 24 | +// Default returns the default FieldLogger instance. |
| 25 | +// The default logger is initialized with InfoLevel on first call. |
| 26 | +func Default() FieldLogger { |
| 27 | + defaultLoggerOnce.Do(func() { |
| 28 | + if defaultLogger == nil { |
| 29 | + defaultLogger = New(InfoLevel) |
| 30 | + } |
| 31 | + }) |
| 32 | + return defaultLogger |
52 | 33 | } |
53 | 34 |
|
54 | | -func (s *fieldLogger) Debugf(msg string, args ...any) { |
55 | | - s.log(slog.LevelDebug, msg, args...) |
| 35 | +// SetDefault sets the default logger instance. |
| 36 | +// Subsequent calls to Default() and package-level logging functions will use this logger. |
| 37 | +func SetDefault(l FieldLogger) { |
| 38 | + defaultLogger = l |
56 | 39 | } |
57 | 40 |
|
58 | | -func (s *fieldLogger) Infof(msg string, args ...any) { |
59 | | - s.log(slog.LevelInfo, msg, args...) |
| 41 | +// Debugf logs a formatted debug message using the default logger. |
| 42 | +func Debugf(msg string, args ...any) { |
| 43 | + Default().Debugf(msg, args...) |
60 | 44 | } |
61 | 45 |
|
62 | | -func (s *fieldLogger) Printf(msg string, args ...any) { |
63 | | - s.Infof(msg, args...) |
| 46 | +// Infof logs a formatted info message using the default logger. |
| 47 | +func Infof(msg string, args ...any) { |
| 48 | + Default().Infof(msg, args...) |
64 | 49 | } |
65 | 50 |
|
66 | | -func (s *fieldLogger) Warnf(msg string, args ...any) { |
67 | | - s.log(slog.LevelWarn, msg, args...) |
| 51 | +// Printf logs a formatted message using the default logger. |
| 52 | +func Printf(msg string, args ...any) { |
| 53 | + Default().Printf(msg, args...) |
68 | 54 | } |
69 | 55 |
|
70 | | -func (s *fieldLogger) Errorf(msg string, args ...any) { |
71 | | - s.log(slog.LevelError, msg, args...) |
| 56 | +// Warnf logs a formatted warning message using the default logger. |
| 57 | +func Warnf(msg string, args ...any) { |
| 58 | + Default().Warnf(msg, args...) |
72 | 59 | } |
73 | 60 |
|
74 | | -func (s *fieldLogger) Fatalf(msg string, args ...any) { |
75 | | - s.log(slog.Level(12), msg, args...) |
76 | | - os.Exit(1) |
| 61 | +// Errorf logs a formatted error message using the default logger. |
| 62 | +func Errorf(msg string, args ...any) { |
| 63 | + Default().Errorf(msg, args...) |
77 | 64 | } |
78 | 65 |
|
79 | | -func (s *fieldLogger) Debug(args ...any) { |
80 | | - s.log(slog.LevelDebug, fmt.Sprint(args...)) |
| 66 | +// Fatalf logs a formatted fatal message using the default logger and exits the program. |
| 67 | +func Fatalf(msg string, args ...any) { |
| 68 | + Default().Fatalf(msg, args...) |
81 | 69 | } |
82 | 70 |
|
83 | | -func (s *fieldLogger) Info(args ...any) { |
84 | | - s.log(slog.LevelInfo, fmt.Sprint(args...)) |
| 71 | +// Debug logs a debug message using the default logger. |
| 72 | +func Debug(args ...any) { |
| 73 | + Default().Debug(args...) |
85 | 74 | } |
86 | 75 |
|
87 | | -func (s *fieldLogger) Warn(args ...any) { |
88 | | - s.log(slog.LevelWarn, fmt.Sprint(args...)) |
| 76 | +// Info logs an info message using the default logger. |
| 77 | +func Info(args ...any) { |
| 78 | + Default().Info(args...) |
89 | 79 | } |
90 | 80 |
|
91 | | -func (s *fieldLogger) Error(args ...any) { |
92 | | - s.log(slog.LevelError, fmt.Sprint(args...)) |
| 81 | +// Warn logs a warning message using the default logger. |
| 82 | +func Warn(args ...any) { |
| 83 | + Default().Warn(args...) |
93 | 84 | } |
94 | 85 |
|
95 | | -func (s *fieldLogger) Fatal(args ...any) { |
96 | | - s.log(slog.Level(12), fmt.Sprint(args...)) |
97 | | - os.Exit(1) |
| 86 | +// Error logs an error message using the default logger. |
| 87 | +func Error(args ...any) { |
| 88 | + Default().Error(args...) |
98 | 89 | } |
99 | 90 |
|
100 | | -func (s *fieldLogger) Panic(args ...any) { |
101 | | - msg := fmt.Sprint(args...) |
102 | | - s.log(slog.Level(12), msg) |
103 | | - panic(msg) |
| 91 | +// Fatal logs a fatal message using the default logger and exits the program. |
| 92 | +func Fatal(args ...any) { |
| 93 | + Default().Fatal(args...) |
104 | 94 | } |
105 | 95 |
|
106 | | -// ParseLevel parses a string level into a Level. |
107 | | -func ParseLevel(level string) (Level, error) { |
108 | | - switch level { |
109 | | - case "panic": |
110 | | - return PanicLevel, nil |
111 | | - case "fatal": |
112 | | - return FatalLevel, nil |
113 | | - case "error": |
114 | | - return ErrorLevel, nil |
115 | | - case "warn", "warning": |
116 | | - return WarnLevel, nil |
117 | | - case "info": |
118 | | - return InfoLevel, nil |
119 | | - case "debug": |
120 | | - return DebugLevel, nil |
121 | | - } |
122 | | - |
123 | | - var l Level |
124 | | - return l, fmt.Errorf("not a valid Level: %q", level) |
| 96 | +// Panic logs a panic message using the default logger and panics. |
| 97 | +func Panic(args ...any) { |
| 98 | + Default().Panic(args...) |
125 | 99 | } |
126 | 100 |
|
127 | | -// NewLogger based on the specified log level, defaults to "debug". |
128 | | -// See `New` for more details. |
129 | | -func NewLogger(level string) FieldLogger { |
130 | | - lvl, err := ParseLevel(level) |
131 | | - if err != nil { |
132 | | - lvl = DebugLevel |
133 | | - } |
134 | | - return New(lvl) |
| 101 | +// WithField adds a field to the default logger and returns a new FieldLogger. |
| 102 | +func WithField(key string, value any) FieldLogger { |
| 103 | + return Default().WithField(key, value) |
135 | 104 | } |
136 | 105 |
|
137 | | -// New based on the specified log level, defaults to "debug". |
138 | | -// This logger will log to the STDOUT in a human readable, |
139 | | -// but parseable form. |
140 | | -// |
141 | | -// Example: time="2016-12-01T21:02:07-05:00" level=info duration=225.283µs human_size="106 B" method=GET path="/" render=199.79µs request_id=2265736089 size=106 status=200 |
142 | | -func New(lvl Level) FieldLogger { |
143 | | - return newSlog(lvl) |
| 106 | +// WithFields adds multiple fields to the default logger and returns a new FieldLogger. |
| 107 | +func WithFields(fields map[string]any) FieldLogger { |
| 108 | + return Default().WithFields(fields) |
144 | 109 | } |
145 | 110 |
|
146 | | -func newSlog(lvl Level) FieldLogger { |
| 111 | +func newDefault(lvl Level) FieldLogger { |
147 | 112 | opts := &slog.HandlerOptions{ |
148 | | - Level: toSlogLevel(lvl), |
| 113 | + Level: toDefaultLevel(lvl), |
149 | 114 | } |
150 | 115 | handler := slog.NewTextHandler(os.Stdout, opts) |
151 | 116 | return &fieldLogger{ |
152 | 117 | logger: slog.New(handler), |
| 118 | + level: lvl, |
153 | 119 | } |
154 | 120 | } |
155 | 121 |
|
156 | | -// toSlogLevel converts our Level to slog.Level. |
157 | | -func toSlogLevel(l Level) slog.Level { |
| 122 | +// toDefaultLevel converts our Level to slog.Level. |
| 123 | +func toDefaultLevel(l Level) slog.Level { |
158 | 124 | switch l { |
159 | 125 | case PanicLevel, FatalLevel: |
160 | 126 | return slog.Level(12) // custom level above error |
|
0 commit comments