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