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 "time"
10 11 "golang.org/x/exp/slices"
12 )
13 14 const nAttrsInline = 5
15 16 // A Record holds information about a log event.
17 // Copies of a Record share state.
18 // Do not modify a Record after handing out a copy to it.
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 func (r *Record) AddAttrs(attrs ...Attr) {
97 n := copy(r.front[r.nFront:], attrs)
98 r.nFront += n
99 // Check if a copy was modified by slicing past the end
100 // and seeing if the Attr there is non-zero.
101 if cap(r.back) > len(r.back) {
102 end := r.back[:len(r.back)+1][len(r.back)]
103 if !end.isEmpty() {
104 panic("copies of a slog.Record were both modified")
105 }
106 }
107 r.back = append(r.back, attrs[n:]...)
108 }
109 110 // Add converts the args to Attrs as described in [Logger.Log],
111 // then appends the Attrs to the Record's list of Attrs.
112 func (r *Record) Add(args ...any) {
113 var a Attr
114 for len(args) > 0 {
115 a, args = argsToAttr(args)
116 if r.nFront < len(r.front) {
117 r.front[r.nFront] = a
118 r.nFront++
119 } else {
120 if r.back == nil {
121 r.back = make([]Attr, 0, countAttrs(args))
122 }
123 r.back = append(r.back, a)
124 }
125 }
126 127 }
128 129 // countAttrs returns the number of Attrs that would be created from args.
130 func countAttrs(args []any) int {
131 n := 0
132 for i := 0; i < len(args); i++ {
133 n++
134 if _, ok := args[i].(string); ok {
135 i++
136 }
137 }
138 return n
139 }
140 141 const badKey = "!BADKEY"
142 143 // argsToAttr turns a prefix of the nonempty args slice into an Attr
144 // and returns the unconsumed portion of the slice.
145 // If args[0] is an Attr, it returns it.
146 // If args[0] is a string, it treats the first two elements as
147 // a key-value pair.
148 // Otherwise, it treats args[0] as a value with a missing key.
149 func argsToAttr(args []any) (Attr, []any) {
150 switch x := args[0].(type) {
151 case string:
152 if len(args) == 1 {
153 return String(badKey, x), nil
154 }
155 return Any(x, args[1]), args[2:]
156 157 case Attr:
158 return x, args[1:]
159 160 default:
161 return Any(badKey, x), args[1:]
162 }
163 }
164 165 // Source describes the location of a line of source code.
166 type Source struct {
167 // Function is the package path-qualified function name containing the
168 // source line. If non-empty, this string uniquely identifies a single
169 // function in the program. This may be the empty string if not known.
170 Function string `json:"function"`
171 // File and Line are the file name and line number (1-based) of the source
172 // line. These may be the empty string and zero, respectively, if not known.
173 File string `json:"file"`
174 Line int `json:"line"`
175 }
176 177 // attrs returns the non-zero fields of s as a slice of attrs.
178 // It is similar to a LogValue method, but we don't want Source
179 // to implement LogValuer because it would be resolved before
180 // the ReplaceAttr function was called.
181 func (s *Source) group() Value {
182 var as []Attr
183 if s.Function != "" {
184 as = append(as, String("function", s.Function))
185 }
186 if s.File != "" {
187 as = append(as, String("file", s.File))
188 }
189 if s.Line != 0 {
190 as = append(as, Int("line", s.Line))
191 }
192 return GroupValue(as...)
193 }
194 195 // source returns a Source for the log event.
196 // If the Record was created without the necessary information,
197 // or if the location is unavailable, it returns a non-nil *Source
198 // with zero fields.
199 func (r Record) source() *Source {
200 fs := runtime.CallersFrames([]uintptr{r.PC})
201 f, _ := fs.Next()
202 return &Source{
203 Function: f.Function,
204 File: f.File,
205 Line: f.Line,
206 }
207 }
208