golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/slog/value.go (about)

     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  }