]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/record.go
log: add available godoc link
[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 // 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 // attrs 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 }