Source file 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 // group 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 } 227