]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/record.go
log/slog: function argument to Record.Attrs returns bool
[gostls13.git] / 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 // Use [Record.Clone] to create a copy with no shared state.
19 type Record struct {
20         // The time at which the output method (Log, Info, etc.) was called.
21         Time time.Time
22
23         // The log message.
24         Message string
25
26         // The level of the event.
27         Level Level
28
29         // The program counter at the time the record was constructed, as determined
30         // by runtime.Callers. If zero, no program counter is available.
31         //
32         // The only valid use for this value is as an argument to
33         // [runtime.CallersFrames]. In particular, it must not be passed to
34         // [runtime.FuncForPC].
35         PC uintptr
36
37         // Allocation optimization: an inline array sized to hold
38         // the majority of log calls (based on examination of open-source
39         // code). It holds the start of the list of Attrs.
40         front [nAttrsInline]Attr
41
42         // The number of Attrs in front.
43         nFront int
44
45         // The list of Attrs except for those in front.
46         // Invariants:
47         //   - len(back) > 0 iff nFront == len(front)
48         //   - Unused array elements are zero. Used to detect mistakes.
49         back []Attr
50 }
51
52 // NewRecord creates a Record from the given arguments.
53 // Use [Record.AddAttrs] to add attributes to the Record.
54 //
55 // NewRecord is intended for logging APIs that want to support a [Handler] as
56 // a backend.
57 func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
58         return Record{
59                 Time:    t,
60                 Message: msg,
61                 Level:   level,
62                 PC:      pc,
63         }
64 }
65
66 // frame returns the runtime.Frame of the log event.
67 // If the Record was created without the necessary information,
68 // or if the location is unavailable, it returns a zero Frame.
69 func (r Record) frame() runtime.Frame {
70         fs := runtime.CallersFrames([]uintptr{r.PC})
71         f, _ := fs.Next()
72         return f
73 }
74
75 // Clone returns a copy of the record with no shared state.
76 // The original record and the clone can both be modified
77 // without interfering with each other.
78 func (r Record) Clone() Record {
79         r.back = slices.Clip(r.back) // prevent append from mutating shared array
80         return r
81 }
82
83 // NumAttrs returns the number of attributes in the Record.
84 func (r Record) NumAttrs() int {
85         return r.nFront + len(r.back)
86 }
87
88 // Attrs calls f on each Attr in the Record.
89 // Iteration stops if f returns false.
90 // The Attrs are already resolved.
91 func (r Record) Attrs(f func(Attr) bool) {
92         for i := 0; i < r.nFront; i++ {
93                 if !f(r.front[i]) {
94                         return
95                 }
96         }
97         for _, a := range r.back {
98                 if !f(a) {
99                         return
100                 }
101         }
102 }
103
104 // AddAttrs appends the given Attrs to the Record's list of Attrs.
105 // It resolves the Attrs before doing so.
106 func (r *Record) AddAttrs(attrs ...Attr) {
107         resolveAttrs(attrs)
108         n := copy(r.front[r.nFront:], attrs)
109         r.nFront += n
110         // Check if a copy was modified by slicing past the end
111         // and seeing if the Attr there is non-zero.
112         if cap(r.back) > len(r.back) {
113                 end := r.back[:len(r.back)+1][len(r.back)]
114                 if !end.isEmpty() {
115                         panic("copies of a slog.Record were both modified")
116                 }
117         }
118         r.back = append(r.back, attrs[n:]...)
119 }
120
121 // Add converts the args to Attrs as described in [Logger.Log],
122 // then appends the Attrs to the Record's list of Attrs.
123 // It resolves the Attrs before doing so.
124 func (r *Record) Add(args ...any) {
125         var a Attr
126         for len(args) > 0 {
127                 a, args = argsToAttr(args)
128                 if r.nFront < len(r.front) {
129                         r.front[r.nFront] = a
130                         r.nFront++
131                 } else {
132                         if r.back == nil {
133                                 r.back = make([]Attr, 0, countAttrs(args))
134                         }
135                         r.back = append(r.back, a)
136                 }
137         }
138
139 }
140
141 // countAttrs returns the number of Attrs that would be created from args.
142 func countAttrs(args []any) int {
143         n := 0
144         for i := 0; i < len(args); i++ {
145                 n++
146                 if _, ok := args[i].(string); ok {
147                         i++
148                 }
149         }
150         return n
151 }
152
153 const badKey = "!BADKEY"
154
155 // argsToAttr turns a prefix of the nonempty args slice into an Attr
156 // and returns the unconsumed portion of the slice.
157 // If args[0] is an Attr, it returns it, resolved.
158 // If args[0] is a string, it treats the first two elements as
159 // a key-value pair.
160 // Otherwise, it treats args[0] as a value with a missing key.
161 func argsToAttr(args []any) (Attr, []any) {
162         switch x := args[0].(type) {
163         case string:
164                 if len(args) == 1 {
165                         return String(badKey, x), nil
166                 }
167                 a := Any(x, args[1])
168                 a.Value = a.Value.Resolve()
169                 return a, args[2:]
170
171         case Attr:
172                 x.Value = x.Value.Resolve()
173                 return x, args[1:]
174
175         default:
176                 return Any(badKey, x), args[1:]
177         }
178 }