golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/slog/handler.go (about)

     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  	"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.
    32  type 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  
    90  type defaultHandler struct {
    91  	ch *commonHandler
    92  	// log.Output, except for testing
    93  	output func(calldepth int, message string) error
    94  }
    95  
    96  func newDefaultHandler(output func(int, string) error) *defaultHandler {
    97  	return &defaultHandler{
    98  		ch:     &commonHandler{json: false},
    99  		output: output,
   100  	}
   101  }
   102  
   103  func (*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.
   110  func (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  
   123  func (h *defaultHandler) WithAttrs(as []Attr) Handler {
   124  	return &defaultHandler{h.ch.withAttrs(as), h.output}
   125  }
   126  
   127  func (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.
   133  type 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.
   174  const (
   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  
   189  type 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  
   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  	}
   211  }
   212  
   213  // enabled reports whether l is greater than or equal to the
   214  // minimum level.
   215  func (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  
   223  func (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  
   246  func (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  
   255  func (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  
   307  func (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.
   335  func (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.
   345  type 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  
   354  var groupPool = sync.Pool{New: func() any {
   355  	s := make([]string, 0, 10)
   356  	return &s
   357  }}
   358  
   359  func (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  
   374  func (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  
   384  func (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.
   391  const keyComponentSep = '.'
   392  
   393  // openGroup starts a new group of attributes
   394  // with the given name.
   395  func (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.
   411  func (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).
   426  func (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  
   472  func (s *handleState) appendError(err error) {
   473  	s.appendString(fmt.Sprintf("!ERROR:%v", err))
   474  }
   475  
   476  func (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  
   492  func (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  
   507  func (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  
   536  func (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.
   545  func 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  }