1
2
3
4
5
6
7
8
9
10
11 package slog
12
13 import (
14 "context"
15 "fmt"
16 "os"
17 "runtime"
18 "sync"
19 "time"
20
21 "go.opencensus.io/trace"
22 )
23
24 var defaultExitFn = os.Exit
25
26
27
28
29 type Sink interface {
30 LogEntry(ctx context.Context, e SinkEntry)
31 Sync()
32 }
33
34
35
36
37
38 func (l Logger) Log(ctx context.Context, e SinkEntry) {
39 if e.Level < l.level {
40 return
41 }
42
43 e.Fields = l.fields.append(e.Fields)
44 e.LoggerNames = appendNames(l.names, e.LoggerNames...)
45
46 for _, s := range l.sinks {
47 s.LogEntry(ctx, e)
48 }
49 }
50
51
52 func (l Logger) Sync() {
53 for _, s := range l.sinks {
54 s.Sync()
55 }
56 }
57
58
59
60
61 type Logger struct {
62 sinks []Sink
63 level Level
64
65 names []string
66 fields Map
67
68 skip int
69 exit func(int)
70 }
71
72
73 func Make(sinks ...Sink) Logger {
74 return Logger{
75 sinks: sinks,
76 level: LevelInfo,
77
78 exit: os.Exit,
79 }
80 }
81
82
83 func (l Logger) Debug(ctx context.Context, msg string, fields ...Field) {
84 l.log(ctx, LevelDebug, msg, fields)
85 }
86
87
88 func (l Logger) Info(ctx context.Context, msg string, fields ...Field) {
89 l.log(ctx, LevelInfo, msg, fields)
90 }
91
92
93 func (l Logger) Warn(ctx context.Context, msg string, fields ...Field) {
94 l.log(ctx, LevelWarn, msg, fields)
95 }
96
97
98
99
100 func (l Logger) Error(ctx context.Context, msg string, fields ...Field) {
101 l.log(ctx, LevelError, msg, fields)
102 l.Sync()
103 }
104
105
106
107
108 func (l Logger) Critical(ctx context.Context, msg string, fields ...Field) {
109 l.log(ctx, LevelCritical, msg, fields)
110 l.Sync()
111 }
112
113
114
115
116 func (l Logger) Fatal(ctx context.Context, msg string, fields ...Field) {
117 l.log(ctx, LevelFatal, msg, fields)
118 l.Sync()
119
120 if l.exit == nil {
121 l.exit = defaultExitFn
122 }
123
124 l.exit(1)
125 }
126
127
128
129
130
131 func (l Logger) With(fields ...Field) Logger {
132 l.fields = l.fields.append(fields)
133 return l
134 }
135
136
137
138 func (l Logger) Named(name string) Logger {
139 l.names = appendNames(l.names, name)
140 return l
141 }
142
143
144
145 func (l Logger) Leveled(level Level) Logger {
146 l.level = level
147 l.sinks = append([]Sink(nil), l.sinks...)
148 return l
149 }
150
151
152
153 func (l Logger) AppendSinks(s ...Sink) Logger {
154 l.sinks = append(l.sinks, s...)
155 return l
156 }
157
158 func (l Logger) log(ctx context.Context, level Level, msg string, fields Map) {
159 ent := l.entry(ctx, level, msg, fields)
160 l.Log(ctx, ent)
161 }
162
163 func (l Logger) entry(ctx context.Context, level Level, msg string, fields Map) SinkEntry {
164 ent := SinkEntry{
165 Time: time.Now().UTC(),
166 Level: level,
167 Message: msg,
168 Fields: fieldsFromContext(ctx).append(fields),
169 SpanContext: trace.FromContext(ctx).SpanContext(),
170 }
171 ent = ent.fillLoc(l.skip + 3)
172 return ent
173 }
174
175 var helpers sync.Map
176
177
178
179
180 func Helper() {
181 _, _, fn := location(1)
182 helpers.LoadOrStore(fn, struct{}{})
183 }
184
185 func (ent SinkEntry) fillFromFrame(f runtime.Frame) SinkEntry {
186 ent.Func = f.Function
187 ent.File = f.File
188 ent.Line = f.Line
189 return ent
190 }
191
192 func (ent SinkEntry) fillLoc(skip int) SinkEntry {
193
194 const maxStackLen = 50
195 var pc [maxStackLen]uintptr
196
197
198
199 n := runtime.Callers(skip+2, pc[:])
200 frames := runtime.CallersFrames(pc[:n])
201 for {
202 frame, more := frames.Next()
203 _, helper := helpers.Load(frame.Function)
204 if !helper || !more {
205
206
207 return ent.fillFromFrame(frame)
208 }
209 }
210 }
211
212 func location(skip int) (file string, line int, fn string) {
213 pc, file, line, _ := runtime.Caller(skip + 1)
214 f := runtime.FuncForPC(pc)
215 return file, line, f.Name()
216 }
217
218 func appendNames(names []string, names2 ...string) []string {
219 if len(names2) == 0 {
220 return names
221 }
222 names3 := make([]string, 0, len(names)+len(names2))
223 names3 = append(names3, names...)
224 names3 = append(names3, names2...)
225 return names3
226 }
227
228
229 type Field struct {
230 Name string
231 Value interface{}
232 }
233
234
235 func F(name string, value interface{}) Field {
236 return Field{Name: name, Value: value}
237 }
238
239
240 func M(fs ...Field) Map {
241 return fs
242 }
243
244
245 func Error(err error) Field {
246 return F("error", err)
247 }
248
249 type fieldsKey struct{}
250
251 func fieldsWithContext(ctx context.Context, fields Map) context.Context {
252 return context.WithValue(ctx, fieldsKey{}, fields)
253 }
254
255 func fieldsFromContext(ctx context.Context) Map {
256 l, _ := ctx.Value(fieldsKey{}).(Map)
257 return l
258 }
259
260
261
262
263
264
265 func With(ctx context.Context, fields ...Field) context.Context {
266 f1 := fieldsFromContext(ctx)
267 f2 := f1.append(fields)
268 return fieldsWithContext(ctx, f2)
269 }
270
271
272
273 type SinkEntry struct {
274 Time time.Time
275
276 Level Level
277 Message string
278
279 LoggerNames []string
280
281 Func string
282 File string
283 Line int
284
285 SpanContext trace.SpanContext
286
287 Fields Map
288 }
289
290
291 type Level int
292
293
294
295
296 const (
297
298 LevelDebug Level = iota
299
300
301 LevelInfo
302
303
304 LevelWarn
305
306
307 LevelError
308
309
310
311 LevelCritical
312
313
314 LevelFatal
315 )
316
317 var levelStrings = map[Level]string{
318 LevelDebug: "DEBUG",
319 LevelInfo: "INFO",
320 LevelWarn: "WARN",
321 LevelError: "ERROR",
322 LevelCritical: "CRITICAL",
323 LevelFatal: "FATAL",
324 }
325
326
327 func (l Level) String() string {
328 s, ok := levelStrings[l]
329 if !ok {
330 return fmt.Sprintf("slog.Level(%v)", int(l))
331 }
332 return s
333 }
334
View as plain text