| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package slog |
| |
| import ( |
| "runtime" |
| "time" |
| |
| "golang.org/x/exp/slices" |
| ) |
| |
| const nAttrsInline = 5 |
| |
| // A Record holds information about a log event. |
| // Copies of a Record share state. |
| // Do not modify a Record after handing out a copy to it. |
| // Use [Record.Clone] to create a copy with no shared state. |
| type Record struct { |
| // The time at which the output method (Log, Info, etc.) was called. |
| Time time.Time |
| |
| // The log message. |
| Message string |
| |
| // The level of the event. |
| Level Level |
| |
| // The program counter at the time the record was constructed, as determined |
| // by runtime.Callers. If zero, no program counter is available. |
| // |
| // The only valid use for this value is as an argument to |
| // [runtime.CallersFrames]. In particular, it must not be passed to |
| // [runtime.FuncForPC]. |
| PC uintptr |
| |
| // Allocation optimization: an inline array sized to hold |
| // the majority of log calls (based on examination of open-source |
| // code). It holds the start of the list of Attrs. |
| front [nAttrsInline]Attr |
| |
| // The number of Attrs in front. |
| nFront int |
| |
| // The list of Attrs except for those in front. |
| // Invariants: |
| // - len(back) > 0 iff nFront == len(front) |
| // - Unused array elements are zero. Used to detect mistakes. |
| back []Attr |
| } |
| |
| // NewRecord creates a Record from the given arguments. |
| // Use [Record.AddAttrs] to add attributes to the Record. |
| // |
| // NewRecord is intended for logging APIs that want to support a [Handler] as |
| // a backend. |
| func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record { |
| return Record{ |
| Time: t, |
| Message: msg, |
| Level: level, |
| PC: pc, |
| } |
| } |
| |
| // Clone returns a copy of the record with no shared state. |
| // The original record and the clone can both be modified |
| // without interfering with each other. |
| func (r Record) Clone() Record { |
| r.back = slices.Clip(r.back) // prevent append from mutating shared array |
| return r |
| } |
| |
| // NumAttrs returns the number of attributes in the Record. |
| func (r Record) NumAttrs() int { |
| return r.nFront + len(r.back) |
| } |
| |
| // Attrs calls f on each Attr in the Record. |
| // Iteration stops if f returns false. |
| func (r Record) Attrs(f func(Attr) bool) { |
| for i := 0; i < r.nFront; i++ { |
| if !f(r.front[i]) { |
| return |
| } |
| } |
| for _, a := range r.back { |
| if !f(a) { |
| return |
| } |
| } |
| } |
| |
| // AddAttrs appends the given Attrs to the Record's list of Attrs. |
| func (r *Record) AddAttrs(attrs ...Attr) { |
| n := copy(r.front[r.nFront:], attrs) |
| r.nFront += n |
| // Check if a copy was modified by slicing past the end |
| // and seeing if the Attr there is non-zero. |
| if cap(r.back) > len(r.back) { |
| end := r.back[:len(r.back)+1][len(r.back)] |
| if !end.isEmpty() { |
| panic("copies of a slog.Record were both modified") |
| } |
| } |
| r.back = append(r.back, attrs[n:]...) |
| } |
| |
| // Add converts the args to Attrs as described in [Logger.Log], |
| // then appends the Attrs to the Record's list of Attrs. |
| func (r *Record) Add(args ...any) { |
| var a Attr |
| for len(args) > 0 { |
| a, args = argsToAttr(args) |
| if r.nFront < len(r.front) { |
| r.front[r.nFront] = a |
| r.nFront++ |
| } else { |
| if r.back == nil { |
| r.back = make([]Attr, 0, countAttrs(args)) |
| } |
| r.back = append(r.back, a) |
| } |
| } |
| |
| } |
| |
| // countAttrs returns the number of Attrs that would be created from args. |
| func countAttrs(args []any) int { |
| n := 0 |
| for i := 0; i < len(args); i++ { |
| n++ |
| if _, ok := args[i].(string); ok { |
| i++ |
| } |
| } |
| return n |
| } |
| |
| const badKey = "!BADKEY" |
| |
| // argsToAttr turns a prefix of the nonempty args slice into an Attr |
| // and returns the unconsumed portion of the slice. |
| // If args[0] is an Attr, it returns it. |
| // If args[0] is a string, it treats the first two elements as |
| // a key-value pair. |
| // Otherwise, it treats args[0] as a value with a missing key. |
| func argsToAttr(args []any) (Attr, []any) { |
| switch x := args[0].(type) { |
| case string: |
| if len(args) == 1 { |
| return String(badKey, x), nil |
| } |
| return Any(x, args[1]), args[2:] |
| |
| case Attr: |
| return x, args[1:] |
| |
| default: |
| return Any(badKey, x), args[1:] |
| } |
| } |
| |
| // Source describes the location of a line of source code. |
| type Source struct { |
| // Function is the package path-qualified function name containing the |
| // source line. If non-empty, this string uniquely identifies a single |
| // function in the program. This may be the empty string if not known. |
| Function string `json:"function"` |
| // File and Line are the file name and line number (1-based) of the source |
| // line. These may be the empty string and zero, respectively, if not known. |
| File string `json:"file"` |
| Line int `json:"line"` |
| } |
| |
| // attrs returns the non-zero fields of s as a slice of attrs. |
| // It is similar to a LogValue method, but we don't want Source |
| // to implement LogValuer because it would be resolved before |
| // the ReplaceAttr function was called. |
| func (s *Source) group() Value { |
| var as []Attr |
| if s.Function != "" { |
| as = append(as, String("function", s.Function)) |
| } |
| if s.File != "" { |
| as = append(as, String("file", s.File)) |
| } |
| if s.Line != 0 { |
| as = append(as, Int("line", s.Line)) |
| } |
| return GroupValue(as...) |
| } |
| |
| // source returns a Source for the log event. |
| // If the Record was created without the necessary information, |
| // or if the location is unavailable, it returns a non-nil *Source |
| // with zero fields. |
| func (r Record) source() *Source { |
| fs := runtime.CallersFrames([]uintptr{r.PC}) |
| f, _ := fs.Next() |
| return &Source{ |
| Function: f.Function, |
| File: f.File, |
| Line: f.Line, |
| } |
| } |