]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/handler.go
log/slog: doc commonHandler.handle
[gostls13.git] / src / log / slog / handler.go
1 // Copyright 2022 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package slog
6
7 import (
8         "context"
9         "fmt"
10         "io"
11         "log/slog/internal/buffer"
12         "slices"
13         "strconv"
14         "sync"
15         "time"
16 )
17
18 // A Handler handles log records produced by a Logger..
19 //
20 // A typical handler may print log records to standard error,
21 // or write them to a file or database, or perhaps augment them
22 // with additional attributes and pass them on to another handler.
23 //
24 // Any of the Handler's methods may be called concurrently with itself
25 // or with other methods. It is the responsibility of the Handler to
26 // manage this concurrency.
27 //
28 // Users of the slog package should not invoke Handler methods directly.
29 // They should use the methods of [Logger] instead.
30 type Handler interface {
31         // Enabled reports whether the handler handles records at the given level.
32         // The handler ignores records whose level is lower.
33         // It is called early, before any arguments are processed,
34         // to save effort if the log event should be discarded.
35         // If called from a Logger method, the first argument is the context
36         // passed to that method, or context.Background() if nil was passed
37         // or the method does not take a context.
38         // The context is passed so Enabled can use its values
39         // to make a decision.
40         Enabled(context.Context, Level) bool
41
42         // Handle handles the Record.
43         // It will only be called when Enabled returns true.
44         // The Context argument is as for Enabled.
45         // It is present solely to provide Handlers access to the context's values.
46         // Canceling the context should not affect record processing.
47         // (Among other things, log messages may be necessary to debug a
48         // cancellation-related problem.)
49         //
50         // Handle methods that produce output should observe the following rules:
51         //   - If r.Time is the zero time, ignore the time.
52         //   - If r.PC is zero, ignore it.
53         //   - Attr's values should be resolved.
54         //   - If an Attr's key and value are both the zero value, ignore the Attr.
55         //     This can be tested with attr.Equal(Attr{}).
56         //   - If a group's key is empty, inline the group's Attrs.
57         //   - If a group has no Attrs (even if it has a non-empty key),
58         //     ignore it.
59         Handle(context.Context, Record) error
60
61         // WithAttrs returns a new Handler whose attributes consist of
62         // both the receiver's attributes and the arguments.
63         // The Handler owns the slice: it may retain, modify or discard it.
64         WithAttrs(attrs []Attr) Handler
65
66         // WithGroup returns a new Handler with the given group appended to
67         // the receiver's existing groups.
68         // The keys of all subsequent attributes, whether added by With or in a
69         // Record, should be qualified by the sequence of group names.
70         //
71         // How this qualification happens is up to the Handler, so long as
72         // this Handler's attribute keys differ from those of another Handler
73         // with a different sequence of group names.
74         //
75         // A Handler should treat WithGroup as starting a Group of Attrs that ends
76         // at the end of the log event. That is,
77         //
78         //     logger.WithGroup("s").LogAttrs(level, msg, slog.Int("a", 1), slog.Int("b", 2))
79         //
80         // should behave like
81         //
82         //     logger.LogAttrs(level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
83         //
84         // If the name is empty, WithGroup returns the receiver.
85         WithGroup(name string) Handler
86 }
87
88 type defaultHandler struct {
89         ch *commonHandler
90         // internal.DefaultOutput, except for testing
91         output func(pc uintptr, data []byte) error
92 }
93
94 func newDefaultHandler(output func(uintptr, []byte) error) *defaultHandler {
95         return &defaultHandler{
96                 ch:     &commonHandler{json: false},
97                 output: output,
98         }
99 }
100
101 func (*defaultHandler) Enabled(_ context.Context, l Level) bool {
102         return l >= LevelInfo
103 }
104
105 // Collect the level, attributes and message in a string and
106 // write it with the default log.Logger.
107 // Let the log.Logger handle time and file/line.
108 func (h *defaultHandler) Handle(ctx context.Context, r Record) error {
109         buf := buffer.New()
110         buf.WriteString(r.Level.String())
111         buf.WriteByte(' ')
112         buf.WriteString(r.Message)
113         state := h.ch.newHandleState(buf, true, " ")
114         defer state.free()
115         state.appendNonBuiltIns(r)
116         return h.output(r.PC, *buf)
117 }
118
119 func (h *defaultHandler) WithAttrs(as []Attr) Handler {
120         return &defaultHandler{h.ch.withAttrs(as), h.output}
121 }
122
123 func (h *defaultHandler) WithGroup(name string) Handler {
124         return &defaultHandler{h.ch.withGroup(name), h.output}
125 }
126
127 // HandlerOptions are options for a TextHandler or JSONHandler.
128 // A zero HandlerOptions consists entirely of default values.
129 type HandlerOptions struct {
130         // AddSource causes the handler to compute the source code position
131         // of the log statement and add a SourceKey attribute to the output.
132         AddSource bool
133
134         // Level reports the minimum record level that will be logged.
135         // The handler discards records with lower levels.
136         // If Level is nil, the handler assumes LevelInfo.
137         // The handler calls Level.Level for each record processed;
138         // to adjust the minimum level dynamically, use a LevelVar.
139         Level Leveler
140
141         // ReplaceAttr is called to rewrite each non-group attribute before it is logged.
142         // The attribute's value has been resolved (see [Value.Resolve]).
143         // If ReplaceAttr returns a zero Attr, the attribute is discarded.
144         //
145         // The built-in attributes with keys "time", "level", "source", and "msg"
146         // are passed to this function, except that time is omitted
147         // if zero, and source is omitted if AddSource is false.
148         //
149         // The first argument is a list of currently open groups that contain the
150         // Attr. It must not be retained or modified. ReplaceAttr is never called
151         // for Group attributes, only their contents. For example, the attribute
152         // list
153         //
154         //     Int("a", 1), Group("g", Int("b", 2)), Int("c", 3)
155         //
156         // results in consecutive calls to ReplaceAttr with the following arguments:
157         //
158         //     nil, Int("a", 1)
159         //     []string{"g"}, Int("b", 2)
160         //     nil, Int("c", 3)
161         //
162         // ReplaceAttr can be used to change the default keys of the built-in
163         // attributes, convert types (for example, to replace a `time.Time` with the
164         // integer seconds since the Unix epoch), sanitize personal information, or
165         // remove attributes from the output.
166         ReplaceAttr func(groups []string, a Attr) Attr
167 }
168
169 // Keys for "built-in" attributes.
170 const (
171         // TimeKey is the key used by the built-in handlers for the time
172         // when the log method is called. The associated Value is a [time.Time].
173         TimeKey = "time"
174         // LevelKey is the key used by the built-in handlers for the level
175         // of the log call. The associated value is a [Level].
176         LevelKey = "level"
177         // MessageKey is the key used by the built-in handlers for the
178         // message of the log call. The associated value is a string.
179         MessageKey = "msg"
180         // SourceKey is the key used by the built-in handlers for the source file
181         // and line of the log call. The associated value is a string.
182         SourceKey = "source"
183 )
184
185 type commonHandler struct {
186         json              bool // true => output JSON; false => output text
187         opts              HandlerOptions
188         preformattedAttrs []byte
189         // groupPrefix is for the text handler only.
190         // It holds the prefix for groups that were already pre-formatted.
191         // A group will appear here when a call to WithGroup is followed by
192         // a call to WithAttrs.
193         groupPrefix string
194         groups      []string // all groups started from WithGroup
195         nOpenGroups int      // the number of groups opened in preformattedAttrs
196         mu          *sync.Mutex
197         w           io.Writer
198 }
199
200 func (h *commonHandler) clone() *commonHandler {
201         // We can't use assignment because we can't copy the mutex.
202         return &commonHandler{
203                 json:              h.json,
204                 opts:              h.opts,
205                 preformattedAttrs: slices.Clip(h.preformattedAttrs),
206                 groupPrefix:       h.groupPrefix,
207                 groups:            slices.Clip(h.groups),
208                 nOpenGroups:       h.nOpenGroups,
209                 w:                 h.w,
210                 mu:                h.mu, // mutex shared among all clones of this handler
211         }
212 }
213
214 // enabled reports whether l is greater than or equal to the
215 // minimum level.
216 func (h *commonHandler) enabled(l Level) bool {
217         minLevel := LevelInfo
218         if h.opts.Level != nil {
219                 minLevel = h.opts.Level.Level()
220         }
221         return l >= minLevel
222 }
223
224 func (h *commonHandler) withAttrs(as []Attr) *commonHandler {
225         // We are going to ignore empty groups, so if the entire slice consists of
226         // them, there is nothing to do.
227         if countEmptyGroups(as) == len(as) {
228                 return h
229         }
230         h2 := h.clone()
231         // Pre-format the attributes as an optimization.
232         state := h2.newHandleState((*buffer.Buffer)(&h2.preformattedAttrs), false, "")
233         defer state.free()
234         state.prefix.WriteString(h.groupPrefix)
235         if len(h2.preformattedAttrs) > 0 {
236                 state.sep = h.attrSep()
237         }
238         state.openGroups()
239         for _, a := range as {
240                 state.appendAttr(a)
241         }
242         // Remember the new prefix for later keys.
243         h2.groupPrefix = state.prefix.String()
244         // Remember how many opened groups are in preformattedAttrs,
245         // so we don't open them again when we handle a Record.
246         h2.nOpenGroups = len(h2.groups)
247         return h2
248 }
249
250 func (h *commonHandler) withGroup(name string) *commonHandler {
251         h2 := h.clone()
252         h2.groups = append(h2.groups, name)
253         return h2
254 }
255
256 // handle is the internal implementation of Handler.Handle
257 // used by TextHandler and JSONHandler.
258 func (h *commonHandler) handle(r Record) error {
259         state := h.newHandleState(buffer.New(), true, "")
260         defer state.free()
261         if h.json {
262                 state.buf.WriteByte('{')
263         }
264         // Built-in attributes. They are not in a group.
265         stateGroups := state.groups
266         state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups.
267         rep := h.opts.ReplaceAttr
268         // time
269         if !r.Time.IsZero() {
270                 key := TimeKey
271                 val := r.Time.Round(0) // strip monotonic to match Attr behavior
272                 if rep == nil {
273                         state.appendKey(key)
274                         state.appendTime(val)
275                 } else {
276                         state.appendAttr(Time(key, val))
277                 }
278         }
279         // level
280         key := LevelKey
281         val := r.Level
282         if rep == nil {
283                 state.appendKey(key)
284                 state.appendString(val.String())
285         } else {
286                 state.appendAttr(Any(key, val))
287         }
288         // source
289         if h.opts.AddSource {
290                 state.appendAttr(Any(SourceKey, r.source()))
291         }
292         key = MessageKey
293         msg := r.Message
294         if rep == nil {
295                 state.appendKey(key)
296                 state.appendString(msg)
297         } else {
298                 state.appendAttr(String(key, msg))
299         }
300         state.groups = stateGroups // Restore groups passed to ReplaceAttrs.
301         state.appendNonBuiltIns(r)
302         state.buf.WriteByte('\n')
303
304         h.mu.Lock()
305         defer h.mu.Unlock()
306         _, err := h.w.Write(*state.buf)
307         return err
308 }
309
310 func (s *handleState) appendNonBuiltIns(r Record) {
311         // preformatted Attrs
312         if len(s.h.preformattedAttrs) > 0 {
313                 s.buf.WriteString(s.sep)
314                 s.buf.Write(s.h.preformattedAttrs)
315                 s.sep = s.h.attrSep()
316         }
317         // Attrs in Record -- unlike the built-in ones, they are in groups started
318         // from WithGroup.
319         // If the record has no Attrs, don't output any groups.
320         nOpenGroups := s.h.nOpenGroups
321         if r.NumAttrs() > 0 {
322                 s.prefix.WriteString(s.h.groupPrefix)
323                 s.openGroups()
324                 nOpenGroups = len(s.h.groups)
325                 r.Attrs(func(a Attr) bool {
326                         s.appendAttr(a)
327                         return true
328                 })
329         }
330         if s.h.json {
331                 // Close all open groups.
332                 for range s.h.groups[:nOpenGroups] {
333                         s.buf.WriteByte('}')
334                 }
335                 // Close the top-level object.
336                 s.buf.WriteByte('}')
337         }
338 }
339
340 // attrSep returns the separator between attributes.
341 func (h *commonHandler) attrSep() string {
342         if h.json {
343                 return ","
344         }
345         return " "
346 }
347
348 // handleState holds state for a single call to commonHandler.handle.
349 // The initial value of sep determines whether to emit a separator
350 // before the next key, after which it stays true.
351 type handleState struct {
352         h       *commonHandler
353         buf     *buffer.Buffer
354         freeBuf bool           // should buf be freed?
355         sep     string         // separator to write before next key
356         prefix  *buffer.Buffer // for text: key prefix
357         groups  *[]string      // pool-allocated slice of active groups, for ReplaceAttr
358 }
359
360 var groupPool = sync.Pool{New: func() any {
361         s := make([]string, 0, 10)
362         return &s
363 }}
364
365 func (h *commonHandler) newHandleState(buf *buffer.Buffer, freeBuf bool, sep string) handleState {
366         s := handleState{
367                 h:       h,
368                 buf:     buf,
369                 freeBuf: freeBuf,
370                 sep:     sep,
371                 prefix:  buffer.New(),
372         }
373         if h.opts.ReplaceAttr != nil {
374                 s.groups = groupPool.Get().(*[]string)
375                 *s.groups = append(*s.groups, h.groups[:h.nOpenGroups]...)
376         }
377         return s
378 }
379
380 func (s *handleState) free() {
381         if s.freeBuf {
382                 s.buf.Free()
383         }
384         if gs := s.groups; gs != nil {
385                 *gs = (*gs)[:0]
386                 groupPool.Put(gs)
387         }
388         s.prefix.Free()
389 }
390
391 func (s *handleState) openGroups() {
392         for _, n := range s.h.groups[s.h.nOpenGroups:] {
393                 s.openGroup(n)
394         }
395 }
396
397 // Separator for group names and keys.
398 const keyComponentSep = '.'
399
400 // openGroup starts a new group of attributes
401 // with the given name.
402 func (s *handleState) openGroup(name string) {
403         if s.h.json {
404                 s.appendKey(name)
405                 s.buf.WriteByte('{')
406                 s.sep = ""
407         } else {
408                 s.prefix.WriteString(name)
409                 s.prefix.WriteByte(keyComponentSep)
410         }
411         // Collect group names for ReplaceAttr.
412         if s.groups != nil {
413                 *s.groups = append(*s.groups, name)
414         }
415 }
416
417 // closeGroup ends the group with the given name.
418 func (s *handleState) closeGroup(name string) {
419         if s.h.json {
420                 s.buf.WriteByte('}')
421         } else {
422                 (*s.prefix) = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */]
423         }
424         s.sep = s.h.attrSep()
425         if s.groups != nil {
426                 *s.groups = (*s.groups)[:len(*s.groups)-1]
427         }
428 }
429
430 // appendAttr appends the Attr's key and value using app.
431 // It handles replacement and checking for an empty key.
432 // after replacement).
433 func (s *handleState) appendAttr(a Attr) {
434         if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != KindGroup {
435                 var gs []string
436                 if s.groups != nil {
437                         gs = *s.groups
438                 }
439                 // Resolve before calling ReplaceAttr, so the user doesn't have to.
440                 a.Value = a.Value.Resolve()
441                 a = rep(gs, a)
442         }
443         a.Value = a.Value.Resolve()
444         // Elide empty Attrs.
445         if a.isEmpty() {
446                 return
447         }
448         // Special case: Source.
449         if v := a.Value; v.Kind() == KindAny {
450                 if src, ok := v.Any().(*Source); ok {
451                         if s.h.json {
452                                 a.Value = src.group()
453                         } else {
454                                 a.Value = StringValue(fmt.Sprintf("%s:%d", src.File, src.Line))
455                         }
456                 }
457         }
458         if a.Value.Kind() == KindGroup {
459                 attrs := a.Value.Group()
460                 // Output only non-empty groups.
461                 if len(attrs) > 0 {
462                         // Inline a group with an empty key.
463                         if a.Key != "" {
464                                 s.openGroup(a.Key)
465                         }
466                         for _, aa := range attrs {
467                                 s.appendAttr(aa)
468                         }
469                         if a.Key != "" {
470                                 s.closeGroup(a.Key)
471                         }
472                 }
473         } else {
474                 s.appendKey(a.Key)
475                 s.appendValue(a.Value)
476         }
477 }
478
479 func (s *handleState) appendError(err error) {
480         s.appendString(fmt.Sprintf("!ERROR:%v", err))
481 }
482
483 func (s *handleState) appendKey(key string) {
484         s.buf.WriteString(s.sep)
485         if s.prefix != nil && len(*s.prefix) > 0 {
486                 // TODO: optimize by avoiding allocation.
487                 s.appendString(string(*s.prefix) + key)
488         } else {
489                 s.appendString(key)
490         }
491         if s.h.json {
492                 s.buf.WriteByte(':')
493         } else {
494                 s.buf.WriteByte('=')
495         }
496         s.sep = s.h.attrSep()
497 }
498
499 func (s *handleState) appendString(str string) {
500         if s.h.json {
501                 s.buf.WriteByte('"')
502                 *s.buf = appendEscapedJSONString(*s.buf, str)
503                 s.buf.WriteByte('"')
504         } else {
505                 // text
506                 if needsQuoting(str) {
507                         *s.buf = strconv.AppendQuote(*s.buf, str)
508                 } else {
509                         s.buf.WriteString(str)
510                 }
511         }
512 }
513
514 func (s *handleState) appendValue(v Value) {
515         var err error
516         if s.h.json {
517                 err = appendJSONValue(s, v)
518         } else {
519                 err = appendTextValue(s, v)
520         }
521         if err != nil {
522                 s.appendError(err)
523         }
524 }
525
526 func (s *handleState) appendTime(t time.Time) {
527         if s.h.json {
528                 appendJSONTime(s, t)
529         } else {
530                 writeTimeRFC3339Millis(s.buf, t)
531         }
532 }
533
534 // This takes half the time of Time.AppendFormat.
535 func writeTimeRFC3339Millis(buf *buffer.Buffer, t time.Time) {
536         year, month, day := t.Date()
537         buf.WritePosIntWidth(year, 4)
538         buf.WriteByte('-')
539         buf.WritePosIntWidth(int(month), 2)
540         buf.WriteByte('-')
541         buf.WritePosIntWidth(day, 2)
542         buf.WriteByte('T')
543         hour, min, sec := t.Clock()
544         buf.WritePosIntWidth(hour, 2)
545         buf.WriteByte(':')
546         buf.WritePosIntWidth(min, 2)
547         buf.WriteByte(':')
548         buf.WritePosIntWidth(sec, 2)
549         ns := t.Nanosecond()
550         buf.WriteByte('.')
551         buf.WritePosIntWidth(ns/1e6, 3)
552         _, offsetSeconds := t.Zone()
553         if offsetSeconds == 0 {
554                 buf.WriteByte('Z')
555         } else {
556                 offsetMinutes := offsetSeconds / 60
557                 if offsetMinutes < 0 {
558                         buf.WriteByte('-')
559                         offsetMinutes = -offsetMinutes
560                 } else {
561                         buf.WriteByte('+')
562                 }
563                 buf.WritePosIntWidth(offsetMinutes/60, 2)
564                 buf.WriteByte(':')
565                 buf.WritePosIntWidth(offsetMinutes%60, 2)
566         }
567 }