Source file src/log/slog/record.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  	"runtime"
     9  	"slices"
    10  	"time"
    11  )
    12  
    13  const nAttrsInline = 5
    14  
    15  // A Record holds information about a log event.
    16  // Copies of a Record share state.
    17  // Do not modify a Record after handing out a copy to it.
    18  // Call [NewRecord] to create a new Record.
    19  // Use [Record.Clone] to create a copy with no shared state.
    20  type Record struct {
    21  	// The time at which the output method (Log, Info, etc.) was called.
    22  	Time time.Time
    23  
    24  	// The log message.
    25  	Message string
    26  
    27  	// The level of the event.
    28  	Level Level
    29  
    30  	// The program counter at the time the record was constructed, as determined
    31  	// by runtime.Callers. If zero, no program counter is available.
    32  	//
    33  	// The only valid use for this value is as an argument to
    34  	// [runtime.CallersFrames]. In particular, it must not be passed to
    35  	// [runtime.FuncForPC].
    36  	PC uintptr
    37  
    38  	// Allocation optimization: an inline array sized to hold
    39  	// the majority of log calls (based on examination of open-source
    40  	// code). It holds the start of the list of Attrs.
    41  	front [nAttrsInline]Attr
    42  
    43  	// The number of Attrs in front.
    44  	nFront int
    45  
    46  	// The list of Attrs except for those in front.
    47  	// Invariants:
    48  	//   - len(back) > 0 iff nFront == len(front)
    49  	//   - Unused array elements are zero. Used to detect mistakes.
    50  	back []Attr
    51  }
    52  
    53  // NewRecord creates a [Record] from the given arguments.
    54  // Use [Record.AddAttrs] to add attributes to the Record.
    55  //
    56  // NewRecord is intended for logging APIs that want to support a [Handler] as
    57  // a backend.
    58  func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
    59  	return Record{
    60  		Time:    t,
    61  		Message: msg,
    62  		Level:   level,
    63  		PC:      pc,
    64  	}
    65  }
    66  
    67  // Clone returns a copy of the record with no shared state.
    68  // The original record and the clone can both be modified
    69  // without interfering with each other.
    70  func (r Record) Clone() Record {
    71  	r.back = slices.Clip(r.back) // prevent append from mutating shared array
    72  	return r
    73  }
    74  
    75  // NumAttrs returns the number of attributes in the [Record].
    76  func (r Record) NumAttrs() int {
    77  	return r.nFront + len(r.back)
    78  }
    79  
    80  // Attrs calls f on each Attr in the [Record].
    81  // Iteration stops if f returns false.
    82  func (r Record) Attrs(f func(Attr) bool) {
    83  	for i := 0; i < r.nFront; i++ {
    84  		if !f(r.front[i]) {
    85  			return
    86  		}
    87  	}
    88  	for _, a := range r.back {
    89  		if !f(a) {
    90  			return
    91  		}
    92  	}
    93  }
    94  
    95  // AddAttrs appends the given Attrs to the [Record]'s list of Attrs.
    96  // It omits empty groups.
    97  func (r *Record) AddAttrs(attrs ...Attr) {
    98  	var i int
    99  	for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
   100  		a := attrs[i]
   101  		if a.Value.isEmptyGroup() {
   102  			continue
   103  		}
   104  		r.front[r.nFront] = a
   105  		r.nFront++
   106  	}
   107  	// Check if a copy was modified by slicing past the end
   108  	// and seeing if the Attr there is non-zero.
   109  	if cap(r.back) > len(r.back) {
   110  		end := r.back[:len(r.back)+1][len(r.back)]
   111  		if !end.isEmpty() {
   112  			// Don't panic; copy and muddle through.
   113  			r.back = slices.Clip(r.back)
   114  			r.back = append(r.back, String("!BUG", "AddAttrs unsafely called on copy of Record made without using Record.Clone"))
   115  		}
   116  	}
   117  	ne := countEmptyGroups(attrs[i:])
   118  	r.back = slices.Grow(r.back, len(attrs[i:])-ne)
   119  	for _, a := range attrs[i:] {
   120  		if !a.Value.isEmptyGroup() {
   121  			r.back = append(r.back, a)
   122  		}
   123  	}
   124  }
   125  
   126  // Add converts the args to Attrs as described in [Logger.Log],
   127  // then appends the Attrs to the [Record]'s list of Attrs.
   128  // It omits empty groups.
   129  func (r *Record) Add(args ...any) {
   130  	var a Attr
   131  	for len(args) > 0 {
   132  		a, args = argsToAttr(args)
   133  		if a.Value.isEmptyGroup() {
   134  			continue
   135  		}
   136  		if r.nFront < len(r.front) {
   137  			r.front[r.nFront] = a
   138  			r.nFront++
   139  		} else {
   140  			if r.back == nil {
   141  				r.back = make([]Attr, 0, countAttrs(args)+1)
   142  			}
   143  			r.back = append(r.back, a)
   144  		}
   145  	}
   146  }
   147  
   148  // countAttrs returns the number of Attrs that would be created from args.
   149  func countAttrs(args []any) int {
   150  	n := 0
   151  	for i := 0; i < len(args); i++ {
   152  		n++
   153  		if _, ok := args[i].(string); ok {
   154  			i++
   155  		}
   156  	}
   157  	return n
   158  }
   159  
   160  const badKey = "!BADKEY"
   161  
   162  // argsToAttr turns a prefix of the nonempty args slice into an Attr
   163  // and returns the unconsumed portion of the slice.
   164  // If args[0] is an Attr, it returns it.
   165  // If args[0] is a string, it treats the first two elements as
   166  // a key-value pair.
   167  // Otherwise, it treats args[0] as a value with a missing key.
   168  func argsToAttr(args []any) (Attr, []any) {
   169  	switch x := args[0].(type) {
   170  	case string:
   171  		if len(args) == 1 {
   172  			return String(badKey, x), nil
   173  		}
   174  		return Any(x, args[1]), args[2:]
   175  
   176  	case Attr:
   177  		return x, args[1:]
   178  
   179  	default:
   180  		return Any(badKey, x), args[1:]
   181  	}
   182  }
   183  
   184  // Source describes the location of a line of source code.
   185  type Source struct {
   186  	// Function is the package path-qualified function name containing the
   187  	// source line. If non-empty, this string uniquely identifies a single
   188  	// function in the program. This may be the empty string if not known.
   189  	Function string `json:"function"`
   190  	// File and Line are the file name and line number (1-based) of the source
   191  	// line. These may be the empty string and zero, respectively, if not known.
   192  	File string `json:"file"`
   193  	Line int    `json:"line"`
   194  }
   195  
   196  // group returns the non-zero fields of s as a slice of attrs.
   197  // It is similar to a LogValue method, but we don't want Source
   198  // to implement LogValuer because it would be resolved before
   199  // the ReplaceAttr function was called.
   200  func (s *Source) group() Value {
   201  	var as []Attr
   202  	if s.Function != "" {
   203  		as = append(as, String("function", s.Function))
   204  	}
   205  	if s.File != "" {
   206  		as = append(as, String("file", s.File))
   207  	}
   208  	if s.Line != 0 {
   209  		as = append(as, Int("line", s.Line))
   210  	}
   211  	return GroupValue(as...)
   212  }
   213  
   214  // source returns a Source for the log event.
   215  // If the Record was created without the necessary information,
   216  // or if the location is unavailable, it returns a non-nil *Source
   217  // with zero fields.
   218  func (r Record) source() *Source {
   219  	fs := runtime.CallersFrames([]uintptr{r.PC})
   220  	f, _ := fs.Next()
   221  	return &Source{
   222  		Function: f.Function,
   223  		File:     f.File,
   224  		Line:     f.Line,
   225  	}
   226  }
   227  

View as plain text