mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-24 06:01:25 +00:00
457 lines
11 KiB
Go
457 lines
11 KiB
Go
// Copyright 2022 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package slog
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
// A Value can represent any Go value, but unlike type any,
|
|
// it can represent most small values without an allocation.
|
|
// The zero Value corresponds to nil.
|
|
type Value struct {
|
|
_ [0]func() // disallow ==
|
|
// num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration,
|
|
// the string length for KindString, and nanoseconds since the epoch for KindTime.
|
|
num uint64
|
|
// If any is of type Kind, then the value is in num as described above.
|
|
// If any is of type *time.Location, then the Kind is Time and time.Time value
|
|
// can be constructed from the Unix nanos in num and the location (monotonic time
|
|
// is not preserved).
|
|
// If any is of type stringptr, then the Kind is String and the string value
|
|
// consists of the length in num and the pointer in any.
|
|
// Otherwise, the Kind is Any and any is the value.
|
|
// (This implies that Attrs cannot store values of type Kind, *time.Location
|
|
// or stringptr.)
|
|
any any
|
|
}
|
|
|
|
// Kind is the kind of a Value.
|
|
type Kind int
|
|
|
|
// The following list is sorted alphabetically, but it's also important that
|
|
// KindAny is 0 so that a zero Value represents nil.
|
|
|
|
const (
|
|
KindAny Kind = iota
|
|
KindBool
|
|
KindDuration
|
|
KindFloat64
|
|
KindInt64
|
|
KindString
|
|
KindTime
|
|
KindUint64
|
|
KindGroup
|
|
KindLogValuer
|
|
)
|
|
|
|
var kindStrings = []string{
|
|
"Any",
|
|
"Bool",
|
|
"Duration",
|
|
"Float64",
|
|
"Int64",
|
|
"String",
|
|
"Time",
|
|
"Uint64",
|
|
"Group",
|
|
"LogValuer",
|
|
}
|
|
|
|
func (k Kind) String() string {
|
|
if k >= 0 && int(k) < len(kindStrings) {
|
|
return kindStrings[k]
|
|
}
|
|
return "<unknown slog.Kind>"
|
|
}
|
|
|
|
// Unexported version of Kind, just so we can store Kinds in Values.
|
|
// (No user-provided value has this type.)
|
|
type kind Kind
|
|
|
|
// Kind returns v's Kind.
|
|
func (v Value) Kind() Kind {
|
|
switch x := v.any.(type) {
|
|
case Kind:
|
|
return x
|
|
case stringptr:
|
|
return KindString
|
|
case timeLocation:
|
|
return KindTime
|
|
case groupptr:
|
|
return KindGroup
|
|
case LogValuer:
|
|
return KindLogValuer
|
|
case kind: // a kind is just a wrapper for a Kind
|
|
return KindAny
|
|
default:
|
|
return KindAny
|
|
}
|
|
}
|
|
|
|
//////////////// Constructors
|
|
|
|
// IntValue returns a Value for an int.
|
|
func IntValue(v int) Value {
|
|
return Int64Value(int64(v))
|
|
}
|
|
|
|
// Int64Value returns a Value for an int64.
|
|
func Int64Value(v int64) Value {
|
|
return Value{num: uint64(v), any: KindInt64}
|
|
}
|
|
|
|
// Uint64Value returns a Value for a uint64.
|
|
func Uint64Value(v uint64) Value {
|
|
return Value{num: v, any: KindUint64}
|
|
}
|
|
|
|
// Float64Value returns a Value for a floating-point number.
|
|
func Float64Value(v float64) Value {
|
|
return Value{num: math.Float64bits(v), any: KindFloat64}
|
|
}
|
|
|
|
// BoolValue returns a Value for a bool.
|
|
func BoolValue(v bool) Value {
|
|
u := uint64(0)
|
|
if v {
|
|
u = 1
|
|
}
|
|
return Value{num: u, any: KindBool}
|
|
}
|
|
|
|
// Unexported version of *time.Location, just so we can store *time.Locations in
|
|
// Values. (No user-provided value has this type.)
|
|
type timeLocation *time.Location
|
|
|
|
// TimeValue returns a Value for a time.Time.
|
|
// It discards the monotonic portion.
|
|
func TimeValue(v time.Time) Value {
|
|
if v.IsZero() {
|
|
// UnixNano on the zero time is undefined, so represent the zero time
|
|
// with a nil *time.Location instead. time.Time.Location method never
|
|
// returns nil, so a Value with any == timeLocation(nil) cannot be
|
|
// mistaken for any other Value, time.Time or otherwise.
|
|
return Value{any: timeLocation(nil)}
|
|
}
|
|
return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
|
|
}
|
|
|
|
// DurationValue returns a Value for a time.Duration.
|
|
func DurationValue(v time.Duration) Value {
|
|
return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
|
|
}
|
|
|
|
// AnyValue returns a Value for the supplied value.
|
|
//
|
|
// If the supplied value is of type Value, it is returned
|
|
// unmodified.
|
|
//
|
|
// Given a value of one of Go's predeclared string, bool, or
|
|
// (non-complex) numeric types, AnyValue returns a Value of kind
|
|
// String, Bool, Uint64, Int64, or Float64. The width of the
|
|
// original numeric type is not preserved.
|
|
//
|
|
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
|
|
// KindTime or KindDuration. The monotonic time is not preserved.
|
|
//
|
|
// For nil, or values of all other types, including named types whose
|
|
// underlying type is numeric, AnyValue returns a value of kind KindAny.
|
|
func AnyValue(v any) Value {
|
|
switch v := v.(type) {
|
|
case string:
|
|
return StringValue(v)
|
|
case int:
|
|
return Int64Value(int64(v))
|
|
case uint:
|
|
return Uint64Value(uint64(v))
|
|
case int64:
|
|
return Int64Value(v)
|
|
case uint64:
|
|
return Uint64Value(v)
|
|
case bool:
|
|
return BoolValue(v)
|
|
case time.Duration:
|
|
return DurationValue(v)
|
|
case time.Time:
|
|
return TimeValue(v)
|
|
case uint8:
|
|
return Uint64Value(uint64(v))
|
|
case uint16:
|
|
return Uint64Value(uint64(v))
|
|
case uint32:
|
|
return Uint64Value(uint64(v))
|
|
case uintptr:
|
|
return Uint64Value(uint64(v))
|
|
case int8:
|
|
return Int64Value(int64(v))
|
|
case int16:
|
|
return Int64Value(int64(v))
|
|
case int32:
|
|
return Int64Value(int64(v))
|
|
case float64:
|
|
return Float64Value(v)
|
|
case float32:
|
|
return Float64Value(float64(v))
|
|
case []Attr:
|
|
return GroupValue(v...)
|
|
case Kind:
|
|
return Value{any: kind(v)}
|
|
case Value:
|
|
return v
|
|
default:
|
|
return Value{any: v}
|
|
}
|
|
}
|
|
|
|
//////////////// Accessors
|
|
|
|
// Any returns v's value as an any.
|
|
func (v Value) Any() any {
|
|
switch v.Kind() {
|
|
case KindAny:
|
|
if k, ok := v.any.(kind); ok {
|
|
return Kind(k)
|
|
}
|
|
return v.any
|
|
case KindLogValuer:
|
|
return v.any
|
|
case KindGroup:
|
|
return v.group()
|
|
case KindInt64:
|
|
return int64(v.num)
|
|
case KindUint64:
|
|
return v.num
|
|
case KindFloat64:
|
|
return v.float()
|
|
case KindString:
|
|
return v.str()
|
|
case KindBool:
|
|
return v.bool()
|
|
case KindDuration:
|
|
return v.duration()
|
|
case KindTime:
|
|
return v.time()
|
|
default:
|
|
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
|
}
|
|
}
|
|
|
|
// Int64 returns v's value as an int64. It panics
|
|
// if v is not a signed integer.
|
|
func (v Value) Int64() int64 {
|
|
if g, w := v.Kind(), KindInt64; g != w {
|
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
|
}
|
|
return int64(v.num)
|
|
}
|
|
|
|
// Uint64 returns v's value as a uint64. It panics
|
|
// if v is not an unsigned integer.
|
|
func (v Value) Uint64() uint64 {
|
|
if g, w := v.Kind(), KindUint64; g != w {
|
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
|
}
|
|
return v.num
|
|
}
|
|
|
|
// Bool returns v's value as a bool. It panics
|
|
// if v is not a bool.
|
|
func (v Value) Bool() bool {
|
|
if g, w := v.Kind(), KindBool; g != w {
|
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
|
}
|
|
return v.bool()
|
|
}
|
|
|
|
func (v Value) bool() bool {
|
|
return v.num == 1
|
|
}
|
|
|
|
// Duration returns v's value as a time.Duration. It panics
|
|
// if v is not a time.Duration.
|
|
func (v Value) Duration() time.Duration {
|
|
if g, w := v.Kind(), KindDuration; g != w {
|
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
|
}
|
|
|
|
return v.duration()
|
|
}
|
|
|
|
func (v Value) duration() time.Duration {
|
|
return time.Duration(int64(v.num))
|
|
}
|
|
|
|
// Float64 returns v's value as a float64. It panics
|
|
// if v is not a float64.
|
|
func (v Value) Float64() float64 {
|
|
if g, w := v.Kind(), KindFloat64; g != w {
|
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
|
}
|
|
|
|
return v.float()
|
|
}
|
|
|
|
func (v Value) float() float64 {
|
|
return math.Float64frombits(v.num)
|
|
}
|
|
|
|
// Time returns v's value as a time.Time. It panics
|
|
// if v is not a time.Time.
|
|
func (v Value) Time() time.Time {
|
|
if g, w := v.Kind(), KindTime; g != w {
|
|
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
|
}
|
|
return v.time()
|
|
}
|
|
|
|
func (v Value) time() time.Time {
|
|
loc := v.any.(timeLocation)
|
|
if loc == nil {
|
|
return time.Time{}
|
|
}
|
|
return time.Unix(0, int64(v.num)).In(loc)
|
|
}
|
|
|
|
// LogValuer returns v's value as a LogValuer. It panics
|
|
// if v is not a LogValuer.
|
|
func (v Value) LogValuer() LogValuer {
|
|
return v.any.(LogValuer)
|
|
}
|
|
|
|
// Group returns v's value as a []Attr.
|
|
// It panics if v's Kind is not KindGroup.
|
|
func (v Value) Group() []Attr {
|
|
if sp, ok := v.any.(groupptr); ok {
|
|
return unsafe.Slice((*Attr)(sp), v.num)
|
|
}
|
|
panic("Group: bad kind")
|
|
}
|
|
|
|
func (v Value) group() []Attr {
|
|
return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
|
|
}
|
|
|
|
//////////////// Other
|
|
|
|
// Equal reports whether v and w represent the same Go value.
|
|
func (v Value) Equal(w Value) bool {
|
|
k1 := v.Kind()
|
|
k2 := w.Kind()
|
|
if k1 != k2 {
|
|
return false
|
|
}
|
|
switch k1 {
|
|
case KindInt64, KindUint64, KindBool, KindDuration:
|
|
return v.num == w.num
|
|
case KindString:
|
|
return v.str() == w.str()
|
|
case KindFloat64:
|
|
return v.float() == w.float()
|
|
case KindTime:
|
|
return v.time().Equal(w.time())
|
|
case KindAny, KindLogValuer:
|
|
return v.any == w.any // may panic if non-comparable
|
|
case KindGroup:
|
|
return slices.EqualFunc(v.group(), w.group(), Attr.Equal)
|
|
default:
|
|
panic(fmt.Sprintf("bad kind: %s", k1))
|
|
}
|
|
}
|
|
|
|
// append appends a text representation of v to dst.
|
|
// v is formatted as with fmt.Sprint.
|
|
func (v Value) append(dst []byte) []byte {
|
|
switch v.Kind() {
|
|
case KindString:
|
|
return append(dst, v.str()...)
|
|
case KindInt64:
|
|
return strconv.AppendInt(dst, int64(v.num), 10)
|
|
case KindUint64:
|
|
return strconv.AppendUint(dst, v.num, 10)
|
|
case KindFloat64:
|
|
return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
|
|
case KindBool:
|
|
return strconv.AppendBool(dst, v.bool())
|
|
case KindDuration:
|
|
return append(dst, v.duration().String()...)
|
|
case KindTime:
|
|
return append(dst, v.time().String()...)
|
|
case KindGroup:
|
|
return fmt.Append(dst, v.group())
|
|
case KindAny, KindLogValuer:
|
|
return fmt.Append(dst, v.any)
|
|
default:
|
|
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
|
}
|
|
}
|
|
|
|
// A LogValuer is any Go value that can convert itself into a Value for logging.
|
|
//
|
|
// This mechanism may be used to defer expensive operations until they are
|
|
// needed, or to expand a single value into a sequence of components.
|
|
type LogValuer interface {
|
|
LogValue() Value
|
|
}
|
|
|
|
const maxLogValues = 100
|
|
|
|
// Resolve repeatedly calls LogValue on v while it implements LogValuer,
|
|
// and returns the result.
|
|
// If v resolves to a group, the group's attributes' values are not recursively
|
|
// resolved.
|
|
// If the number of LogValue calls exceeds a threshold, a Value containing an
|
|
// error is returned.
|
|
// Resolve's return value is guaranteed not to be of Kind KindLogValuer.
|
|
func (v Value) Resolve() (rv Value) {
|
|
orig := v
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
rv = AnyValue(fmt.Errorf("LogValue panicked\n%s", stack(3, 5)))
|
|
}
|
|
}()
|
|
|
|
for i := 0; i < maxLogValues; i++ {
|
|
if v.Kind() != KindLogValuer {
|
|
return v
|
|
}
|
|
v = v.LogValuer().LogValue()
|
|
}
|
|
err := fmt.Errorf("LogValue called too many times on Value of type %T", orig.Any())
|
|
return AnyValue(err)
|
|
}
|
|
|
|
func stack(skip, nFrames int) string {
|
|
pcs := make([]uintptr, nFrames+1)
|
|
n := runtime.Callers(skip+1, pcs)
|
|
if n == 0 {
|
|
return "(no stack)"
|
|
}
|
|
frames := runtime.CallersFrames(pcs[:n])
|
|
var b strings.Builder
|
|
i := 0
|
|
for {
|
|
frame, more := frames.Next()
|
|
fmt.Fprintf(&b, "called from %s (%s:%d)\n", frame.Function, frame.File, frame.Line)
|
|
if !more {
|
|
break
|
|
}
|
|
i++
|
|
if i >= nFrames {
|
|
fmt.Fprintf(&b, "(rest of stack elided)\n")
|
|
break
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|