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
exporters: support SLICE attributes (#8216)
Fixes #8162 Follow-up to #8153 for `attribute.SLICE`. Add end-to-end exporter handling for `attribute.SLICE` in the remaining paths that still treated it as invalid or relied on fallback formatting. Changes: - encode `attribute.SLICE` as OTLP `AnyValue_ArrayValue` for trace, log, and metric transforms - serialize Zipkin `SLICE` attributes using the non-OTLP AnyValue string representation - add trace-side coverage for recursive `convAttrValue` slice conversion
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
@@ -26,8 +28,13 @@ var (
|
||||
attrFloat64Slice = attribute.Float64Slice("float64 slice", []float64{-1, 1})
|
||||
attrString = attribute.String("string", "o")
|
||||
attrBytes = attribute.ByteSlice("bytes", []byte("otlp"))
|
||||
attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"})
|
||||
attrEmpty = attribute.KeyValue{
|
||||
attrSlice = attribute.Slice("slice",
|
||||
attribute.BoolValue(true),
|
||||
attribute.ByteSliceValue([]byte("otlp")),
|
||||
attribute.SliceValue(attribute.IntValue(2), attribute.Value{}),
|
||||
)
|
||||
attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"})
|
||||
attrEmpty = attribute.KeyValue{
|
||||
Key: attribute.Key("empty"),
|
||||
Value: attribute.Value{},
|
||||
}
|
||||
@@ -40,6 +47,7 @@ var (
|
||||
},
|
||||
}}
|
||||
valIntOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: 1}}
|
||||
valIntTwo = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: 2}}
|
||||
valIntNOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: -1}}
|
||||
valIntSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -53,8 +61,21 @@ var (
|
||||
Values: []*cpb.AnyValue{valDblNOne, valDblOne},
|
||||
},
|
||||
}}
|
||||
valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}}
|
||||
valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}}
|
||||
valAttrBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}}
|
||||
valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: []*cpb.AnyValue{
|
||||
valBoolTrue,
|
||||
valAttrBytes,
|
||||
{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: []*cpb.AnyValue{valIntTwo, {}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}}
|
||||
valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}}
|
||||
valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -72,6 +93,7 @@ var (
|
||||
kvFloat64Slice = &cpb.KeyValue{Key: "float64 slice", Value: valDblSlice}
|
||||
kvString = &cpb.KeyValue{Key: "string", Value: valStrO}
|
||||
kvAttrBytes = &cpb.KeyValue{Key: "bytes", Value: valAttrBytes}
|
||||
kvAttrSlice = &cpb.KeyValue{Key: "slice", Value: valSlice}
|
||||
kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice}
|
||||
kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}}
|
||||
)
|
||||
@@ -141,6 +163,11 @@ func TestAttrTransforms(t *testing.T) {
|
||||
[]attribute.KeyValue{attrBytes},
|
||||
[]*cpb.KeyValue{kvAttrBytes},
|
||||
},
|
||||
{
|
||||
"slice",
|
||||
[]attribute.KeyValue{attrSlice},
|
||||
[]*cpb.KeyValue{kvAttrSlice},
|
||||
},
|
||||
{
|
||||
"string slice",
|
||||
[]attribute.KeyValue{attrStringSlice},
|
||||
@@ -159,6 +186,7 @@ func TestAttrTransforms(t *testing.T) {
|
||||
attrFloat64Slice,
|
||||
attrString,
|
||||
attrBytes,
|
||||
attrSlice,
|
||||
attrStringSlice,
|
||||
attrEmpty,
|
||||
},
|
||||
@@ -173,6 +201,7 @@ func TestAttrTransforms(t *testing.T) {
|
||||
kvFloat64Slice,
|
||||
kvString,
|
||||
kvAttrBytes,
|
||||
kvAttrSlice,
|
||||
kvStringSlice,
|
||||
kvEmpty,
|
||||
},
|
||||
@@ -180,12 +209,45 @@ func TestAttrTransforms(t *testing.T) {
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Run("Attrs", func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.want, Attrs(test.in))
|
||||
assertKeyValueSlicesEqual(t, test.want, Attrs(test.in))
|
||||
})
|
||||
t.Run("AttrIter", func(t *testing.T) {
|
||||
s := attribute.NewSet(test.in...)
|
||||
assert.ElementsMatch(t, test.want, AttrIter(s.Iter()))
|
||||
assertKeyValueSlicesEqual(t, test.want, AttrIter(s.Iter()))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttrsPreserveDuplicateKeys(t *testing.T) {
|
||||
want := []*cpb.KeyValue{
|
||||
{Key: "dup", Value: valBoolTrue},
|
||||
{Key: "dup", Value: valStrO},
|
||||
}
|
||||
|
||||
assertKeyValueSlicesEqual(t, want, Attrs([]attribute.KeyValue{
|
||||
attribute.Bool("dup", true),
|
||||
attribute.String("dup", "o"),
|
||||
}))
|
||||
}
|
||||
|
||||
func assertKeyValueSlicesEqual(t *testing.T, want, got []*cpb.KeyValue) {
|
||||
t.Helper()
|
||||
require.Len(t, got, len(want))
|
||||
|
||||
used := make([]bool, len(got))
|
||||
for i, wantKV := range want {
|
||||
matched := false
|
||||
for j, gotKV := range got {
|
||||
if used[j] {
|
||||
continue
|
||||
}
|
||||
if proto.Equal(wantKV, gotKV) {
|
||||
used[j] = true
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Truef(t, matched, "missing match for want[%d] = %#v in got = %#v", i, wantKV, got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,12 @@ func AttrValue(v attribute.Value) *cpb.AnyValue {
|
||||
av.Value = &cpb.AnyValue_BytesValue{
|
||||
BytesValue: v.AsByteSlice(),
|
||||
}
|
||||
case attribute.SLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: attrValues(v.AsSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -262,6 +268,14 @@ func stringSliceValues(vals []string) []*cpb.AnyValue {
|
||||
return converted
|
||||
}
|
||||
|
||||
func attrValues(vals []attribute.Value) []*cpb.AnyValue {
|
||||
converted := make([]*cpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = AttrValue(v)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
// LogAttrs transforms a slice of [api.KeyValue] into OTLP key-values.
|
||||
func LogAttrs(attrs []api.KeyValue) []*cpb.KeyValue {
|
||||
if len(attrs) == 0 {
|
||||
|
||||
@@ -9,8 +9,6 @@ package transform
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
@@ -138,7 +136,19 @@ func TestLogAttrs(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.want, LogAttrs(test.in))
|
||||
assertKeyValueSlicesEqual(t, test.want, LogAttrs(test.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAttrsPreserveDuplicateKeys(t *testing.T) {
|
||||
want := []*cpb.KeyValue{
|
||||
{Key: "dup", Value: valBoolTrue},
|
||||
{Key: "dup", Value: valStrO},
|
||||
}
|
||||
|
||||
assertKeyValueSlicesEqual(t, want, LogAttrs([]log.KeyValue{
|
||||
log.Bool("dup", true),
|
||||
log.String("dup", "o"),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -85,6 +85,12 @@ func Value(v attribute.Value) *cpb.AnyValue {
|
||||
av.Value = &cpb.AnyValue_BytesValue{
|
||||
BytesValue: v.AsByteSlice(),
|
||||
}
|
||||
case attribute.SLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: attrValues(v.AsSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -147,3 +153,11 @@ func stringSliceValues(vals []string) []*cpb.AnyValue {
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func attrValues(vals []attribute.Value) []*cpb.AnyValue {
|
||||
converted := make([]*cpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = Value(v)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
@@ -26,8 +28,13 @@ var (
|
||||
attrFloat64Slice = attribute.Float64Slice("float64 slice", []float64{-1, 1})
|
||||
attrString = attribute.String("string", "o")
|
||||
attrBytes = attribute.ByteSlice("bytes", []byte("otlp"))
|
||||
attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"})
|
||||
attrEmpty = attribute.KeyValue{
|
||||
attrSlice = attribute.Slice("slice",
|
||||
attribute.BoolValue(true),
|
||||
attribute.ByteSliceValue([]byte("otlp")),
|
||||
attribute.SliceValue(attribute.IntValue(2), attribute.Value{}),
|
||||
)
|
||||
attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"})
|
||||
attrEmpty = attribute.KeyValue{
|
||||
Key: attribute.Key("empty"),
|
||||
Value: attribute.Value{},
|
||||
}
|
||||
@@ -40,6 +47,7 @@ var (
|
||||
},
|
||||
}}
|
||||
valIntOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: 1}}
|
||||
valIntTwo = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: 2}}
|
||||
valIntNOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: -1}}
|
||||
valIntSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -53,8 +61,21 @@ var (
|
||||
Values: []*cpb.AnyValue{valDblNOne, valDblOne},
|
||||
},
|
||||
}}
|
||||
valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}}
|
||||
valBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}}
|
||||
valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}}
|
||||
valBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}}
|
||||
valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: []*cpb.AnyValue{
|
||||
valBoolTrue,
|
||||
valBytes,
|
||||
{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: []*cpb.AnyValue{valIntTwo, {}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}}
|
||||
valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}}
|
||||
valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -72,6 +93,7 @@ var (
|
||||
kvFloat64Slice = &cpb.KeyValue{Key: "float64 slice", Value: valDblSlice}
|
||||
kvString = &cpb.KeyValue{Key: "string", Value: valStrO}
|
||||
kvBytes = &cpb.KeyValue{Key: "bytes", Value: valBytes}
|
||||
kvSlice = &cpb.KeyValue{Key: "slice", Value: valSlice}
|
||||
kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice}
|
||||
kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}}
|
||||
)
|
||||
@@ -141,6 +163,11 @@ func TestAttributeTransforms(t *testing.T) {
|
||||
[]attribute.KeyValue{attrBytes},
|
||||
[]*cpb.KeyValue{kvBytes},
|
||||
},
|
||||
{
|
||||
"slice",
|
||||
[]attribute.KeyValue{attrSlice},
|
||||
[]*cpb.KeyValue{kvSlice},
|
||||
},
|
||||
{
|
||||
"string slice",
|
||||
[]attribute.KeyValue{attrStringSlice},
|
||||
@@ -159,6 +186,7 @@ func TestAttributeTransforms(t *testing.T) {
|
||||
attrFloat64Slice,
|
||||
attrString,
|
||||
attrBytes,
|
||||
attrSlice,
|
||||
attrStringSlice,
|
||||
attrEmpty,
|
||||
},
|
||||
@@ -173,6 +201,7 @@ func TestAttributeTransforms(t *testing.T) {
|
||||
kvFloat64Slice,
|
||||
kvString,
|
||||
kvBytes,
|
||||
kvSlice,
|
||||
kvStringSlice,
|
||||
kvEmpty,
|
||||
},
|
||||
@@ -180,12 +209,45 @@ func TestAttributeTransforms(t *testing.T) {
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Run("KeyValues", func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.want, KeyValues(test.in))
|
||||
assertKeyValueSlicesEqual(t, test.want, KeyValues(test.in))
|
||||
})
|
||||
t.Run("AttrIter", func(t *testing.T) {
|
||||
s := attribute.NewSet(test.in...)
|
||||
assert.ElementsMatch(t, test.want, AttrIter(s.Iter()))
|
||||
assertKeyValueSlicesEqual(t, test.want, AttrIter(s.Iter()))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyValuesPreserveDuplicateKeys(t *testing.T) {
|
||||
want := []*cpb.KeyValue{
|
||||
{Key: "dup", Value: valBoolTrue},
|
||||
{Key: "dup", Value: valStrO},
|
||||
}
|
||||
|
||||
assertKeyValueSlicesEqual(t, want, KeyValues([]attribute.KeyValue{
|
||||
attribute.Bool("dup", true),
|
||||
attribute.String("dup", "o"),
|
||||
}))
|
||||
}
|
||||
|
||||
func assertKeyValueSlicesEqual(t *testing.T, want, got []*cpb.KeyValue) {
|
||||
t.Helper()
|
||||
require.Len(t, got, len(want))
|
||||
|
||||
used := make([]bool, len(got))
|
||||
for i, wantKV := range want {
|
||||
matched := false
|
||||
for j, gotKV := range got {
|
||||
if used[j] {
|
||||
continue
|
||||
}
|
||||
if proto.Equal(wantKV, gotKV) {
|
||||
used[j] = true
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Truef(t, matched, "missing match for want[%d] = %#v in got = %#v", i, wantKV, got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user