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
attribute: add BYTESLICE type support (#7948)
Fixes #7933 Add BYTES type to https://pkg.go.dev/go.opentelemetry.io/otel/attribute - Introduces BYTES type and byte - Adds Bytes / BytesValue constructors - Implements hashing support - Adds base64 representation in Emit() - Adds test coverage for constructors, hashing, and set equality ``` $ go test -run=^$ -bench=BenchmarkByteSlice goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/attribute cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkByteSlice/Value-20 149529567 7.993 ns/op 0 B/op 0 allocs/op BenchmarkByteSlice/KeyValue-20 136973736 8.768 ns/op 0 B/op 0 allocs/op BenchmarkByteSlice/AsByteSlice-20 562915658 2.120 ns/op 0 B/op 0 allocs/op BenchmarkByteSlice/Emit-20 29149410 40.26 ns/op 16 B/op 1 allocs/op PASS ``` --------- Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> Co-authored-by: Robert Pająk <pellared@hotmail.com>
This commit is contained in:
@@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948)
|
||||||
|
|
||||||
<!-- Released section -->
|
<!-- Released section -->
|
||||||
<!-- Don't change this section unless doing release -->
|
<!-- Don't change this section unless doing release -->
|
||||||
|
|
||||||
|
|||||||
@@ -322,6 +322,34 @@ func BenchmarkStringSlice(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkByteSlice(b *testing.B) {
|
||||||
|
k, v := "bytes", []byte("forty-two")
|
||||||
|
kv := attribute.ByteSlice(k, v)
|
||||||
|
|
||||||
|
b.Run("Value", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
attribute.ByteSliceValue(v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("KeyValue", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
attribute.ByteSlice(k, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("AsByteSlice", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
kv.Value.AsByteSlice()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Emit", benchmarkEmit(kv))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkSetEquals(b *testing.B) {
|
func BenchmarkSetEquals(b *testing.B) {
|
||||||
b.Run("Empty", func(b *testing.B) {
|
b.Run("Empty", func(b *testing.B) {
|
||||||
benchmarkSetEquals(b, attribute.EmptySet())
|
benchmarkSetEquals(b, attribute.EmptySet())
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const (
|
|||||||
int64SliceID uint64 = 3762322556277578591 // "_[]int64" (little endian)
|
int64SliceID uint64 = 3762322556277578591 // "_[]int64" (little endian)
|
||||||
float64SliceID uint64 = 7308324551835016539 // "[]double" (little endian)
|
float64SliceID uint64 = 7308324551835016539 // "[]double" (little endian)
|
||||||
stringSliceID uint64 = 7453010373645655387 // "[]string" (little endian)
|
stringSliceID uint64 = 7453010373645655387 // "[]string" (little endian)
|
||||||
|
byteSliceID uint64 = 6874028470941080415 // "_[]byte_" (little endian)
|
||||||
emptyID uint64 = 7305809155345288421 // "__empty_" (little endian)
|
emptyID uint64 = 7305809155345288421 // "__empty_" (little endian)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,6 +82,9 @@ func hashKV(h xxhash.Hash, kv KeyValue) xxhash.Hash {
|
|||||||
for i := 0; i < rv.Len(); i++ {
|
for i := 0; i < rv.Len(); i++ {
|
||||||
h = h.String(rv.Index(i).String())
|
h = h.String(rv.Index(i).String())
|
||||||
}
|
}
|
||||||
|
case BYTESLICE:
|
||||||
|
h = h.Uint64(byteSliceID)
|
||||||
|
h = h.String(kv.Value.stringly)
|
||||||
case EMPTY:
|
case EMPTY:
|
||||||
h = h.Uint64(emptyID)
|
h = h.Uint64(emptyID)
|
||||||
default:
|
default:
|
||||||
|
|||||||
+10
-1
@@ -36,6 +36,8 @@ var keyVals = []func(string) KeyValue{
|
|||||||
func(k string) KeyValue { return String(k, "bar") },
|
func(k string) KeyValue { return String(k, "bar") },
|
||||||
func(k string) KeyValue { return StringSlice(k, []string{"foo", "bar", "baz"}) },
|
func(k string) KeyValue { return StringSlice(k, []string{"foo", "bar", "baz"}) },
|
||||||
func(k string) KeyValue { return StringSlice(k, []string{"[]i1"}) },
|
func(k string) KeyValue { return StringSlice(k, []string{"[]i1"}) },
|
||||||
|
func(k string) KeyValue { return ByteSlice(k, []byte("foo")) },
|
||||||
|
func(k string) KeyValue { return ByteSlice(k, []byte("[]i1")) },
|
||||||
func(k string) KeyValue { return KeyValue{Key: Key(k)} }, // Empty value.
|
func(k string) KeyValue { return KeyValue{Key: Key(k)} }, // Empty value.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +190,7 @@ func FuzzHashKVs(f *testing.F) {
|
|||||||
|
|
||||||
// Add slice types based on sliceType parameter
|
// Add slice types based on sliceType parameter
|
||||||
if numAttrs > 5 {
|
if numAttrs > 5 {
|
||||||
switch sliceType % 4 {
|
switch sliceType % 5 {
|
||||||
case 0:
|
case 0:
|
||||||
// Test BoolSlice with variable length.
|
// Test BoolSlice with variable length.
|
||||||
bools := make([]bool, len(s)%5) // 0-4 elements
|
bools := make([]bool, len(s)%5) // 0-4 elements
|
||||||
@@ -226,6 +228,13 @@ func FuzzHashKVs(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
kvs = append(kvs, Float64Slice("float64slice", float64s))
|
kvs = append(kvs, Float64Slice("float64slice", float64s))
|
||||||
|
case 4:
|
||||||
|
// Test ByteSlice with variable length.
|
||||||
|
bytes := make([]byte, len(s)%5)
|
||||||
|
for i := range bytes {
|
||||||
|
bytes[i] = byte(i + len(k1))
|
||||||
|
}
|
||||||
|
kvs = append(kvs, ByteSlice("bytes", bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,17 @@ func (k Key) StringSlice(v []string) KeyValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByteSlice creates a KeyValue instance with a BYTESLICE Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- ByteSlice(name, value).
|
||||||
|
func (k Key) ByteSlice(v []byte) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: ByteSliceValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Defined reports whether the key is not empty.
|
// Defined reports whether the key is not empty.
|
||||||
func (k Key) Defined() bool {
|
func (k Key) Defined() bool {
|
||||||
return len(k) != 0
|
return len(k) != 0
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ func TestEmit(t *testing.T) {
|
|||||||
v: attribute.StringSliceValue([]string{"foo", "bar"}),
|
v: attribute.StringSliceValue([]string{"foo", "bar"}),
|
||||||
want: `["foo","bar"]`,
|
want: `["foo","bar"]`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: `test Key.Emit() can emit a string representing self.BYTESLICE`,
|
||||||
|
v: attribute.ByteSliceValue([]byte("foo")),
|
||||||
|
want: "Zm9v",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: `test Key.Emit() can emit a string representing self.EMPTY`,
|
name: `test Key.Emit() can emit a string representing self.EMPTY`,
|
||||||
v: attribute.Value{},
|
v: attribute.Value{},
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ func StringSlice(k string, v []string) KeyValue {
|
|||||||
return Key(k).StringSlice(v)
|
return Key(k).StringSlice(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByteSlice creates a KeyValue with a BYTESLICE Value type.
|
||||||
|
func ByteSlice(k string, v []byte) KeyValue {
|
||||||
|
return Key(k).ByteSlice(v)
|
||||||
|
}
|
||||||
|
|
||||||
// Stringer creates a new key-value pair with a passed name and a string
|
// Stringer creates a new key-value pair with a passed name and a string
|
||||||
// value generated by the passed Stringer interface.
|
// value generated by the passed Stringer interface.
|
||||||
func Stringer(k string, v fmt.Stringer) KeyValue {
|
func Stringer(k string, v fmt.Stringer) KeyValue {
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ func TestKeyValueConstructors(t *testing.T) {
|
|||||||
Value: attribute.IntValue(123),
|
Value: attribute.IntValue(123),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ByteSlice",
|
||||||
|
actual: attribute.ByteSlice("k1", []byte{123}),
|
||||||
|
expected: attribute.KeyValue{
|
||||||
|
Key: "k1",
|
||||||
|
Value: attribute.ByteSliceValue([]byte{123}),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tt {
|
for _, test := range tt {
|
||||||
@@ -114,6 +122,11 @@ func TestKeyValueValid(t *testing.T) {
|
|||||||
valid: true,
|
valid: true,
|
||||||
kv: attribute.String("string", ""),
|
kv: attribute.String("string", ""),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "non-empty key with BYTESLICE type Value should be valid",
|
||||||
|
valid: true,
|
||||||
|
kv: attribute.ByteSlice("bytes", []byte{}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -152,6 +165,10 @@ func TestIncorrectCast(t *testing.T) {
|
|||||||
name: "StringSlice",
|
name: "StringSlice",
|
||||||
val: attribute.BoolSliceValue([]bool{true}),
|
val: attribute.BoolSliceValue([]bool{true}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ByteSlice",
|
||||||
|
val: attribute.ByteSliceValue([]byte{123}),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Empty",
|
name: "Empty",
|
||||||
val: attribute.Value{},
|
val: attribute.Value{},
|
||||||
@@ -169,6 +186,7 @@ func TestIncorrectCast(t *testing.T) {
|
|||||||
tt.val.AsInterface()
|
tt.val.AsInterface()
|
||||||
tt.val.AsString()
|
tt.val.AsString()
|
||||||
tt.val.AsStringSlice()
|
tt.val.AsStringSlice()
|
||||||
|
tt.val.AsByteSlice()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ func _() {
|
|||||||
_ = x[INT64SLICE-6]
|
_ = x[INT64SLICE-6]
|
||||||
_ = x[FLOAT64SLICE-7]
|
_ = x[FLOAT64SLICE-7]
|
||||||
_ = x[STRINGSLICE-8]
|
_ = x[STRINGSLICE-8]
|
||||||
|
_ = x[BYTESLICE-9]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE"
|
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICEBYTESLICE"
|
||||||
|
|
||||||
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69}
|
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69, 78}
|
||||||
|
|
||||||
func (i Type) String() string {
|
func (i Type) String() string {
|
||||||
idx := int(i) - 0
|
idx := int(i) - 0
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -45,6 +46,8 @@ const (
|
|||||||
FLOAT64SLICE
|
FLOAT64SLICE
|
||||||
// STRINGSLICE is a slice of strings Type Value.
|
// STRINGSLICE is a slice of strings Type Value.
|
||||||
STRINGSLICE
|
STRINGSLICE
|
||||||
|
// BYTESLICE is a slice of bytes Type Value.
|
||||||
|
BYTESLICE
|
||||||
// INVALID is used for a Value with no value set.
|
// INVALID is used for a Value with no value set.
|
||||||
//
|
//
|
||||||
// Deprecated: Use EMPTY instead as an empty value is a valid value.
|
// Deprecated: Use EMPTY instead as an empty value is a valid value.
|
||||||
@@ -134,6 +137,14 @@ func StringSliceValue(v []string) Value {
|
|||||||
return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Type returns a type of the Value.
|
// Type returns a type of the Value.
|
||||||
func (v Value) Type() Type {
|
func (v Value) Type() Type {
|
||||||
return v.vtype
|
return v.vtype
|
||||||
@@ -215,6 +226,19 @@ func (v Value) asStringSlice() []string {
|
|||||||
return attribute.AsSlice[string](v.slice)
|
return attribute.AsSlice[string](v.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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{}
|
type unknownValueType struct{}
|
||||||
|
|
||||||
// AsInterface returns Value's data as any.
|
// AsInterface returns Value's data as any.
|
||||||
@@ -236,6 +260,8 @@ func (v Value) AsInterface() any {
|
|||||||
return v.stringly
|
return v.stringly
|
||||||
case STRINGSLICE:
|
case STRINGSLICE:
|
||||||
return v.asStringSlice()
|
return v.asStringSlice()
|
||||||
|
case BYTESLICE:
|
||||||
|
return v.asByteSlice()
|
||||||
case EMPTY:
|
case EMPTY:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -273,6 +299,8 @@ func (v Value) Emit() string {
|
|||||||
return string(j)
|
return string(j)
|
||||||
case STRING:
|
case STRING:
|
||||||
return v.stringly
|
return v.stringly
|
||||||
|
case BYTESLICE:
|
||||||
|
return base64.StdEncoding.EncodeToString(v.asByteSlice())
|
||||||
case EMPTY:
|
case EMPTY:
|
||||||
return ""
|
return ""
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ func TestValue(t *testing.T) {
|
|||||||
wantType: attribute.STRINGSLICE,
|
wantType: attribute.STRINGSLICE,
|
||||||
wantValue: []string{"forty-two", "negative three", "twelve"},
|
wantValue: []string{"forty-two", "negative three", "twelve"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Key.ByteSlice() correctly returns keys's internal []byte value",
|
||||||
|
value: k.ByteSlice([]byte("hello world")).Value,
|
||||||
|
wantType: attribute.BYTESLICE,
|
||||||
|
wantValue: []byte("hello world"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "empty value",
|
name: "empty value",
|
||||||
value: attribute.Value{},
|
value: attribute.Value{},
|
||||||
@@ -146,6 +152,10 @@ func TestEquivalence(t *testing.T) {
|
|||||||
attribute.StringSlice("StringSlice", []string{"one", "two", "three"}),
|
attribute.StringSlice("StringSlice", []string{"one", "two", "three"}),
|
||||||
attribute.StringSlice("StringSlice", []string{"one", "two", "three"}),
|
attribute.StringSlice("StringSlice", []string{"one", "two", "three"}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
attribute.ByteSlice("ByteSlice", []byte("one")),
|
||||||
|
attribute.ByteSlice("ByteSlice", []byte("one")),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
attribute.KeyValue{Key: "Empty"},
|
attribute.KeyValue{Key: "Empty"},
|
||||||
attribute.KeyValue{Key: "Empty"},
|
attribute.KeyValue{Key: "Empty"},
|
||||||
@@ -229,6 +239,10 @@ func TestNotEquivalence(t *testing.T) {
|
|||||||
attribute.Float64("Float64", 19.09),
|
attribute.Float64("Float64", 19.09),
|
||||||
attribute.Float64("Float64", 22.09),
|
attribute.Float64("Float64", 22.09),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
attribute.ByteSlice("ByteSlice", []byte("bytes value")),
|
||||||
|
attribute.ByteSlice("ByteSlice", []byte("another value")),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 3}),
|
attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 3}),
|
||||||
attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 5}),
|
attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 5}),
|
||||||
@@ -315,4 +329,9 @@ func TestAsSlice(t *testing.T) {
|
|||||||
kv = attribute.StringSlice("StringSlice", ss1)
|
kv = attribute.StringSlice("StringSlice", ss1)
|
||||||
ss2 := kv.Value.AsStringSlice()
|
ss2 := kv.Value.AsStringSlice()
|
||||||
assert.Equal(t, ss1, ss2)
|
assert.Equal(t, ss1, ss2)
|
||||||
|
|
||||||
|
b1 := []byte("one")
|
||||||
|
kv = attribute.ByteSlice("ByteSlice", b1)
|
||||||
|
b2 := kv.Value.AsByteSlice()
|
||||||
|
assert.Equal(t, b1, b2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,6 +428,9 @@ func ValueFromAttribute(value attribute.Value) Value {
|
|||||||
res = append(res, StringValue(v))
|
res = append(res, StringValue(v))
|
||||||
}
|
}
|
||||||
return SliceValue(res...)
|
return SliceValue(res...)
|
||||||
|
case attribute.BYTESLICE:
|
||||||
|
val := value.AsByteSlice()
|
||||||
|
return BytesValue(val)
|
||||||
}
|
}
|
||||||
// This code should never be reached
|
// This code should never be reached
|
||||||
// as log attributes are a superset of standard attributes.
|
// as log attributes are a superset of standard attributes.
|
||||||
|
|||||||
Reference in New Issue
Block a user