// 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 slogimport ()// 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.typeValuestruct { _ [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}type ( stringptr *byte// used in Value.any when the Value is a string groupptr *Attr// used in Value.any when the Value is a []Attr)// Kind is the kind of a [Value].typeKindint// The following list is sorted alphabetically, but it's also important that// KindAny is 0 so that a zero Value represents nil.const (KindAnyKind = iotaKindBoolKindDurationKindFloat64KindInt64KindStringKindTimeKindUint64KindGroupKindLogValuer)var kindStrings = []string{"Any","Bool","Duration","Float64","Int64","String","Time","Uint64","Group","LogValuer",}func ( Kind) () string {if >= 0 && int() < len(kindStrings) {returnkindStrings[] }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 ( Value) () Kind {switch x := .any.(type) {caseKind:returncasestringptr:returnKindStringcasetimeLocation, timeTime:returnKindTimecasegroupptr:returnKindGroupcaseLogValuer:returnKindLogValuercasekind: // a kind is just a wrapper for a KindreturnKindAnydefault:returnKindAny }}//////////////// Constructors// StringValue returns a new [Value] for a string.func ( string) Value {returnValue{num: uint64(len()), any: stringptr(unsafe.StringData())}}// IntValue returns a [Value] for an int.func ( int) Value {returnInt64Value(int64())}// Int64Value returns a [Value] for an int64.func ( int64) Value {returnValue{num: uint64(), any: KindInt64}}// Uint64Value returns a [Value] for a uint64.func ( uint64) Value {returnValue{num: , any: KindUint64}}// Float64Value returns a [Value] for a floating-point number.func ( float64) Value {returnValue{num: math.Float64bits(), any: KindFloat64}}// BoolValue returns a [Value] for a bool.func ( bool) Value { := uint64(0)if { = 1 }returnValue{num: , any: KindBool}}type (// Unexported version of *time.Location, just so we can store *time.Locations in // Values. (No user-provided value has this type.) timeLocation *time.Location// timeTime is for times where UnixNano is undefined. timeTime time.Time)// TimeValue returns a [Value] for a [time.Time].// It discards the monotonic portion.func ( time.Time) Value {if .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.returnValue{any: timeLocation(nil)} } := .UnixNano() := time.Unix(0, )if .Equal() {// UnixNano correctly represents the time, so use a zero-alloc representation.returnValue{num: uint64(), any: timeLocation(.Location())} }// Fall back to the general form. // Strip the monotonic portion to match the other representation.returnValue{any: timeTime(.Round(0))}}// DurationValue returns a [Value] for a [time.Duration].func ( time.Duration) Value {returnValue{num: uint64(.Nanoseconds()), any: KindDuration}}// GroupValue returns a new [Value] for a list of Attrs.// The caller must not subsequently mutate the argument slice.func ( ...Attr) Value {// Remove empty groups. // It is simpler overall to do this at construction than // to check each Group recursively for emptiness.if := countEmptyGroups(); > 0 { := make([]Attr, 0, len()-)for , := range {if !.Value.isEmptyGroup() { = append(, ) } } = }returnValue{num: uint64(len()), any: groupptr(unsafe.SliceData())}}// countEmptyGroups returns the number of empty group values in its argument.func countEmptyGroups( []Attr) int { := 0for , := range {if .Value.isEmptyGroup() { ++ } }return}// 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// [KindString], [KindBool], [KindUint64], [KindInt64], or [KindFloat64].// 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 ( any) Value {switch v := .(type) {casestring:returnStringValue()caseint:returnInt64Value(int64())caseuint:returnUint64Value(uint64())caseint64:returnInt64Value()caseuint64:returnUint64Value()casebool:returnBoolValue()casetime.Duration:returnDurationValue()casetime.Time:returnTimeValue()caseuint8:returnUint64Value(uint64())caseuint16:returnUint64Value(uint64())caseuint32:returnUint64Value(uint64())caseuintptr:returnUint64Value(uint64())caseint8:returnInt64Value(int64())caseint16:returnInt64Value(int64())caseint32:returnInt64Value(int64())casefloat64:returnFloat64Value()casefloat32:returnFloat64Value(float64())case []Attr:returnGroupValue(...)caseKind:returnValue{any: kind()}caseValue:returndefault:returnValue{any: } }}//////////////// Accessors// Any returns v's value as an any.func ( Value) () any {switch .Kind() {caseKindAny:if , := .any.(kind); {returnKind() }return .anycaseKindLogValuer:return .anycaseKindGroup:return .group()caseKindInt64:returnint64(.num)caseKindUint64:return .numcaseKindFloat64:return .float()caseKindString:return .str()caseKindBool:return .bool()caseKindDuration:return .duration()caseKindTime:return .time()default:panic(fmt.Sprintf("bad kind: %s", .Kind())) }}// String returns Value's value as a string, formatted like [fmt.Sprint]. Unlike// the methods Int64, Float64, and so on, which panic if v is of the// wrong kind, String never panics.func ( Value) () string {if , := .any.(stringptr); {returnunsafe.String(, .num) }var []bytereturnstring(.append())}func ( Value) () string {returnunsafe.String(.any.(stringptr), .num)}// Int64 returns v's value as an int64. It panics// if v is not a signed integer.func ( Value) () int64 {if , := .Kind(), KindInt64; != {panic(fmt.Sprintf("Value kind is %s, not %s", , )) }returnint64(.num)}// Uint64 returns v's value as a uint64. It panics// if v is not an unsigned integer.func ( Value) () uint64 {if , := .Kind(), KindUint64; != {panic(fmt.Sprintf("Value kind is %s, not %s", , )) }return .num}// Bool returns v's value as a bool. It panics// if v is not a bool.func ( Value) () bool {if , := .Kind(), KindBool; != {panic(fmt.Sprintf("Value kind is %s, not %s", , )) }return .bool()}func ( Value) () bool {return .num == 1}// Duration returns v's value as a [time.Duration]. It panics// if v is not a time.Duration.func ( Value) () time.Duration {if , := .Kind(), KindDuration; != {panic(fmt.Sprintf("Value kind is %s, not %s", , )) }return .duration()}func ( Value) () time.Duration {returntime.Duration(int64(.num))}// Float64 returns v's value as a float64. It panics// if v is not a float64.func ( Value) () float64 {if , := .Kind(), KindFloat64; != {panic(fmt.Sprintf("Value kind is %s, not %s", , )) }return .float()}func ( Value) () float64 {returnmath.Float64frombits(.num)}// Time returns v's value as a [time.Time]. It panics// if v is not a time.Time.func ( Value) () time.Time {if , := .Kind(), KindTime; != {panic(fmt.Sprintf("Value kind is %s, not %s", , )) }return .time()}// See TimeValue to understand how times are represented.func ( Value) () time.Time {switch a := .any.(type) {casetimeLocation:if == nil {returntime.Time{} }returntime.Unix(0, int64(.num)).In()casetimeTime:returntime.Time()default:panic(fmt.Sprintf("bad time type %T", .any)) }}// LogValuer returns v's value as a LogValuer. It panics// if v is not a LogValuer.func ( Value) () LogValuer {return .any.(LogValuer)}// Group returns v's value as a []Attr.// It panics if v's [Kind] is not [KindGroup].func ( Value) () []Attr {if , := .any.(groupptr); {returnunsafe.Slice((*Attr)(), .num) }panic("Group: bad kind")}func ( Value) () []Attr {returnunsafe.Slice((*Attr)(.any.(groupptr)), .num)}//////////////// Other// Equal reports whether v and w represent the same Go value.func ( Value) ( Value) bool { := .Kind() := .Kind()if != {returnfalse }switch {caseKindInt64, KindUint64, KindBool, KindDuration:return .num == .numcaseKindString:return .str() == .str()caseKindFloat64:return .float() == .float()caseKindTime:return .time().Equal(.time())caseKindAny, KindLogValuer:return .any == .any// may panic if non-comparablecaseKindGroup:returnslices.EqualFunc(.group(), .group(), Attr.Equal)default:panic(fmt.Sprintf("bad kind: %s", )) }}// isEmptyGroup reports whether v is a group that has no attributes.func ( Value) () bool {if .Kind() != KindGroup {returnfalse }// We do not need to recursively examine the group's Attrs for emptiness, // because GroupValue removed them when the group was constructed, and // groups are immutable.returnlen(.group()) == 0}// append appends a text representation of v to dst.// v is formatted as with fmt.Sprint.func ( Value) ( []byte) []byte {switch .Kind() {caseKindString:returnappend(, .str()...)caseKindInt64:returnstrconv.AppendInt(, int64(.num), 10)caseKindUint64:returnstrconv.AppendUint(, .num, 10)caseKindFloat64:returnstrconv.AppendFloat(, .float(), 'g', -1, 64)caseKindBool:returnstrconv.AppendBool(, .bool())caseKindDuration:returnappend(, .duration().String()...)caseKindTime:returnappend(, .time().String()...)caseKindGroup:returnfmt.Append(, .group())caseKindAny, KindLogValuer:returnfmt.Append(, .any)default:panic(fmt.Sprintf("bad kind: %s", .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.typeLogValuerinterface {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 ( Value) () ( Value) { := deferfunc() {if := recover(); != nil { = AnyValue(fmt.Errorf("LogValue panicked\n%s", stack(3, 5))) } }()for := 0; < maxLogValues; ++ {if .Kind() != KindLogValuer {return } = .LogValuer().LogValue() } := fmt.Errorf("LogValue called too many times on Value of type %T", .Any())returnAnyValue()}func stack(, int) string { := make([]uintptr, +1) := runtime.Callers(+1, )if == 0 {return"(no stack)" } := runtime.CallersFrames([:])varstrings.Builder := 0for { , := .Next()fmt.Fprintf(&, "called from %s (%s:%d)\n", .Function, .File, .Line)if ! {break } ++if >= {fmt.Fprintf(&, "(rest of stack elided)\n")break } }return .String()}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.