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 "fmt"
9 "math"
10 "runtime"
11 "strconv"
12 "strings"
13 "time"
14 "unsafe"
15 16 "golang.org/x/exp/slices"
17 )
18 19 // A Value can represent any Go value, but unlike type any,
20 // it can represent most small values without an allocation.
21 // The zero Value corresponds to nil.
22 type Value struct {
23 _ [0]func() // disallow ==
24 // num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration,
25 // the string length for KindString, and nanoseconds since the epoch for KindTime.
26 num uint64
27 // If any is of type Kind, then the value is in num as described above.
28 // If any is of type *time.Location, then the Kind is Time and time.Time value
29 // can be constructed from the Unix nanos in num and the location (monotonic time
30 // is not preserved).
31 // If any is of type stringptr, then the Kind is String and the string value
32 // consists of the length in num and the pointer in any.
33 // Otherwise, the Kind is Any and any is the value.
34 // (This implies that Attrs cannot store values of type Kind, *time.Location
35 // or stringptr.)
36 any any
37 }
38 39 // Kind is the kind of a Value.
40 type Kind int
41 42 // The following list is sorted alphabetically, but it's also important that
43 // KindAny is 0 so that a zero Value represents nil.
44 45 const (
46 KindAny Kind = iota
47 KindBool
48 KindDuration
49 KindFloat64
50 KindInt64
51 KindString
52 KindTime
53 KindUint64
54 KindGroup
55 KindLogValuer
56 )
57 58 var kindStrings = []string{
59 "Any",
60 "Bool",
61 "Duration",
62 "Float64",
63 "Int64",
64 "String",
65 "Time",
66 "Uint64",
67 "Group",
68 "LogValuer",
69 }
70 71 func (k Kind) String() string {
72 if k >= 0 && int(k) < len(kindStrings) {
73 return kindStrings[k]
74 }
75 return "<unknown slog.Kind>"
76 }
77 78 // Unexported version of Kind, just so we can store Kinds in Values.
79 // (No user-provided value has this type.)
80 type kind Kind
81 82 // Kind returns v's Kind.
83 func (v Value) Kind() Kind {
84 switch x := v.any.(type) {
85 case Kind:
86 return x
87 case stringptr:
88 return KindString
89 case timeLocation:
90 return KindTime
91 case groupptr:
92 return KindGroup
93 case LogValuer:
94 return KindLogValuer
95 case kind: // a kind is just a wrapper for a Kind
96 return KindAny
97 default:
98 return KindAny
99 }
100 }
101 102 //////////////// Constructors
103 104 // IntValue returns a Value for an int.
105 func IntValue(v int) Value {
106 return Int64Value(int64(v))
107 }
108 109 // Int64Value returns a Value for an int64.
110 func Int64Value(v int64) Value {
111 return Value{num: uint64(v), any: KindInt64}
112 }
113 114 // Uint64Value returns a Value for a uint64.
115 func Uint64Value(v uint64) Value {
116 return Value{num: v, any: KindUint64}
117 }
118 119 // Float64Value returns a Value for a floating-point number.
120 func Float64Value(v float64) Value {
121 return Value{num: math.Float64bits(v), any: KindFloat64}
122 }
123 124 // BoolValue returns a Value for a bool.
125 func BoolValue(v bool) Value {
126 u := uint64(0)
127 if v {
128 u = 1
129 }
130 return Value{num: u, any: KindBool}
131 }
132 133 // Unexported version of *time.Location, just so we can store *time.Locations in
134 // Values. (No user-provided value has this type.)
135 type timeLocation *time.Location
136 137 // TimeValue returns a Value for a time.Time.
138 // It discards the monotonic portion.
139 func TimeValue(v time.Time) Value {
140 if v.IsZero() {
141 // UnixNano on the zero time is undefined, so represent the zero time
142 // with a nil *time.Location instead. time.Time.Location method never
143 // returns nil, so a Value with any == timeLocation(nil) cannot be
144 // mistaken for any other Value, time.Time or otherwise.
145 return Value{any: timeLocation(nil)}
146 }
147 return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
148 }
149 150 // DurationValue returns a Value for a time.Duration.
151 func DurationValue(v time.Duration) Value {
152 return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
153 }
154 155 // AnyValue returns a Value for the supplied value.
156 //
157 // If the supplied value is of type Value, it is returned
158 // unmodified.
159 //
160 // Given a value of one of Go's predeclared string, bool, or
161 // (non-complex) numeric types, AnyValue returns a Value of kind
162 // String, Bool, Uint64, Int64, or Float64. The width of the
163 // original numeric type is not preserved.
164 //
165 // Given a time.Time or time.Duration value, AnyValue returns a Value of kind
166 // KindTime or KindDuration. The monotonic time is not preserved.
167 //
168 // For nil, or values of all other types, including named types whose
169 // underlying type is numeric, AnyValue returns a value of kind KindAny.
170 func AnyValue(v any) Value {
171 switch v := v.(type) {
172 case string:
173 return StringValue(v)
174 case int:
175 return Int64Value(int64(v))
176 case uint:
177 return Uint64Value(uint64(v))
178 case int64:
179 return Int64Value(v)
180 case uint64:
181 return Uint64Value(v)
182 case bool:
183 return BoolValue(v)
184 case time.Duration:
185 return DurationValue(v)
186 case time.Time:
187 return TimeValue(v)
188 case uint8:
189 return Uint64Value(uint64(v))
190 case uint16:
191 return Uint64Value(uint64(v))
192 case uint32:
193 return Uint64Value(uint64(v))
194 case uintptr:
195 return Uint64Value(uint64(v))
196 case int8:
197 return Int64Value(int64(v))
198 case int16:
199 return Int64Value(int64(v))
200 case int32:
201 return Int64Value(int64(v))
202 case float64:
203 return Float64Value(v)
204 case float32:
205 return Float64Value(float64(v))
206 case []Attr:
207 return GroupValue(v...)
208 case Kind:
209 return Value{any: kind(v)}
210 case Value:
211 return v
212 default:
213 return Value{any: v}
214 }
215 }
216 217 //////////////// Accessors
218 219 // Any returns v's value as an any.
220 func (v Value) Any() any {
221 switch v.Kind() {
222 case KindAny:
223 if k, ok := v.any.(kind); ok {
224 return Kind(k)
225 }
226 return v.any
227 case KindLogValuer:
228 return v.any
229 case KindGroup:
230 return v.group()
231 case KindInt64:
232 return int64(v.num)
233 case KindUint64:
234 return v.num
235 case KindFloat64:
236 return v.float()
237 case KindString:
238 return v.str()
239 case KindBool:
240 return v.bool()
241 case KindDuration:
242 return v.duration()
243 case KindTime:
244 return v.time()
245 default:
246 panic(fmt.Sprintf("bad kind: %s", v.Kind()))
247 }
248 }
249 250 // Int64 returns v's value as an int64. It panics
251 // if v is not a signed integer.
252 func (v Value) Int64() int64 {
253 if g, w := v.Kind(), KindInt64; g != w {
254 panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
255 }
256 return int64(v.num)
257 }
258 259 // Uint64 returns v's value as a uint64. It panics
260 // if v is not an unsigned integer.
261 func (v Value) Uint64() uint64 {
262 if g, w := v.Kind(), KindUint64; g != w {
263 panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
264 }
265 return v.num
266 }
267 268 // Bool returns v's value as a bool. It panics
269 // if v is not a bool.
270 func (v Value) Bool() bool {
271 if g, w := v.Kind(), KindBool; g != w {
272 panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
273 }
274 return v.bool()
275 }
276 277 func (v Value) bool() bool {
278 return v.num == 1
279 }
280 281 // Duration returns v's value as a time.Duration. It panics
282 // if v is not a time.Duration.
283 func (v Value) Duration() time.Duration {
284 if g, w := v.Kind(), KindDuration; g != w {
285 panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
286 }
287 288 return v.duration()
289 }
290 291 func (v Value) duration() time.Duration {
292 return time.Duration(int64(v.num))
293 }
294 295 // Float64 returns v's value as a float64. It panics
296 // if v is not a float64.
297 func (v Value) Float64() float64 {
298 if g, w := v.Kind(), KindFloat64; g != w {
299 panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
300 }
301 302 return v.float()
303 }
304 305 func (v Value) float() float64 {
306 return math.Float64frombits(v.num)
307 }
308 309 // Time returns v's value as a time.Time. It panics
310 // if v is not a time.Time.
311 func (v Value) Time() time.Time {
312 if g, w := v.Kind(), KindTime; g != w {
313 panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
314 }
315 return v.time()
316 }
317 318 func (v Value) time() time.Time {
319 loc := v.any.(timeLocation)
320 if loc == nil {
321 return time.Time{}
322 }
323 return time.Unix(0, int64(v.num)).In(loc)
324 }
325 326 // LogValuer returns v's value as a LogValuer. It panics
327 // if v is not a LogValuer.
328 func (v Value) LogValuer() LogValuer {
329 return v.any.(LogValuer)
330 }
331 332 // Group returns v's value as a []Attr.
333 // It panics if v's Kind is not KindGroup.
334 func (v Value) Group() []Attr {
335 if sp, ok := v.any.(groupptr); ok {
336 return unsafe.Slice((*Attr)(sp), v.num)
337 }
338 panic("Group: bad kind")
339 }
340 341 func (v Value) group() []Attr {
342 return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
343 }
344 345 //////////////// Other
346 347 // Equal reports whether v and w represent the same Go value.
348 func (v Value) Equal(w Value) bool {
349 k1 := v.Kind()
350 k2 := w.Kind()
351 if k1 != k2 {
352 return false
353 }
354 switch k1 {
355 case KindInt64, KindUint64, KindBool, KindDuration:
356 return v.num == w.num
357 case KindString:
358 return v.str() == w.str()
359 case KindFloat64:
360 return v.float() == w.float()
361 case KindTime:
362 return v.time().Equal(w.time())
363 case KindAny, KindLogValuer:
364 return v.any == w.any // may panic if non-comparable
365 case KindGroup:
366 return slices.EqualFunc(v.group(), w.group(), Attr.Equal)
367 default:
368 panic(fmt.Sprintf("bad kind: %s", k1))
369 }
370 }
371 372 // append appends a text representation of v to dst.
373 // v is formatted as with fmt.Sprint.
374 func (v Value) append(dst []byte) []byte {
375 switch v.Kind() {
376 case KindString:
377 return append(dst, v.str()...)
378 case KindInt64:
379 return strconv.AppendInt(dst, int64(v.num), 10)
380 case KindUint64:
381 return strconv.AppendUint(dst, v.num, 10)
382 case KindFloat64:
383 return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
384 case KindBool:
385 return strconv.AppendBool(dst, v.bool())
386 case KindDuration:
387 return append(dst, v.duration().String()...)
388 case KindTime:
389 return append(dst, v.time().String()...)
390 case KindGroup:
391 return fmt.Append(dst, v.group())
392 case KindAny, KindLogValuer:
393 return fmt.Append(dst, v.any)
394 default:
395 panic(fmt.Sprintf("bad kind: %s", v.Kind()))
396 }
397 }
398 399 // A LogValuer is any Go value that can convert itself into a Value for logging.
400 //
401 // This mechanism may be used to defer expensive operations until they are
402 // needed, or to expand a single value into a sequence of components.
403 type LogValuer interface {
404 LogValue() Value
405 }
406 407 const maxLogValues = 100
408 409 // Resolve repeatedly calls LogValue on v while it implements LogValuer,
410 // and returns the result.
411 // If v resolves to a group, the group's attributes' values are not recursively
412 // resolved.
413 // If the number of LogValue calls exceeds a threshold, a Value containing an
414 // error is returned.
415 // Resolve's return value is guaranteed not to be of Kind KindLogValuer.
416 func (v Value) Resolve() (rv Value) {
417 orig := v
418 defer func() {
419 if r := recover(); r != nil {
420 rv = AnyValue(fmt.Errorf("LogValue panicked\n%s", stack(3, 5)))
421 }
422 }()
423 424 for i := 0; i < maxLogValues; i++ {
425 if v.Kind() != KindLogValuer {
426 return v
427 }
428 v = v.LogValuer().LogValue()
429 }
430 err := fmt.Errorf("LogValue called too many times on Value of type %T", orig.Any())
431 return AnyValue(err)
432 }
433 434 func stack(skip, nFrames int) string {
435 pcs := make([]uintptr, nFrames+1)
436 n := runtime.Callers(skip+1, pcs)
437 if n == 0 {
438 return "(no stack)"
439 }
440 frames := runtime.CallersFrames(pcs[:n])
441 var b strings.Builder
442 i := 0
443 for {
444 frame, more := frames.Next()
445 fmt.Fprintf(&b, "called from %s (%s:%d)\n", frame.Function, frame.File, frame.Line)
446 if !more {
447 break
448 }
449 i++
450 if i >= nFrames {
451 fmt.Fprintf(&b, "(rest of stack elided)\n")
452 break
453 }
454 }
455 return b.String()
456 }
457