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 }