value.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
  5package slog
  6
  7import (
  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.
 22type 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.
 40type 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
 45const (
 46	KindAny Kind = iota
 47	KindBool
 48	KindDuration
 49	KindFloat64
 50	KindInt64
 51	KindString
 52	KindTime
 53	KindUint64
 54	KindGroup
 55	KindLogValuer
 56)
 57
 58var kindStrings = []string{
 59	"Any",
 60	"Bool",
 61	"Duration",
 62	"Float64",
 63	"Int64",
 64	"String",
 65	"Time",
 66	"Uint64",
 67	"Group",
 68	"LogValuer",
 69}
 70
 71func (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.)
 80type kind Kind
 81
 82// Kind returns v's Kind.
 83func (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.
105func IntValue(v int) Value {
106	return Int64Value(int64(v))
107}
108
109// Int64Value returns a Value for an int64.
110func Int64Value(v int64) Value {
111	return Value{num: uint64(v), any: KindInt64}
112}
113
114// Uint64Value returns a Value for a uint64.
115func Uint64Value(v uint64) Value {
116	return Value{num: v, any: KindUint64}
117}
118
119// Float64Value returns a Value for a floating-point number.
120func Float64Value(v float64) Value {
121	return Value{num: math.Float64bits(v), any: KindFloat64}
122}
123
124// BoolValue returns a Value for a bool.
125func 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.)
135type timeLocation *time.Location
136
137// TimeValue returns a Value for a time.Time.
138// It discards the monotonic portion.
139func 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.
151func 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.
170func 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.
220func (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.
252func (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.
261func (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.
270func (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
277func (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.
283func (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
291func (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.
297func (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
305func (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.
311func (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
318func (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.
328func (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.
334func (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
341func (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.
348func (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.
374func (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.
403type LogValuer interface {
404	LogValue() Value
405}
406
407const 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.
416func (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
434func 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}