| // Copyright The OpenTelemetry Authors | |
| // SPDX-License-Identifier: Apache-2.0 | |
| package attribute | |
| import ( | |
| "cmp" | |
| "encoding/base64" | |
| "encoding/json" | |
| "fmt" | |
| "math" | |
| "reflect" | |
| "slices" | |
| "strconv" | |
| "strings" | |
| "unicode/utf8" | |
| attribute "go.opentelemetry.io/otel/attribute/internal" | |
| ) | |
| //go:generate stringer -type=Type | |
| // Type describes the kind of data a [Value] holds. | |
| type Type int // nolint: revive // redefines builtin Type. | |
| // Value represents the value in key-value pairs. | |
| // | |
| // Note that the zero value is a valid empty value. | |
| type Value struct { | |
| vtype Type | |
| numeric uint64 | |
| stringly string | |
| slice any | |
| } | |
| const ( | |
| // EMPTY identifies a Value with no data. | |
| EMPTY Type = iota | |
| // BOOL identifies a Value containing a bool. | |
| BOOL | |
| // INT64 identifies a Value containing an int64. | |
| INT64 | |
| // FLOAT64 identifies a Value containing a float64. | |
| FLOAT64 | |
| // STRING identifies a Value containing a string. | |
| STRING | |
| // BOOLSLICE identifies a Value containing a []bool. | |
| BOOLSLICE | |
| // INT64SLICE identifies a Value containing a []int64. | |
| INT64SLICE | |
| // FLOAT64SLICE identifies a Value containing a []float64. | |
| FLOAT64SLICE | |
| // STRINGSLICE identifies a Value containing a []string. | |
| STRINGSLICE | |
| // BYTESLICE identifies a Value containing a []byte. | |
| BYTESLICE | |
| // SLICE identifies a Value containing a []Value. | |
| SLICE | |
| // MAP identifies a Value containing a []KeyValue representation of a map. | |
| // | |
| // Note that MAP values may contain duplicate keys if duplicate keys are | |
| // provided when creating the value. | |
| MAP | |
| // INVALID is used for a Value with no value set. | |
| // | |
| // Deprecated: Use EMPTY instead as an empty value is a valid value. | |
| INVALID = EMPTY | |
| ) | |
| // BoolValue returns a [Value] for a bool value. | |
| func BoolValue(v bool) Value { | |
| return Value{ | |
| vtype: BOOL, | |
| numeric: boolToRaw(v), | |
| } | |
| } | |
| // BoolSliceValue returns a [Value] for a []bool value. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| func BoolSliceValue(v []bool) Value { | |
| return Value{vtype: BOOLSLICE, slice: attribute.SliceValue(v)} | |
| } | |
| // IntValue returns a [Value] for an int value. | |
| // | |
| // It is provided as a convenience for [Int64Value]. | |
| func IntValue(v int) Value { | |
| return Int64Value(int64(v)) | |
| } | |
| // IntSliceValue returns a [Value] for a []int value. | |
| // | |
| // It is provided as a convenience for [Int64SliceValue]. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| func IntSliceValue(v []int) Value { | |
| val := Value{vtype: INT64SLICE} | |
| // Avoid the common tiny-slice cases from allocating a new slice. | |
| switch len(v) { | |
| case 0: | |
| val.slice = [0]int64{} | |
| case 1: | |
| val.slice = [1]int64{int64(v[0])} | |
| case 2: | |
| val.slice = [2]int64{int64(v[0]), int64(v[1])} | |
| case 3: | |
| val.slice = [3]int64{int64(v[0]), int64(v[1]), int64(v[2])} | |
| default: | |
| // Fallback to a new slice for larger slices. | |
| cp := make([]int64, len(v)) | |
| for i, val := range v { | |
| cp[i] = int64(val) | |
| } | |
| val.slice = attribute.SliceValue(cp) | |
| } | |
| return val | |
| } | |
| // Int64Value returns a [Value] for an int64 value. | |
| func Int64Value(v int64) Value { | |
| return Value{ | |
| vtype: INT64, | |
| numeric: int64ToRaw(v), | |
| } | |
| } | |
| // Int64SliceValue returns a [Value] for a []int64 value. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| func Int64SliceValue(v []int64) Value { | |
| return Value{vtype: INT64SLICE, slice: attribute.SliceValue(v)} | |
| } | |
| // Float64Value returns a [Value] for a float64 value. | |
| func Float64Value(v float64) Value { | |
| return Value{ | |
| vtype: FLOAT64, | |
| numeric: float64ToRaw(v), | |
| } | |
| } | |
| // Float64SliceValue returns a [Value] for a []float64 value. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| func Float64SliceValue(v []float64) Value { | |
| return Value{vtype: FLOAT64SLICE, slice: attribute.SliceValue(v)} | |
| } | |
| // StringValue returns a [Value] for a string value. | |
| func StringValue(v string) Value { | |
| return Value{ | |
| vtype: STRING, | |
| stringly: v, | |
| } | |
| } | |
| // StringSliceValue returns a [Value] for a []string value. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| func StringSliceValue(v []string) Value { | |
| return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)} | |
| } | |
| // ByteSliceValue returns a [Value] for a []byte value. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| func ByteSliceValue(v []byte) Value { | |
| return Value{ | |
| vtype: BYTESLICE, | |
| stringly: string(v), | |
| } | |
| } | |
| // SliceValue returns a [Value] for a []Value value. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| func SliceValue(v ...Value) Value { | |
| return Value{vtype: SLICE, slice: sliceValue(v)} | |
| } | |
| // MapValue returns a [Value] for a []KeyValue value. | |
| // | |
| // Note that many observability backends are not optimized to query, index, or | |
| // aggregate complex attribute values. Complex values may also carry | |
| // additional performance overhead. Prefer primitive values when | |
| // possible. | |
| // | |
| // Users should avoid providing duplicate keys; many receivers handle maps | |
| // containing duplicate keys unpredictably. | |
| // | |
| // The order of v is not preserved. | |
| func MapValue(v ...KeyValue) Value { | |
| return Value{vtype: MAP, slice: mapValue(v)} | |
| } | |
| // Type returns v's type. | |
| func (v Value) Type() Type { | |
| return v.vtype | |
| } | |
| // AsBool returns the bool value. Make sure that the Value's type is | |
| // BOOL. | |
| func (v Value) AsBool() bool { | |
| return rawToBool(v.numeric) | |
| } | |
| // AsBoolSlice returns the []bool value. Make sure that the Value's type is | |
| // BOOLSLICE. | |
| func (v Value) AsBoolSlice() []bool { | |
| if v.vtype != BOOLSLICE { | |
| return nil | |
| } | |
| return v.asBoolSlice() | |
| } | |
| func (v Value) asBoolSlice() []bool { | |
| return attribute.AsSlice[bool](v.slice) | |
| } | |
| // AsInt64 returns the int64 value. Make sure that the Value's type is | |
| // INT64. | |
| func (v Value) AsInt64() int64 { | |
| return rawToInt64(v.numeric) | |
| } | |
| // AsInt64Slice returns the []int64 value. Make sure that the Value's type is | |
| // INT64SLICE. | |
| func (v Value) AsInt64Slice() []int64 { | |
| if v.vtype != INT64SLICE { | |
| return nil | |
| } | |
| return v.asInt64Slice() | |
| } | |
| func (v Value) asInt64Slice() []int64 { | |
| return attribute.AsSlice[int64](v.slice) | |
| } | |
| // AsFloat64 returns the float64 value. Make sure that the Value's | |
| // type is FLOAT64. | |
| func (v Value) AsFloat64() float64 { | |
| return rawToFloat64(v.numeric) | |
| } | |
| // AsFloat64Slice returns the []float64 value. Make sure that the Value's type is | |
| // FLOAT64SLICE. | |
| func (v Value) AsFloat64Slice() []float64 { | |
| if v.vtype != FLOAT64SLICE { | |
| return nil | |
| } | |
| return v.asFloat64Slice() | |
| } | |
| func (v Value) asFloat64Slice() []float64 { | |
| return attribute.AsSlice[float64](v.slice) | |
| } | |
| // AsString returns the string value. Make sure that the Value's type | |
| // is STRING. | |
| func (v Value) AsString() string { | |
| return v.stringly | |
| } | |
| // AsStringSlice returns the []string value. Make sure that the Value's type is | |
| // STRINGSLICE. | |
| func (v Value) AsStringSlice() []string { | |
| if v.vtype != STRINGSLICE { | |
| return nil | |
| } | |
| return v.asStringSlice() | |
| } | |
| func (v Value) asStringSlice() []string { | |
| return attribute.AsSlice[string](v.slice) | |
| } | |
| // AsSlice returns the []Value value. Make sure that the Value's type is | |
| // SLICE. | |
| func (v Value) AsSlice() []Value { | |
| if v.vtype != SLICE { | |
| return nil | |
| } | |
| return v.asSlice() | |
| } | |
| func (v Value) asSlice() []Value { | |
| switch vals := v.slice.(type) { | |
| case [0]Value: | |
| return []Value{} | |
| case [1]Value: | |
| return []Value{vals[0]} | |
| case [2]Value: | |
| return []Value{vals[0], vals[1]} | |
| case [3]Value: | |
| return []Value{vals[0], vals[1], vals[2]} | |
| case [4]Value: | |
| return []Value{vals[0], vals[1], vals[2], vals[3]} | |
| case [5]Value: | |
| return []Value{vals[0], vals[1], vals[2], vals[3], vals[4]} | |
| default: | |
| return asValueSliceReflect(v.slice) | |
| } | |
| } | |
| func asValueSliceReflect(v any) []Value { | |
| rv := reflect.ValueOf(v) | |
| if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[Value]() { | |
| return nil | |
| } | |
| cpy := make([]Value, rv.Len()) | |
| if len(cpy) > 0 { | |
| _ = reflect.Copy(reflect.ValueOf(cpy), rv) | |
| } | |
| return cpy | |
| } | |
| // AsMap returns the []KeyValue value. Make sure that the Value's type is | |
| // MAP. | |
| // | |
| // The returned slice is sorted by key and may differ from the order | |
| // provided when creating the map value. | |
| // | |
| // The returned slice may contain duplicate keys if duplicate keys were | |
| // provided when creating the map value. Callers should not assume the returned | |
| // keys are unique. | |
| func (v Value) AsMap() []KeyValue { | |
| if v.vtype != MAP { | |
| return nil | |
| } | |
| return v.asMap() | |
| } | |
| func (v Value) asMap() []KeyValue { | |
| switch vals := v.slice.(type) { | |
| case [0]KeyValue: | |
| return []KeyValue{} | |
| case [1]KeyValue: | |
| return []KeyValue{vals[0]} | |
| case [2]KeyValue: | |
| return []KeyValue{vals[0], vals[1]} | |
| case [3]KeyValue: | |
| return []KeyValue{vals[0], vals[1], vals[2]} | |
| case [4]KeyValue: | |
| return []KeyValue{vals[0], vals[1], vals[2], vals[3]} | |
| case [5]KeyValue: | |
| return []KeyValue{vals[0], vals[1], vals[2], vals[3], vals[4]} | |
| default: | |
| return asKeyValueSliceReflect(v.slice) | |
| } | |
| } | |
| func asKeyValueSliceReflect(v any) []KeyValue { | |
| rv := reflect.ValueOf(v) | |
| if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[KeyValue]() { | |
| return nil | |
| } | |
| cpy := make([]KeyValue, rv.Len()) | |
| if len(cpy) > 0 { | |
| _ = reflect.Copy(reflect.ValueOf(cpy), rv) | |
| } | |
| return cpy | |
| } | |
| // AsByteSlice returns the bytes value. Make sure that the Value's type | |
| // is BYTESLICE. | |
| func (v Value) AsByteSlice() []byte { | |
| if v.vtype != BYTESLICE { | |
| return nil | |
| } | |
| return v.asByteSlice() | |
| } | |
| func (v Value) asByteSlice() []byte { | |
| return []byte(v.stringly) | |
| } | |
| type unknownValueType struct{} | |
| // AsInterface returns Value's data as any. | |
| func (v Value) AsInterface() any { | |
| switch v.Type() { | |
| case BOOL: | |
| return v.AsBool() | |
| case BOOLSLICE: | |
| return v.asBoolSlice() | |
| case INT64: | |
| return v.AsInt64() | |
| case INT64SLICE: | |
| return v.asInt64Slice() | |
| case FLOAT64: | |
| return v.AsFloat64() | |
| case FLOAT64SLICE: | |
| return v.asFloat64Slice() | |
| case STRING: | |
| return v.stringly | |
| case STRINGSLICE: | |
| return v.asStringSlice() | |
| case BYTESLICE: | |
| return v.asByteSlice() | |
| case SLICE: | |
| return v.asSlice() | |
| case MAP: | |
| return v.asMap() | |
| case EMPTY: | |
| return nil | |
| } | |
| return unknownValueType{} | |
| } | |
| // String returns a string representation of Value using the | |
| // [OpenTelemetry AnyValue representation for non-OTLP protocols] rules. | |
| // | |
| // Strings are returned as-is without JSON quoting, booleans and integers use | |
| // JSON literals, floating-point values use JSON numbers except that NaN and | |
| // ±Inf are rendered as NaN, Infinity, and -Infinity, byte slices are | |
| // base64-encoded, empty values are the empty string, slices are encoded as JSON | |
| // arrays, and maps are encoded as JSON objects. String, byte, and special | |
| // floating-point values inside arrays and maps are encoded as JSON strings, and | |
| // empty values inside arrays and maps are encoded as null. | |
| // | |
| // [OpenTelemetry AnyValue representation for non-OTLP protocols]: https://opentelemetry.io/docs/specs/otel/common/#anyvalue-representation-for-non-otlp-protocols | |
| func (v Value) String() string { | |
| switch v.Type() { | |
| case BOOL: | |
| return strconv.FormatBool(v.AsBool()) | |
| case BOOLSLICE: | |
| return formatBoolSliceValue(v.slice) | |
| case INT64: | |
| return strconv.FormatInt(v.AsInt64(), 10) | |
| case INT64SLICE: | |
| return formatInt64SliceValue(v.slice) | |
| case FLOAT64: | |
| return formatFloat64(v.AsFloat64()) | |
| case FLOAT64SLICE: | |
| return formatFloat64SliceValue(v.slice) | |
| case STRING: | |
| return v.stringly | |
| case STRINGSLICE: | |
| return formatStringSliceValue(v.slice) | |
| case BYTESLICE: | |
| return formatByteSlice(v.stringly) | |
| case SLICE: | |
| return formatValueSliceValue(v.slice) | |
| case MAP: | |
| return formatMapValue(v.slice) | |
| case EMPTY: | |
| return "" | |
| default: | |
| return "unknown" | |
| } | |
| } | |
| // Emit returns a string representation of Value's data. | |
| // | |
| // Deprecated: Use [Value.String] instead. | |
| func (v Value) Emit() string { | |
| switch v.Type() { | |
| case BOOLSLICE: | |
| return fmt.Sprint(v.asBoolSlice()) | |
| case BOOL: | |
| return strconv.FormatBool(v.AsBool()) | |
| case INT64SLICE: | |
| j, err := json.Marshal(v.asInt64Slice()) | |
| if err != nil { | |
| return fmt.Sprintf("invalid: %v", v.asInt64Slice()) | |
| } | |
| return string(j) | |
| case INT64: | |
| return strconv.FormatInt(v.AsInt64(), 10) | |
| case FLOAT64SLICE: | |
| j, err := json.Marshal(v.asFloat64Slice()) | |
| if err != nil { | |
| return fmt.Sprintf("invalid: %v", v.asFloat64Slice()) | |
| } | |
| return string(j) | |
| case FLOAT64: | |
| return fmt.Sprint(v.AsFloat64()) | |
| case STRINGSLICE: | |
| j, err := json.Marshal(v.asStringSlice()) | |
| if err != nil { | |
| return fmt.Sprintf("invalid: %v", v.asStringSlice()) | |
| } | |
| return string(j) | |
| case STRING: | |
| return v.stringly | |
| case BYTESLICE: | |
| return formatByteSlice(v.stringly) | |
| case SLICE: | |
| return formatValueSliceValue(v.slice) | |
| case MAP: | |
| return formatMapValue(v.slice) | |
| case EMPTY: | |
| return "" | |
| default: | |
| return "unknown" | |
| } | |
| } | |
| const ( | |
| jsonArrayBracketsLen = len("[]") | |
| boolArrayElemMaxLen = len("false") | |
| int64ArrayElemMaxLen = len("-9223372036854775808") | |
| float64ArrayElemMaxLen = len("-1.7976931348623157e+308") | |
| commaLen = len(",") | |
| ) | |
| func sliceValue(v []Value) any { | |
| switch len(v) { | |
| case 0: | |
| return [0]Value{} | |
| case 1: | |
| return [1]Value{v[0]} | |
| case 2: | |
| return [2]Value{v[0], v[1]} | |
| case 3: | |
| return [3]Value{v[0], v[1], v[2]} | |
| case 4: | |
| return [4]Value{v[0], v[1], v[2], v[3]} | |
| case 5: | |
| return [5]Value{v[0], v[1], v[2], v[3], v[4]} | |
| default: | |
| return sliceValueReflect(v) | |
| } | |
| } | |
| func sliceValueReflect(v []Value) any { | |
| cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[Value]())).Elem() | |
| reflect.Copy(cp, reflect.ValueOf(v)) | |
| return cp.Interface() | |
| } | |
| func mapValue(v []KeyValue) any { | |
| switch len(v) { | |
| case 0: | |
| return [0]KeyValue{} | |
| case 1: | |
| return [1]KeyValue{v[0]} | |
| case 2: | |
| vals := [2]KeyValue{v[0], v[1]} | |
| sortKeyValues(vals[:]) | |
| return vals | |
| case 3: | |
| vals := [3]KeyValue{v[0], v[1], v[2]} | |
| sortKeyValues(vals[:]) | |
| return vals | |
| case 4: | |
| vals := [4]KeyValue{v[0], v[1], v[2], v[3]} | |
| sortKeyValues(vals[:]) | |
| return vals | |
| case 5: | |
| vals := [5]KeyValue{v[0], v[1], v[2], v[3], v[4]} | |
| sortKeyValues(vals[:]) | |
| return vals | |
| default: | |
| return mapValueReflect(v) | |
| } | |
| } | |
| func mapValueReflect(v []KeyValue) any { | |
| cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[KeyValue]())).Elem() | |
| reflect.Copy(cp, reflect.ValueOf(v)) | |
| vals := cp.Slice(0, len(v)).Interface().([]KeyValue) | |
| sortKeyValues(vals) | |
| return cp.Interface() | |
| } | |
| func sortKeyValues(vals []KeyValue) { | |
| slices.SortStableFunc(vals, func(a, b KeyValue) int { | |
| return cmp.Compare(a.Key, b.Key) | |
| }) | |
| } | |
| func formatBoolSliceValue(v any) string { | |
| switch vals := v.(type) { | |
| case [0]bool: | |
| return "[]" | |
| case [1]bool: | |
| return formatBoolSlice(vals[:]) | |
| case [2]bool: | |
| return formatBoolSlice(vals[:]) | |
| case [3]bool: | |
| return formatBoolSlice(vals[:]) | |
| default: | |
| return formatBoolSliceReflect(v) | |
| } | |
| } | |
| func formatBoolSlice(vals []bool) string { | |
| var b strings.Builder | |
| appendBoolSlice(&b, vals) | |
| return b.String() | |
| } | |
| func formatBoolSliceReflect(v any) string { | |
| var b strings.Builder | |
| appendBoolSliceReflect(&b, reflect.ValueOf(v)) | |
| return b.String() | |
| } | |
| func appendBoolSliceValue(dst *strings.Builder, v any) { | |
| switch vals := v.(type) { | |
| case [0]bool: | |
| _, _ = dst.WriteString("[]") | |
| case [1]bool: | |
| appendBoolSlice(dst, vals[:]) | |
| case [2]bool: | |
| appendBoolSlice(dst, vals[:]) | |
| case [3]bool: | |
| appendBoolSlice(dst, vals[:]) | |
| default: | |
| appendBoolSliceReflect(dst, reflect.ValueOf(v)) | |
| } | |
| } | |
| func appendBoolSlice(dst *strings.Builder, vals []bool) { | |
| dst.Grow(jsonArrayBracketsLen + len(vals)*(boolArrayElemMaxLen+commaLen)) | |
| _ = dst.WriteByte('[') | |
| for i, val := range vals { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| if val { | |
| _, _ = dst.WriteString("true") | |
| } else { | |
| _, _ = dst.WriteString("false") | |
| } | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func appendBoolSliceReflect(dst *strings.Builder, rv reflect.Value) { | |
| dst.Grow(jsonArrayBracketsLen + rv.Len()*(boolArrayElemMaxLen+commaLen)) | |
| _ = dst.WriteByte('[') | |
| for i := 0; i < rv.Len(); i++ { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| if rv.Index(i).Bool() { | |
| _, _ = dst.WriteString("true") | |
| } else { | |
| _, _ = dst.WriteString("false") | |
| } | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func formatInt64SliceValue(v any) string { | |
| switch vals := v.(type) { | |
| case [0]int64: | |
| return "[]" | |
| case [1]int64: | |
| return formatInt64Slice(vals[:]) | |
| case [2]int64: | |
| return formatInt64Slice(vals[:]) | |
| case [3]int64: | |
| return formatInt64Slice(vals[:]) | |
| default: | |
| return formatInt64SliceReflect(v) | |
| } | |
| } | |
| func formatInt64Slice(vals []int64) string { | |
| var b strings.Builder | |
| appendInt64Slice(&b, vals) | |
| return b.String() | |
| } | |
| func formatInt64SliceReflect(v any) string { | |
| var b strings.Builder | |
| appendInt64SliceReflect(&b, reflect.ValueOf(v)) | |
| return b.String() | |
| } | |
| func appendInt64SliceValue(dst *strings.Builder, v any) { | |
| switch vals := v.(type) { | |
| case [0]int64: | |
| _, _ = dst.WriteString("[]") | |
| case [1]int64: | |
| appendInt64Slice(dst, vals[:]) | |
| case [2]int64: | |
| appendInt64Slice(dst, vals[:]) | |
| case [3]int64: | |
| appendInt64Slice(dst, vals[:]) | |
| default: | |
| appendInt64SliceReflect(dst, reflect.ValueOf(v)) | |
| } | |
| } | |
| func appendInt64Slice(dst *strings.Builder, vals []int64) { | |
| dst.Grow(jsonArrayBracketsLen + len(vals)*(int64ArrayElemMaxLen+commaLen)) | |
| _ = dst.WriteByte('[') | |
| var buf [int64ArrayElemMaxLen]byte | |
| for i, val := range vals { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| out := strconv.AppendInt(buf[:0], val, 10) | |
| _, _ = dst.Write(out) | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func appendInt64SliceReflect(dst *strings.Builder, rv reflect.Value) { | |
| dst.Grow(jsonArrayBracketsLen + rv.Len()*(int64ArrayElemMaxLen+commaLen)) | |
| _ = dst.WriteByte('[') | |
| var scratch [int64ArrayElemMaxLen]byte | |
| for i := 0; i < rv.Len(); i++ { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| out := strconv.AppendInt(scratch[:0], rv.Index(i).Int(), 10) | |
| _, _ = dst.Write(out) | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func formatFloat64(v float64) string { | |
| switch { | |
| case math.IsNaN(v): | |
| return "NaN" | |
| case math.IsInf(v, 1): | |
| return "Infinity" | |
| case math.IsInf(v, -1): | |
| return "-Infinity" | |
| default: | |
| return strconv.FormatFloat(v, 'g', -1, 64) | |
| } | |
| } | |
| func formatFloat64SliceValue(v any) string { | |
| switch vals := v.(type) { | |
| case [0]float64: | |
| return "[]" | |
| case [1]float64: | |
| return formatFloat64Slice(vals[:]) | |
| case [2]float64: | |
| return formatFloat64Slice(vals[:]) | |
| case [3]float64: | |
| return formatFloat64Slice(vals[:]) | |
| default: | |
| return formatFloat64SliceReflect(v) | |
| } | |
| } | |
| func formatFloat64Slice(vals []float64) string { | |
| var b strings.Builder | |
| appendFloat64Slice(&b, vals) | |
| return b.String() | |
| } | |
| func formatFloat64SliceReflect(v any) string { | |
| var b strings.Builder | |
| appendFloat64SliceReflect(&b, reflect.ValueOf(v)) | |
| return b.String() | |
| } | |
| func appendFloat64SliceValue(dst *strings.Builder, v any) { | |
| switch vals := v.(type) { | |
| case [0]float64: | |
| _, _ = dst.WriteString("[]") | |
| case [1]float64: | |
| appendFloat64Slice(dst, vals[:]) | |
| case [2]float64: | |
| appendFloat64Slice(dst, vals[:]) | |
| case [3]float64: | |
| appendFloat64Slice(dst, vals[:]) | |
| default: | |
| appendFloat64SliceReflect(dst, reflect.ValueOf(v)) | |
| } | |
| } | |
| func appendFloat64Slice(dst *strings.Builder, vals []float64) { | |
| dst.Grow(jsonArrayBracketsLen + len(vals)*(float64ArrayElemMaxLen+commaLen)) | |
| _ = dst.WriteByte('[') | |
| var buf [float64ArrayElemMaxLen]byte | |
| for i, val := range vals { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| switch { | |
| case math.IsNaN(val): | |
| _, _ = dst.WriteString(`"NaN"`) | |
| case math.IsInf(val, 1): | |
| _, _ = dst.WriteString(`"Infinity"`) | |
| case math.IsInf(val, -1): | |
| _, _ = dst.WriteString(`"-Infinity"`) | |
| default: | |
| out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64) | |
| _, _ = dst.Write(out) | |
| } | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func appendFloat64SliceReflect(dst *strings.Builder, rv reflect.Value) { | |
| dst.Grow(jsonArrayBracketsLen + rv.Len()*(float64ArrayElemMaxLen+commaLen)) | |
| _ = dst.WriteByte('[') | |
| var scratch [float64ArrayElemMaxLen]byte | |
| for i := 0; i < rv.Len(); i++ { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| val := rv.Index(i).Float() | |
| switch { | |
| case math.IsNaN(val): | |
| _, _ = dst.WriteString(`"NaN"`) | |
| case math.IsInf(val, 1): | |
| _, _ = dst.WriteString(`"Infinity"`) | |
| case math.IsInf(val, -1): | |
| _, _ = dst.WriteString(`"-Infinity"`) | |
| default: | |
| out := strconv.AppendFloat(scratch[:0], val, 'g', -1, 64) | |
| _, _ = dst.Write(out) | |
| } | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func formatStringSliceValue(v any) string { | |
| switch vals := v.(type) { | |
| case [0]string: | |
| return "[]" | |
| case [1]string: | |
| return formatStringSlice(vals[:]) | |
| case [2]string: | |
| return formatStringSlice(vals[:]) | |
| case [3]string: | |
| return formatStringSlice(vals[:]) | |
| default: | |
| return formatStringSliceReflect(v) | |
| } | |
| } | |
| func formatStringSlice(vals []string) string { | |
| var b strings.Builder | |
| appendStringSlice(&b, vals) | |
| return b.String() | |
| } | |
| func formatStringSliceReflect(v any) string { | |
| var b strings.Builder | |
| appendStringSliceReflect(&b, reflect.ValueOf(v)) | |
| return b.String() | |
| } | |
| func appendStringSliceValue(dst *strings.Builder, v any) { | |
| switch vals := v.(type) { | |
| case [0]string: | |
| _, _ = dst.WriteString("[]") | |
| case [1]string: | |
| appendStringSlice(dst, vals[:]) | |
| case [2]string: | |
| appendStringSlice(dst, vals[:]) | |
| case [3]string: | |
| appendStringSlice(dst, vals[:]) | |
| default: | |
| appendStringSliceReflect(dst, reflect.ValueOf(v)) | |
| } | |
| } | |
| func appendStringSlice(dst *strings.Builder, vals []string) { | |
| size := jsonArrayBracketsLen | |
| for _, val := range vals { | |
| size += len(val) + commaLen + 2 // Account for JSON string quotes and comma. | |
| } | |
| dst.Grow(size) | |
| _ = dst.WriteByte('[') | |
| for i, val := range vals { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| appendJSONString(dst, val) | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func appendStringSliceReflect(dst *strings.Builder, rv reflect.Value) { | |
| size := jsonArrayBracketsLen | |
| for i := 0; i < rv.Len(); i++ { | |
| size += len(rv.Index(i).String()) + commaLen + 2 // Account for JSON string quotes and comma. | |
| } | |
| dst.Grow(size) | |
| _ = dst.WriteByte('[') | |
| for i := 0; i < rv.Len(); i++ { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| appendJSONString(dst, rv.Index(i).String()) | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func formatByteSlice(v string) string { | |
| var b strings.Builder | |
| appendBase64(&b, v) | |
| return b.String() | |
| } | |
| func formatValueSliceValue(v any) string { | |
| switch vals := v.(type) { | |
| case [0]Value: | |
| return "[]" | |
| case [1]Value: | |
| return formatValueSlice(vals[:]) | |
| case [2]Value: | |
| return formatValueSlice(vals[:]) | |
| case [3]Value: | |
| return formatValueSlice(vals[:]) | |
| case [4]Value: | |
| return formatValueSlice(vals[:]) | |
| case [5]Value: | |
| return formatValueSlice(vals[:]) | |
| default: | |
| return formatValueSliceReflect(v) | |
| } | |
| } | |
| func formatValueSlice(vals []Value) string { | |
| var b strings.Builder | |
| appendValueSlice(&b, vals) | |
| return b.String() | |
| } | |
| func formatValueSliceReflect(v any) string { | |
| var b strings.Builder | |
| appendValueSliceReflect(&b, reflect.ValueOf(v)) | |
| return b.String() | |
| } | |
| func formatMapValue(v any) string { | |
| switch vals := v.(type) { | |
| case [0]KeyValue: | |
| return "{}" | |
| case [1]KeyValue: | |
| return formatMap(vals[:]) | |
| case [2]KeyValue: | |
| return formatMap(vals[:]) | |
| case [3]KeyValue: | |
| return formatMap(vals[:]) | |
| case [4]KeyValue: | |
| return formatMap(vals[:]) | |
| case [5]KeyValue: | |
| return formatMap(vals[:]) | |
| default: | |
| return formatMapReflect(v) | |
| } | |
| } | |
| func formatMap(vals []KeyValue) string { | |
| var b strings.Builder | |
| appendMap(&b, vals) | |
| return b.String() | |
| } | |
| func formatMapReflect(v any) string { | |
| var b strings.Builder | |
| appendMapReflect(&b, reflect.ValueOf(v)) | |
| return b.String() | |
| } | |
| func appendValueSliceValue(dst *strings.Builder, v any) { | |
| switch vals := v.(type) { | |
| case [0]Value: | |
| _, _ = dst.WriteString("[]") | |
| case [1]Value: | |
| appendValueSlice(dst, vals[:]) | |
| case [2]Value: | |
| appendValueSlice(dst, vals[:]) | |
| case [3]Value: | |
| appendValueSlice(dst, vals[:]) | |
| case [4]Value: | |
| appendValueSlice(dst, vals[:]) | |
| case [5]Value: | |
| appendValueSlice(dst, vals[:]) | |
| default: | |
| appendValueSliceReflect(dst, reflect.ValueOf(v)) | |
| } | |
| } | |
| func appendValueSlice(dst *strings.Builder, vals []Value) { | |
| // Estimate 10 bytes per value for small values and commas. | |
| dst.Grow(jsonArrayBracketsLen + len(vals)*commaLen + len(vals)*10) | |
| _ = dst.WriteByte('[') | |
| for i, val := range vals { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| appendJSONValue(dst, val) | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func appendValueSliceReflect(dst *strings.Builder, rv reflect.Value) { | |
| // Estimate 10 bytes per value for small values and commas. | |
| dst.Grow(jsonArrayBracketsLen + rv.Len()*commaLen + rv.Len()*10) | |
| _ = dst.WriteByte('[') | |
| for i := 0; i < rv.Len(); i++ { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| appendJSONValue(dst, rv.Index(i).Interface().(Value)) | |
| } | |
| _ = dst.WriteByte(']') | |
| } | |
| func appendMapValue(dst *strings.Builder, v any) { | |
| switch vals := v.(type) { | |
| case [0]KeyValue: | |
| _, _ = dst.WriteString("{}") | |
| case [1]KeyValue: | |
| appendMap(dst, vals[:]) | |
| case [2]KeyValue: | |
| appendMap(dst, vals[:]) | |
| case [3]KeyValue: | |
| appendMap(dst, vals[:]) | |
| case [4]KeyValue: | |
| appendMap(dst, vals[:]) | |
| case [5]KeyValue: | |
| appendMap(dst, vals[:]) | |
| default: | |
| appendMapReflect(dst, reflect.ValueOf(v)) | |
| } | |
| } | |
| func appendMap(dst *strings.Builder, vals []KeyValue) { | |
| // Estimate 32 bytes per value for small values, plus key quotes, colon, | |
| // and commas. Escaped keys and larger values grow the builder as needed. | |
| size := len("{}") + len(vals)*commaLen + len(vals)*32 | |
| for _, val := range vals { | |
| size += len(val.Key) + len(`"":`) | |
| } | |
| dst.Grow(size) | |
| _ = dst.WriteByte('{') | |
| for i, val := range vals { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| appendJSONString(dst, string(val.Key)) | |
| _ = dst.WriteByte(':') | |
| appendJSONValue(dst, val.Value) | |
| } | |
| _ = dst.WriteByte('}') | |
| } | |
| func appendMapReflect(dst *strings.Builder, rv reflect.Value) { | |
| // Estimate 32 bytes per value for small values, plus key quotes, colon, | |
| // and commas. Escaped keys and larger values grow the builder as needed. | |
| size := len("{}") + rv.Len()*commaLen + rv.Len()*32 | |
| for i := 0; i < rv.Len(); i++ { | |
| size += len(rv.Index(i).Field(0).String()) + len(`"":`) | |
| } | |
| dst.Grow(size) | |
| _ = dst.WriteByte('{') | |
| for i := 0; i < rv.Len(); i++ { | |
| if i > 0 { | |
| _ = dst.WriteByte(',') | |
| } | |
| val := rv.Index(i).Interface().(KeyValue) | |
| appendJSONString(dst, string(val.Key)) | |
| _ = dst.WriteByte(':') | |
| appendJSONValue(dst, val.Value) | |
| } | |
| _ = dst.WriteByte('}') | |
| } | |
| func appendJSONValue(dst *strings.Builder, v Value) { | |
| switch v.Type() { | |
| case BOOL: | |
| if v.AsBool() { | |
| _, _ = dst.WriteString("true") | |
| } else { | |
| _, _ = dst.WriteString("false") | |
| } | |
| case BOOLSLICE: | |
| appendBoolSliceValue(dst, v.slice) | |
| case INT64: | |
| var buf [int64ArrayElemMaxLen]byte | |
| out := strconv.AppendInt(buf[:0], v.AsInt64(), 10) | |
| _, _ = dst.Write(out) | |
| case INT64SLICE: | |
| appendInt64SliceValue(dst, v.slice) | |
| case FLOAT64: | |
| val := v.AsFloat64() | |
| switch { | |
| case math.IsNaN(val): | |
| appendJSONString(dst, "NaN") | |
| case math.IsInf(val, 1): | |
| appendJSONString(dst, "Infinity") | |
| case math.IsInf(val, -1): | |
| appendJSONString(dst, "-Infinity") | |
| default: | |
| var buf [float64ArrayElemMaxLen]byte | |
| out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64) | |
| _, _ = dst.Write(out) | |
| } | |
| case FLOAT64SLICE: | |
| appendFloat64SliceValue(dst, v.slice) | |
| case STRING: | |
| appendJSONString(dst, v.stringly) | |
| case STRINGSLICE: | |
| appendStringSliceValue(dst, v.slice) | |
| case BYTESLICE: | |
| _ = dst.WriteByte('"') | |
| appendBase64(dst, v.stringly) | |
| _ = dst.WriteByte('"') | |
| case SLICE: | |
| appendValueSliceValue(dst, v.slice) | |
| case MAP: | |
| appendMapValue(dst, v.slice) | |
| case EMPTY: | |
| _, _ = dst.WriteString("null") | |
| default: | |
| appendJSONString(dst, "unknown") | |
| } | |
| } | |
| // appendJSONString appends s to dst as a JSON string literal. | |
| // | |
| // This is adapted from the Go standard library's encoding/json | |
| // [appendString implementation]. It keeps the same escaping behavior we need | |
| // here, but writes directly into a strings.Builder and intentionally does not | |
| // apply HTML escaping because the OpenTelemetry non-OTLP AnyValue representation | |
| // only requires JSON array string encoding. We inline this instead of using | |
| // encoding/json so slice formatting avoids allocations and reflection. | |
| // | |
| // [appendString implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/json/encode.go#L998-L1064 | |
| func appendJSONString(dst *strings.Builder, s string) { | |
| const hex = "0123456789abcdef" // For escaping bytes to hex. | |
| _ = dst.WriteByte('"') | |
| start := 0 | |
| for i := 0; i < len(s); { | |
| if c := s[i]; c < utf8.RuneSelf { | |
| if c >= 0x20 && c != '\\' && c != '"' { | |
| i++ | |
| continue | |
| } | |
| if start < i { | |
| _, _ = dst.WriteString(s[start:i]) | |
| } | |
| switch c { | |
| case '\\', '"': | |
| _ = dst.WriteByte('\\') | |
| _ = dst.WriteByte(c) | |
| case '\b': | |
| _, _ = dst.WriteString(`\b`) | |
| case '\f': | |
| _, _ = dst.WriteString(`\f`) | |
| case '\n': | |
| _, _ = dst.WriteString(`\n`) | |
| case '\r': | |
| _, _ = dst.WriteString(`\r`) | |
| case '\t': | |
| _, _ = dst.WriteString(`\t`) | |
| default: | |
| _, _ = dst.WriteString(`\u00`) | |
| _ = dst.WriteByte(hex[c>>4]) | |
| _ = dst.WriteByte(hex[c&0x0f]) | |
| } | |
| i++ | |
| start = i | |
| continue | |
| } | |
| r, size := utf8.DecodeRuneInString(s[i:]) | |
| if r == utf8.RuneError && size == 1 { | |
| if start < i { | |
| _, _ = dst.WriteString(s[start:i]) | |
| } | |
| // Match encoding/json by replacing invalid UTF-8 with U+FFFD. | |
| _, _ = dst.WriteString(`\ufffd`) | |
| i++ | |
| start = i | |
| continue | |
| } | |
| if r == '\u2028' || r == '\u2029' { | |
| if start < i { | |
| _, _ = dst.WriteString(s[start:i]) | |
| } | |
| // Escape JSONP-sensitive separators unconditionally, like encoding/json. | |
| _, _ = dst.WriteString(`\u202`) | |
| _ = dst.WriteByte(hex[r&0x0f]) | |
| i += size | |
| start = i | |
| continue | |
| } | |
| i += size | |
| } | |
| if start < len(s) { | |
| _, _ = dst.WriteString(s[start:]) | |
| } | |
| _ = dst.WriteByte('"') | |
| } | |
| // This is adapted from the Go standard library's encoding/base64 | |
| // [Encoding.Encode implementation]. It keeps the same encoding behavior we need | |
| // here, but writes directly into a strings.Builder. We inline this instead of using | |
| // encoding/base64 to avoid allocations. | |
| // | |
| // [Encoding.Encode implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/base64/base64.go#L139-L189 | |
| func appendBase64(dst *strings.Builder, s string) { | |
| const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
| dst.Grow(base64.StdEncoding.EncodedLen(len(s))) | |
| i := 0 | |
| for ; i+2 < len(s); i += 3 { | |
| n := uint32(s[i])<<16 | uint32(s[i+1])<<8 | uint32(s[i+2]) | |
| _ = dst.WriteByte(encode[n>>18&0x3f]) | |
| _ = dst.WriteByte(encode[n>>12&0x3f]) | |
| _ = dst.WriteByte(encode[n>>6&0x3f]) | |
| _ = dst.WriteByte(encode[n&0x3f]) | |
| } | |
| switch len(s) - i { | |
| case 1: | |
| n := uint32(s[i]) << 16 | |
| _ = dst.WriteByte(encode[n>>18&0x3f]) | |
| _ = dst.WriteByte(encode[n>>12&0x3f]) | |
| _ = dst.WriteByte('=') | |
| _ = dst.WriteByte('=') | |
| case 2: | |
| n := uint32(s[i])<<16 | uint32(s[i+1])<<8 | |
| _ = dst.WriteByte(encode[n>>18&0x3f]) | |
| _ = dst.WriteByte(encode[n>>12&0x3f]) | |
| _ = dst.WriteByte(encode[n>>6&0x3f]) | |
| _ = dst.WriteByte('=') | |
| } | |
| } | |
| // MarshalJSON returns the JSON encoding of the Value. | |
| func (v Value) MarshalJSON() ([]byte, error) { | |
| var jsonVal struct { | |
| Type string | |
| Value any | |
| } | |
| jsonVal.Type = v.Type().String() | |
| jsonVal.Value = v.AsInterface() | |
| return json.Marshal(jsonVal) | |
| } |