| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- // Copyright The OpenTelemetry Authors
- // SPDX-License-Identifier: Apache-2.0
- package telemetry // import "go.opentelemetry.io/otel/trace/internal/telemetry"
- import (
- "bytes"
- "cmp"
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "math"
- "slices"
- "strconv"
- "unsafe"
- )
- // A Value represents a structured value.
- // A zero value is valid and represents an empty value.
- type Value struct {
- // Ensure forward compatibility by explicitly making this not comparable.
- noCmp [0]func() //nolint: unused // This is indeed used.
- // num holds the value for Int64, Float64, and Bool. It holds the length
- // for String, Bytes, Slice, Map.
- num uint64
- // any holds either the KindBool, KindInt64, KindFloat64, stringptr,
- // bytesptr, sliceptr, or mapptr. If KindBool, KindInt64, or KindFloat64
- // then the value of Value is in num as described above. Otherwise, it
- // contains the value wrapped in the appropriate type.
- any any
- }
- type (
- // sliceptr represents a value in Value.any for KindString Values.
- stringptr *byte
- // bytesptr represents a value in Value.any for KindBytes Values.
- bytesptr *byte
- // sliceptr represents a value in Value.any for KindSlice Values.
- sliceptr *Value
- // mapptr represents a value in Value.any for KindMap Values.
- mapptr *Attr
- )
- // ValueKind is the kind of a [Value].
- type ValueKind int
- // ValueKind values.
- const (
- ValueKindEmpty ValueKind = iota
- ValueKindBool
- ValueKindFloat64
- ValueKindInt64
- ValueKindString
- ValueKindBytes
- ValueKindSlice
- ValueKindMap
- )
- var valueKindStrings = []string{
- "Empty",
- "Bool",
- "Float64",
- "Int64",
- "String",
- "Bytes",
- "Slice",
- "Map",
- }
- func (k ValueKind) String() string {
- if k >= 0 && int(k) < len(valueKindStrings) {
- return valueKindStrings[k]
- }
- return "<unknown telemetry.ValueKind>"
- }
- // StringValue returns a new [Value] for a string.
- func StringValue(v string) Value {
- return Value{
- num: uint64(len(v)),
- any: stringptr(unsafe.StringData(v)),
- }
- }
- // 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), // nolint: gosec // Store raw bytes.
- any: ValueKindInt64,
- }
- }
- // Float64Value returns a [Value] for a float64.
- func Float64Value(v float64) Value {
- return Value{num: math.Float64bits(v), any: ValueKindFloat64}
- }
- // BoolValue returns a [Value] for a bool.
- func BoolValue(v bool) Value { //nolint:revive // Not a control flag.
- var n uint64
- if v {
- n = 1
- }
- return Value{num: n, any: ValueKindBool}
- }
- // BytesValue returns a [Value] for a byte slice. The passed slice must not be
- // changed after it is passed.
- func BytesValue(v []byte) Value {
- return Value{
- num: uint64(len(v)),
- any: bytesptr(unsafe.SliceData(v)),
- }
- }
- // SliceValue returns a [Value] for a slice of [Value]. The passed slice must
- // not be changed after it is passed.
- func SliceValue(vs ...Value) Value {
- return Value{
- num: uint64(len(vs)),
- any: sliceptr(unsafe.SliceData(vs)),
- }
- }
- // MapValue returns a new [Value] for a slice of key-value pairs. The passed
- // slice must not be changed after it is passed.
- func MapValue(kvs ...Attr) Value {
- return Value{
- num: uint64(len(kvs)),
- any: mapptr(unsafe.SliceData(kvs)),
- }
- }
- // AsString returns the value held by v as a string.
- func (v Value) AsString() string {
- if sp, ok := v.any.(stringptr); ok {
- return unsafe.String(sp, v.num)
- }
- // TODO: error handle
- return ""
- }
- // asString returns the value held by v as a string. It will panic if the Value
- // is not KindString.
- func (v Value) asString() string {
- return unsafe.String(v.any.(stringptr), v.num)
- }
- // AsInt64 returns the value held by v as an int64.
- func (v Value) AsInt64() int64 {
- if v.Kind() != ValueKindInt64 {
- // TODO: error handle
- return 0
- }
- return v.asInt64()
- }
- // asInt64 returns the value held by v as an int64. If v is not of KindInt64,
- // this will return garbage.
- func (v Value) asInt64() int64 {
- // Assumes v.num was a valid int64 (overflow not checked).
- return int64(v.num) // nolint: gosec
- }
- // AsBool returns the value held by v as a bool.
- func (v Value) AsBool() bool {
- if v.Kind() != ValueKindBool {
- // TODO: error handle
- return false
- }
- return v.asBool()
- }
- // asBool returns the value held by v as a bool. If v is not of KindBool, this
- // will return garbage.
- func (v Value) asBool() bool { return v.num == 1 }
- // AsFloat64 returns the value held by v as a float64.
- func (v Value) AsFloat64() float64 {
- if v.Kind() != ValueKindFloat64 {
- // TODO: error handle
- return 0
- }
- return v.asFloat64()
- }
- // asFloat64 returns the value held by v as a float64. If v is not of
- // KindFloat64, this will return garbage.
- func (v Value) asFloat64() float64 { return math.Float64frombits(v.num) }
- // AsBytes returns the value held by v as a []byte.
- func (v Value) AsBytes() []byte {
- if sp, ok := v.any.(bytesptr); ok {
- return unsafe.Slice((*byte)(sp), v.num)
- }
- // TODO: error handle
- return nil
- }
- // asBytes returns the value held by v as a []byte. It will panic if the Value
- // is not KindBytes.
- func (v Value) asBytes() []byte {
- return unsafe.Slice((*byte)(v.any.(bytesptr)), v.num)
- }
- // AsSlice returns the value held by v as a []Value.
- func (v Value) AsSlice() []Value {
- if sp, ok := v.any.(sliceptr); ok {
- return unsafe.Slice((*Value)(sp), v.num)
- }
- // TODO: error handle
- return nil
- }
- // asSlice returns the value held by v as a []Value. It will panic if the Value
- // is not KindSlice.
- func (v Value) asSlice() []Value {
- return unsafe.Slice((*Value)(v.any.(sliceptr)), v.num)
- }
- // AsMap returns the value held by v as a []Attr.
- func (v Value) AsMap() []Attr {
- if sp, ok := v.any.(mapptr); ok {
- return unsafe.Slice((*Attr)(sp), v.num)
- }
- // TODO: error handle
- return nil
- }
- // asMap returns the value held by v as a []Attr. It will panic if the
- // Value is not KindMap.
- func (v Value) asMap() []Attr {
- return unsafe.Slice((*Attr)(v.any.(mapptr)), v.num)
- }
- // Kind returns the Kind of v.
- func (v Value) Kind() ValueKind {
- switch x := v.any.(type) {
- case ValueKind:
- return x
- case stringptr:
- return ValueKindString
- case bytesptr:
- return ValueKindBytes
- case sliceptr:
- return ValueKindSlice
- case mapptr:
- return ValueKindMap
- default:
- return ValueKindEmpty
- }
- }
- // Empty returns if v does not hold any value.
- func (v Value) Empty() bool { return v.Kind() == ValueKindEmpty }
- // Equal returns if v is equal to w.
- func (v Value) Equal(w Value) bool {
- k1 := v.Kind()
- k2 := w.Kind()
- if k1 != k2 {
- return false
- }
- switch k1 {
- case ValueKindInt64, ValueKindBool:
- return v.num == w.num
- case ValueKindString:
- return v.asString() == w.asString()
- case ValueKindFloat64:
- return v.asFloat64() == w.asFloat64()
- case ValueKindSlice:
- return slices.EqualFunc(v.asSlice(), w.asSlice(), Value.Equal)
- case ValueKindMap:
- sv := sortMap(v.asMap())
- sw := sortMap(w.asMap())
- return slices.EqualFunc(sv, sw, Attr.Equal)
- case ValueKindBytes:
- return bytes.Equal(v.asBytes(), w.asBytes())
- case ValueKindEmpty:
- return true
- default:
- // TODO: error handle
- return false
- }
- }
- func sortMap(m []Attr) []Attr {
- sm := make([]Attr, len(m))
- copy(sm, m)
- slices.SortFunc(sm, func(a, b Attr) int {
- return cmp.Compare(a.Key, b.Key)
- })
- return sm
- }
- // String returns Value's value as a string, formatted like [fmt.Sprint].
- //
- // The returned string is meant for debugging;
- // the string representation is not stable.
- func (v Value) String() string {
- switch v.Kind() {
- case ValueKindString:
- return v.asString()
- case ValueKindInt64:
- // Assumes v.num was a valid int64 (overflow not checked).
- return strconv.FormatInt(int64(v.num), 10) // nolint: gosec
- case ValueKindFloat64:
- return strconv.FormatFloat(v.asFloat64(), 'g', -1, 64)
- case ValueKindBool:
- return strconv.FormatBool(v.asBool())
- case ValueKindBytes:
- return fmt.Sprint(v.asBytes())
- case ValueKindMap:
- return fmt.Sprint(v.asMap())
- case ValueKindSlice:
- return fmt.Sprint(v.asSlice())
- case ValueKindEmpty:
- return "<nil>"
- default:
- // Try to handle this as gracefully as possible.
- //
- // Don't panic here. The goal here is to have developers find this
- // first if a slog.Kind is is not handled. It is
- // preferable to have user's open issue asking why their attributes
- // have a "unhandled: " prefix than say that their code is panicking.
- return fmt.Sprintf("<unhandled telemetry.ValueKind: %s>", v.Kind())
- }
- }
- // MarshalJSON encodes v into OTLP formatted JSON.
- func (v *Value) MarshalJSON() ([]byte, error) {
- switch v.Kind() {
- case ValueKindString:
- return json.Marshal(struct {
- Value string `json:"stringValue"`
- }{v.asString()})
- case ValueKindInt64:
- return json.Marshal(struct {
- Value string `json:"intValue"`
- }{strconv.FormatInt(int64(v.num), 10)}) // nolint: gosec // From raw bytes.
- case ValueKindFloat64:
- return json.Marshal(struct {
- Value float64 `json:"doubleValue"`
- }{v.asFloat64()})
- case ValueKindBool:
- return json.Marshal(struct {
- Value bool `json:"boolValue"`
- }{v.asBool()})
- case ValueKindBytes:
- return json.Marshal(struct {
- Value []byte `json:"bytesValue"`
- }{v.asBytes()})
- case ValueKindMap:
- return json.Marshal(struct {
- Value struct {
- Values []Attr `json:"values"`
- } `json:"kvlistValue"`
- }{struct {
- Values []Attr `json:"values"`
- }{v.asMap()}})
- case ValueKindSlice:
- return json.Marshal(struct {
- Value struct {
- Values []Value `json:"values"`
- } `json:"arrayValue"`
- }{struct {
- Values []Value `json:"values"`
- }{v.asSlice()}})
- case ValueKindEmpty:
- return nil, nil
- default:
- return nil, fmt.Errorf("unknown Value kind: %s", v.Kind().String())
- }
- }
- // UnmarshalJSON decodes the OTLP formatted JSON contained in data into v.
- func (v *Value) UnmarshalJSON(data []byte) error {
- decoder := json.NewDecoder(bytes.NewReader(data))
- t, err := decoder.Token()
- if err != nil {
- return err
- }
- if t != json.Delim('{') {
- return errors.New("invalid Value type")
- }
- for decoder.More() {
- keyIface, err := decoder.Token()
- if err != nil {
- if errors.Is(err, io.EOF) {
- // Empty.
- return nil
- }
- return err
- }
- key, ok := keyIface.(string)
- if !ok {
- return fmt.Errorf("invalid Value key: %#v", keyIface)
- }
- switch key {
- case "stringValue", "string_value":
- var val string
- err = decoder.Decode(&val)
- *v = StringValue(val)
- case "boolValue", "bool_value":
- var val bool
- err = decoder.Decode(&val)
- *v = BoolValue(val)
- case "intValue", "int_value":
- var val protoInt64
- err = decoder.Decode(&val)
- *v = Int64Value(val.Int64())
- case "doubleValue", "double_value":
- var val float64
- err = decoder.Decode(&val)
- *v = Float64Value(val)
- case "bytesValue", "bytes_value":
- var val64 string
- if err := decoder.Decode(&val64); err != nil {
- return err
- }
- var val []byte
- val, err = base64.StdEncoding.DecodeString(val64)
- *v = BytesValue(val)
- case "arrayValue", "array_value":
- var val struct{ Values []Value }
- err = decoder.Decode(&val)
- *v = SliceValue(val.Values...)
- case "kvlistValue", "kvlist_value":
- var val struct{ Values []Attr }
- err = decoder.Decode(&val)
- *v = MapValue(val.Values...)
- default:
- // Skip unknown.
- continue
- }
- // Use first valid. Ignore the rest.
- return err
- }
- // Only unknown fields. Return nil without unmarshaling any value.
- return nil
- }
|