You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
d13f8ecb2d
Fixes https://github.com/open-telemetry/opentelemetry-go/issues/7934 ``` $ go test -run=^$ -bench=BenchmarkSlice goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/attribute cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkSlice/Len3/Value-20 25297926 52.56 ns/op 144 B/op 1 allocs/op BenchmarkSlice/Len3/KeyValue-20 21315132 55.97 ns/op 144 B/op 1 allocs/op BenchmarkSlice/Len3/AsSlice-20 24214248 50.03 ns/op 144 B/op 1 allocs/op BenchmarkSlice/Len3/String-20 14148270 86.48 ns/op 48 B/op 1 allocs/op BenchmarkSlice/Len3/Emit-20 13605388 85.18 ns/op 48 B/op 1 allocs/op BenchmarkSlice/Len5Nested/Value-20 16086171 71.30 ns/op 240 B/op 1 allocs/op BenchmarkSlice/Len5Nested/KeyValue-20 15547844 75.81 ns/op 240 B/op 1 allocs/op BenchmarkSlice/Len5Nested/AsSlice-20 17806996 66.16 ns/op 240 B/op 1 allocs/op BenchmarkSlice/Len5Nested/String-20 7409064 165.2 ns/op 64 B/op 1 allocs/op BenchmarkSlice/Len5Nested/Emit-20 7666302 161.0 ns/op 64 B/op 1 allocs/op PASS ok go.opentelemetry.io/otel/attribute 12.980s ``` ``` $ go test -run=^$ -bench=BenchmarkHashKVs goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/attribute cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkHashKVs-20 1268742 940.5 ns/op 0 B/op 0 allocs/op PASS ok go.opentelemetry.io/otel/attribute 1.198s ```
1035 lines
24 KiB
Go
1035 lines
24 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
attribute "go.opentelemetry.io/otel/attribute/internal"
|
|
)
|
|
|
|
//go:generate stringer -type=Type
|
|
|
|
// Type describes the type of the data Value holds.
|
|
type Type int // nolint: revive // redefines builtin Type.
|
|
|
|
// Value represents the value part 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 is used for a Value with no value set.
|
|
EMPTY Type = iota
|
|
// BOOL is a boolean Type Value.
|
|
BOOL
|
|
// INT64 is a 64-bit signed integral Type Value.
|
|
INT64
|
|
// FLOAT64 is a 64-bit floating point Type Value.
|
|
FLOAT64
|
|
// STRING is a string Type Value.
|
|
STRING
|
|
// BOOLSLICE is a slice of booleans Type Value.
|
|
BOOLSLICE
|
|
// INT64SLICE is a slice of 64-bit signed integral numbers Type Value.
|
|
INT64SLICE
|
|
// FLOAT64SLICE is a slice of 64-bit floating point numbers Type Value.
|
|
FLOAT64SLICE
|
|
// STRINGSLICE is a slice of strings Type Value.
|
|
STRINGSLICE
|
|
// BYTESLICE is a slice of bytes Type Value.
|
|
BYTESLICE
|
|
// SLICE is a slice of Value Type values.
|
|
SLICE
|
|
// 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 creates a BOOL Value.
|
|
func BoolValue(v bool) Value {
|
|
return Value{
|
|
vtype: BOOL,
|
|
numeric: boolToRaw(v),
|
|
}
|
|
}
|
|
|
|
// BoolSliceValue creates a BOOLSLICE Value.
|
|
func BoolSliceValue(v []bool) Value {
|
|
return Value{vtype: BOOLSLICE, slice: attribute.SliceValue(v)}
|
|
}
|
|
|
|
// IntValue creates an INT64 Value.
|
|
func IntValue(v int) Value {
|
|
return Int64Value(int64(v))
|
|
}
|
|
|
|
// IntSliceValue creates an INT64SLICE Value.
|
|
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 creates an INT64 Value.
|
|
func Int64Value(v int64) Value {
|
|
return Value{
|
|
vtype: INT64,
|
|
numeric: int64ToRaw(v),
|
|
}
|
|
}
|
|
|
|
// Int64SliceValue creates an INT64SLICE Value.
|
|
func Int64SliceValue(v []int64) Value {
|
|
return Value{vtype: INT64SLICE, slice: attribute.SliceValue(v)}
|
|
}
|
|
|
|
// Float64Value creates a FLOAT64 Value.
|
|
func Float64Value(v float64) Value {
|
|
return Value{
|
|
vtype: FLOAT64,
|
|
numeric: float64ToRaw(v),
|
|
}
|
|
}
|
|
|
|
// Float64SliceValue creates a FLOAT64SLICE Value.
|
|
func Float64SliceValue(v []float64) Value {
|
|
return Value{vtype: FLOAT64SLICE, slice: attribute.SliceValue(v)}
|
|
}
|
|
|
|
// StringValue creates a STRING Value.
|
|
func StringValue(v string) Value {
|
|
return Value{
|
|
vtype: STRING,
|
|
stringly: v,
|
|
}
|
|
}
|
|
|
|
// StringSliceValue creates a STRINGSLICE Value.
|
|
func StringSliceValue(v []string) Value {
|
|
return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
|
|
}
|
|
|
|
// ByteSliceValue creates a BYTESLICE Value.
|
|
func ByteSliceValue(v []byte) Value {
|
|
return Value{
|
|
vtype: BYTESLICE,
|
|
stringly: string(v),
|
|
}
|
|
}
|
|
|
|
// SliceValue creates a SLICE Value.
|
|
func SliceValue(v ...Value) Value {
|
|
return Value{vtype: SLICE, slice: sliceValue(v)}
|
|
}
|
|
|
|
// Type returns a type of the Value.
|
|
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
|
|
}
|
|
|
|
// 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 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, and slices are encoded as
|
|
// JSON arrays. String, byte, and special floating-point values inside arrays
|
|
// are encoded as JSON strings, and empty values inside arrays 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 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 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 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 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 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 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)
|
|
}
|