]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/log/slog/record.go
log/slog: fix method name in docs
[gostls13.git] / src / log / slog / record.go
index 0ee2a27f0ea6cae59ba9a0c3c6b455cfe31fc81d..97c87019a6aff5bbee9a96c4084ab815f0737f66 100644 (file)
@@ -15,6 +15,7 @@ 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.
+// Call [NewRecord] to create a new Record.
 // 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.
@@ -49,7 +50,7 @@ type Record struct {
        back []Attr
 }
 
-// NewRecord creates a Record from the given arguments.
+// 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
@@ -63,15 +64,6 @@ func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
        }
 }
 
-// frame returns the runtime.Frame of the log event.
-// If the Record was created without the necessary information,
-// or if the location is unavailable, it returns a zero Frame.
-func (r Record) frame() runtime.Frame {
-       fs := runtime.CallersFrames([]uintptr{r.PC})
-       f, _ := fs.Next()
-       return f
-}
-
 // 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.
@@ -80,57 +72,77 @@ func (r Record) Clone() Record {
        return r
 }
 
-// NumAttrs returns the number of attributes in the Record.
+// 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.
-// The Attrs are already resolved.
-func (r Record) Attrs(f func(Attr)) {
+// 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++ {
-               f(r.front[i])
+               if !f(r.front[i]) {
+                       return
+               }
        }
        for _, a := range r.back {
-               f(a)
+               if !f(a) {
+                       return
+               }
        }
 }
 
-// AddAttrs appends the given Attrs to the Record's list of Attrs.
-// It resolves the Attrs before doing so.
+// AddAttrs appends the given Attrs to the [Record]'s list of Attrs.
+// It omits empty groups.
 func (r *Record) AddAttrs(attrs ...Attr) {
-       resolveAttrs(attrs)
-       n := copy(r.front[r.nFront:], attrs)
-       r.nFront += n
+       var i int
+       for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
+               a := attrs[i]
+               if a.Value.isEmptyGroup() {
+                       continue
+               }
+               r.front[r.nFront] = a
+               r.nFront++
+       }
        // 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")
+                       // Don't panic; copy and muddle through.
+                       r.back = slices.Clip(r.back)
+                       r.back = append(r.back, String("!BUG", "AddAttrs unsafely called on copy of Record made without using Record.Clone"))
+               }
+       }
+       ne := countEmptyGroups(attrs[i:])
+       r.back = slices.Grow(r.back, len(attrs[i:])-ne)
+       for _, a := range attrs[i:] {
+               if !a.Value.isEmptyGroup() {
+                       r.back = append(r.back, a)
                }
        }
-       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.
-// It resolves the Attrs before doing so.
+// then appends the Attrs to the [Record]'s list of Attrs.
+// It omits empty groups.
 func (r *Record) Add(args ...any) {
        var a Attr
        for len(args) > 0 {
                a, args = argsToAttr(args)
+               if a.Value.isEmptyGroup() {
+                       continue
+               }
                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 = make([]Attr, 0, countAttrs(args)+1)
                        }
                        r.back = append(r.back, a)
                }
        }
-
 }
 
 // countAttrs returns the number of Attrs that would be created from args.
@@ -149,7 +161,7 @@ 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, resolved.
+// 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.
@@ -159,15 +171,56 @@ func argsToAttr(args []any) (Attr, []any) {
                if len(args) == 1 {
                        return String(badKey, x), nil
                }
-               a := Any(x, args[1])
-               a.Value = a.Value.Resolve()
-               return a, args[2:]
+               return Any(x, args[1]), args[2:]
 
        case Attr:
-               x.Value = x.Value.Resolve()
                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"`
+}
+
+// group 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,
+       }
+}