// 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.
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
}
}
-// 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.
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.
// 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.
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,
+ }
+}