diff --git a/CHANGELOG.md b/CHANGELOG.md index 3932059c4..d4a2cbe71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add `Map` and `MapValue` functions for new `MAP` attribute type in `go.opentelemetry.io/otel/attribute`. (#8445) +- Support `MAP` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8453) +- Support `MAP` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8453) +- Support `MAP` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8453) +- Support `MAP` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8453) - Apply `AttributeValueLengthLimit` to `attribute.MAP` type attribute values in `go.opentelemetry.io/otel/sdk/trace`, recursively truncating contained values. (#8454) - Add `WithUnsafeAttributes` to `go.opentelemetry.io/otel/metric/x` as an experimental no-copy attribute option intended for future performance work. This is a work in progress. (#8251) diff --git a/exporters/otlp/otlplog/otlploggrpc/internal/transform/attr_test.go b/exporters/otlp/otlplog/otlploggrpc/internal/transform/attr_test.go index 209552ad8..9ecba833d 100644 --- a/exporters/otlp/otlplog/otlploggrpc/internal/transform/attr_test.go +++ b/exporters/otlp/otlplog/otlploggrpc/internal/transform/attr_test.go @@ -33,6 +33,18 @@ var ( attribute.ByteSliceValue([]byte("otlp")), attribute.SliceValue(attribute.IntValue(2), attribute.Value{}), ) + attrMap = attribute.Map("map", + attribute.String("string", "o"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("otlp")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + ) attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"}) attrEmpty = attribute.KeyValue{ Key: attribute.Key("empty"), @@ -61,7 +73,10 @@ var ( Values: []*cpb.AnyValue{valDblNOne, valDblOne}, }, }} - valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrValue = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{ + StringValue: "value", + }} valAttrBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}} valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -76,6 +91,37 @@ var ( }, }, }} + valAttrMap = &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "bytes", Value: valAttrBytes}, + {Key: "empty", Value: &cpb.AnyValue{}}, + {Key: "nested", Value: &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "ok", Value: valBoolTrue}, + }, + }, + }}}, + {Key: "number", Value: valIntTwo}, + {Key: "slice", Value: &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: []*cpb.AnyValue{ + valBoolTrue, + {Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "inner", Value: valStrValue}, + }, + }, + }}, + }, + }, + }}}, + {Key: "string", Value: valStrO}, + }, + }, + }} valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}} valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -94,6 +140,7 @@ var ( kvString = &cpb.KeyValue{Key: "string", Value: valStrO} kvAttrBytes = &cpb.KeyValue{Key: "bytes", Value: valAttrBytes} kvAttrSlice = &cpb.KeyValue{Key: "slice", Value: valSlice} + kvAttrMap = &cpb.KeyValue{Key: "map", Value: valAttrMap} kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice} kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}} ) @@ -168,6 +215,11 @@ func TestAttrTransforms(t *testing.T) { []attribute.KeyValue{attrSlice}, []*cpb.KeyValue{kvAttrSlice}, }, + { + "map", + []attribute.KeyValue{attrMap}, + []*cpb.KeyValue{kvAttrMap}, + }, { "string slice", []attribute.KeyValue{attrStringSlice}, @@ -187,6 +239,7 @@ func TestAttrTransforms(t *testing.T) { attrString, attrBytes, attrSlice, + attrMap, attrStringSlice, attrEmpty, }, @@ -202,6 +255,7 @@ func TestAttrTransforms(t *testing.T) { kvString, kvAttrBytes, kvAttrSlice, + kvAttrMap, kvStringSlice, kvEmpty, }, diff --git a/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go b/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go index aa6eb510b..f14692a03 100644 --- a/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go +++ b/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go @@ -205,6 +205,12 @@ func AttrValue(v attribute.Value) *cpb.AnyValue { Values: attrValues(v.AsSlice()), }, } + case attribute.MAP: + av.Value = &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: Attrs(v.AsMap()), + }, + } case attribute.STRINGSLICE: av.Value = &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ diff --git a/exporters/otlp/otlplog/otlploghttp/internal/transform/attr_test.go b/exporters/otlp/otlplog/otlploghttp/internal/transform/attr_test.go index 209552ad8..9ecba833d 100644 --- a/exporters/otlp/otlplog/otlploghttp/internal/transform/attr_test.go +++ b/exporters/otlp/otlplog/otlploghttp/internal/transform/attr_test.go @@ -33,6 +33,18 @@ var ( attribute.ByteSliceValue([]byte("otlp")), attribute.SliceValue(attribute.IntValue(2), attribute.Value{}), ) + attrMap = attribute.Map("map", + attribute.String("string", "o"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("otlp")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + ) attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"}) attrEmpty = attribute.KeyValue{ Key: attribute.Key("empty"), @@ -61,7 +73,10 @@ var ( Values: []*cpb.AnyValue{valDblNOne, valDblOne}, }, }} - valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrValue = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{ + StringValue: "value", + }} valAttrBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}} valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -76,6 +91,37 @@ var ( }, }, }} + valAttrMap = &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "bytes", Value: valAttrBytes}, + {Key: "empty", Value: &cpb.AnyValue{}}, + {Key: "nested", Value: &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "ok", Value: valBoolTrue}, + }, + }, + }}}, + {Key: "number", Value: valIntTwo}, + {Key: "slice", Value: &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: []*cpb.AnyValue{ + valBoolTrue, + {Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "inner", Value: valStrValue}, + }, + }, + }}, + }, + }, + }}}, + {Key: "string", Value: valStrO}, + }, + }, + }} valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}} valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -94,6 +140,7 @@ var ( kvString = &cpb.KeyValue{Key: "string", Value: valStrO} kvAttrBytes = &cpb.KeyValue{Key: "bytes", Value: valAttrBytes} kvAttrSlice = &cpb.KeyValue{Key: "slice", Value: valSlice} + kvAttrMap = &cpb.KeyValue{Key: "map", Value: valAttrMap} kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice} kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}} ) @@ -168,6 +215,11 @@ func TestAttrTransforms(t *testing.T) { []attribute.KeyValue{attrSlice}, []*cpb.KeyValue{kvAttrSlice}, }, + { + "map", + []attribute.KeyValue{attrMap}, + []*cpb.KeyValue{kvAttrMap}, + }, { "string slice", []attribute.KeyValue{attrStringSlice}, @@ -187,6 +239,7 @@ func TestAttrTransforms(t *testing.T) { attrString, attrBytes, attrSlice, + attrMap, attrStringSlice, attrEmpty, }, @@ -202,6 +255,7 @@ func TestAttrTransforms(t *testing.T) { kvString, kvAttrBytes, kvAttrSlice, + kvAttrMap, kvStringSlice, kvEmpty, }, diff --git a/exporters/otlp/otlplog/otlploghttp/internal/transform/log.go b/exporters/otlp/otlplog/otlploghttp/internal/transform/log.go index 9c1f45a66..9a605f993 100644 --- a/exporters/otlp/otlplog/otlploghttp/internal/transform/log.go +++ b/exporters/otlp/otlplog/otlploghttp/internal/transform/log.go @@ -205,6 +205,12 @@ func AttrValue(v attribute.Value) *cpb.AnyValue { Values: attrValues(v.AsSlice()), }, } + case attribute.MAP: + av.Value = &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: Attrs(v.AsMap()), + }, + } case attribute.STRINGSLICE: av.Value = &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute.go index 96420d421..331d65a53 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute.go @@ -91,6 +91,12 @@ func Value(v attribute.Value) *cpb.AnyValue { Values: attrValues(v.AsSlice()), }, } + case attribute.MAP: + av.Value = &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: KeyValues(v.AsMap()), + }, + } case attribute.STRINGSLICE: av.Value = &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute_test.go index ef81f1541..a9a80d927 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/attribute_test.go @@ -33,6 +33,18 @@ var ( attribute.ByteSliceValue([]byte("otlp")), attribute.SliceValue(attribute.IntValue(2), attribute.Value{}), ) + attrMap = attribute.Map("map", + attribute.String("string", "o"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("otlp")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + ) attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"}) attrEmpty = attribute.KeyValue{ Key: attribute.Key("empty"), @@ -61,7 +73,10 @@ var ( Values: []*cpb.AnyValue{valDblNOne, valDblOne}, }, }} - valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrValue = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{ + StringValue: "value", + }} valBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}} valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -76,6 +91,37 @@ var ( }, }, }} + valMap = &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "bytes", Value: valBytes}, + {Key: "empty", Value: &cpb.AnyValue{}}, + {Key: "nested", Value: &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "ok", Value: valBoolTrue}, + }, + }, + }}}, + {Key: "number", Value: valIntTwo}, + {Key: "slice", Value: &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: []*cpb.AnyValue{ + valBoolTrue, + {Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "inner", Value: valStrValue}, + }, + }, + }}, + }, + }, + }}}, + {Key: "string", Value: valStrO}, + }, + }, + }} valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}} valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -94,6 +140,7 @@ var ( kvString = &cpb.KeyValue{Key: "string", Value: valStrO} kvBytes = &cpb.KeyValue{Key: "bytes", Value: valBytes} kvSlice = &cpb.KeyValue{Key: "slice", Value: valSlice} + kvMap = &cpb.KeyValue{Key: "map", Value: valMap} kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice} kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}} ) @@ -168,6 +215,11 @@ func TestAttributeTransforms(t *testing.T) { []attribute.KeyValue{attrSlice}, []*cpb.KeyValue{kvSlice}, }, + { + "map", + []attribute.KeyValue{attrMap}, + []*cpb.KeyValue{kvMap}, + }, { "string slice", []attribute.KeyValue{attrStringSlice}, @@ -187,6 +239,7 @@ func TestAttributeTransforms(t *testing.T) { attrString, attrBytes, attrSlice, + attrMap, attrStringSlice, attrEmpty, }, @@ -202,6 +255,7 @@ func TestAttributeTransforms(t *testing.T) { kvString, kvBytes, kvSlice, + kvMap, kvStringSlice, kvEmpty, }, diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go index 6a91ff2af..1713fba4f 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go @@ -91,6 +91,12 @@ func Value(v attribute.Value) *cpb.AnyValue { Values: attrValues(v.AsSlice()), }, } + case attribute.MAP: + av.Value = &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: KeyValues(v.AsMap()), + }, + } case attribute.STRINGSLICE: av.Value = &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go index ef81f1541..a9a80d927 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go @@ -33,6 +33,18 @@ var ( attribute.ByteSliceValue([]byte("otlp")), attribute.SliceValue(attribute.IntValue(2), attribute.Value{}), ) + attrMap = attribute.Map("map", + attribute.String("string", "o"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("otlp")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + ) attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"}) attrEmpty = attribute.KeyValue{ Key: attribute.Key("empty"), @@ -61,7 +73,10 @@ var ( Values: []*cpb.AnyValue{valDblNOne, valDblOne}, }, }} - valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrValue = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{ + StringValue: "value", + }} valBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}} valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -76,6 +91,37 @@ var ( }, }, }} + valMap = &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "bytes", Value: valBytes}, + {Key: "empty", Value: &cpb.AnyValue{}}, + {Key: "nested", Value: &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "ok", Value: valBoolTrue}, + }, + }, + }}}, + {Key: "number", Value: valIntTwo}, + {Key: "slice", Value: &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: []*cpb.AnyValue{ + valBoolTrue, + {Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "inner", Value: valStrValue}, + }, + }, + }}, + }, + }, + }}}, + {Key: "string", Value: valStrO}, + }, + }, + }} valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}} valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -94,6 +140,7 @@ var ( kvString = &cpb.KeyValue{Key: "string", Value: valStrO} kvBytes = &cpb.KeyValue{Key: "bytes", Value: valBytes} kvSlice = &cpb.KeyValue{Key: "slice", Value: valSlice} + kvMap = &cpb.KeyValue{Key: "map", Value: valMap} kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice} kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}} ) @@ -168,6 +215,11 @@ func TestAttributeTransforms(t *testing.T) { []attribute.KeyValue{attrSlice}, []*cpb.KeyValue{kvSlice}, }, + { + "map", + []attribute.KeyValue{attrMap}, + []*cpb.KeyValue{kvMap}, + }, { "string slice", []attribute.KeyValue{attrStringSlice}, @@ -187,6 +239,7 @@ func TestAttributeTransforms(t *testing.T) { attrString, attrBytes, attrSlice, + attrMap, attrStringSlice, attrEmpty, }, @@ -202,6 +255,7 @@ func TestAttributeTransforms(t *testing.T) { kvString, kvBytes, kvSlice, + kvMap, kvStringSlice, kvEmpty, }, diff --git a/exporters/otlp/otlptrace/internal/tracetransform/attribute.go b/exporters/otlp/otlptrace/internal/tracetransform/attribute.go index 0d43a5dc5..4b0fcfb9a 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/attribute.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/attribute.go @@ -97,6 +97,12 @@ func Value(v attribute.Value) *commonpb.AnyValue { Values: values(v.AsSlice()), }, } + case attribute.MAP: + av.Value = &commonpb.AnyValue_KvlistValue{ + KvlistValue: &commonpb.KeyValueList{ + Values: KeyValues(v.AsMap()), + }, + } case attribute.STRINGSLICE: av.Value = &commonpb.AnyValue_ArrayValue{ ArrayValue: &commonpb.ArrayValue{ diff --git a/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go b/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go index 6df1dcf11..2a06b7d60 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go @@ -33,6 +33,19 @@ func TestAttributes(t *testing.T) { attribute.ByteSliceValue([]byte("bytes")), attribute.SliceValue(attribute.IntValue(2), attribute.Value{}), ), + attribute.Map( + "map to kvlist", + attribute.String("string", "string"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("bytes")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + ), attribute.Bool("bool to bool", true), {Key: "empty to empty"}, }, @@ -112,6 +125,96 @@ func TestAttributes(t *testing.T) { }, }, }, + { + Key: "map to kvlist", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_KvlistValue{ + KvlistValue: &commonpb.KeyValueList{ + Values: []*commonpb.KeyValue{ + { + Key: "bytes", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_BytesValue{ + BytesValue: []byte("bytes"), + }, + }, + }, + { + Key: "empty", + Value: &commonpb.AnyValue{}, + }, + { + Key: "nested", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_KvlistValue{ + KvlistValue: &commonpb.KeyValueList{ + Values: []*commonpb.KeyValue{ + { + Key: "ok", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_BoolValue{ + BoolValue: true, + }, + }, + }, + }, + }, + }, + }, + }, + { + Key: "number", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_IntValue{ + IntValue: 2, + }, + }, + }, + { + Key: "slice", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: []*commonpb.AnyValue{ + { + Value: &commonpb.AnyValue_BoolValue{ + BoolValue: true, + }, + }, + { + Value: &commonpb.AnyValue_KvlistValue{ + KvlistValue: &commonpb.KeyValueList{ + Values: []*commonpb.KeyValue{ + { + Key: "inner", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_StringValue{ + StringValue: "value", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + Key: "string", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_StringValue{ + StringValue: "string", + }, + }, + }, + }, + }, + }, + }, + }, { Key: "bool to bool", Value: &commonpb.AnyValue{ diff --git a/exporters/zipkin/model.go b/exporters/zipkin/model.go index 8cbce57a7..893112470 100644 --- a/exporters/zipkin/model.go +++ b/exporters/zipkin/model.go @@ -182,10 +182,10 @@ func attributeToStringPair(kv attribute.KeyValue) (string, string) { } encoded, _ := json.Marshal(data) return string(kv.Key), string(encoded) - case attribute.SLICE: + case attribute.SLICE, attribute.MAP: // Note that this is a best effort support as this exporter is already deprecated. // Yet, we want to preserve other existing behavior as much as possible. - // Emit the slice using the non-OTLP AnyValue string representation. + // Emit the value using the non-OTLP AnyValue string representation. // Some values will be emitted differently than the specific types above. return string(kv.Key), kv.Value.String() default: diff --git a/exporters/zipkin/model_test.go b/exporters/zipkin/model_test.go index e5db2c302..66712c625 100644 --- a/exporters/zipkin/model_test.go +++ b/exporters/zipkin/model_test.go @@ -1003,6 +1003,29 @@ func TestAttributeToStringPairSlice(t *testing.T) { assert.Equal(t, `[true,"Ymlu",[2,null]]`, v) } +func TestAttributeToStringPairMap(t *testing.T) { + k, v := attributeToStringPair(attribute.Map( + "map", + attribute.String("string", "o"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("bin")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + )) + + assert.Equal(t, "map", k) + assert.JSONEq( + t, + `{"bytes":"Ymlu","empty":null,"nested":{"ok":true},"number":2,"slice":[true,{"inner":"value"}],"string":"o"}`, + v, + ) +} + func zkmodelIDPtr(n uint64) *zkmodel.ID { id := zkmodel.ID(n) return &id diff --git a/internal/shared/otlp/otlplog/transform/attr_test.go.tmpl b/internal/shared/otlp/otlplog/transform/attr_test.go.tmpl index 209552ad8..9ecba833d 100644 --- a/internal/shared/otlp/otlplog/transform/attr_test.go.tmpl +++ b/internal/shared/otlp/otlplog/transform/attr_test.go.tmpl @@ -33,6 +33,18 @@ var ( attribute.ByteSliceValue([]byte("otlp")), attribute.SliceValue(attribute.IntValue(2), attribute.Value{}), ) + attrMap = attribute.Map("map", + attribute.String("string", "o"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("otlp")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + ) attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"}) attrEmpty = attribute.KeyValue{ Key: attribute.Key("empty"), @@ -61,7 +73,10 @@ var ( Values: []*cpb.AnyValue{valDblNOne, valDblOne}, }, }} - valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrValue = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{ + StringValue: "value", + }} valAttrBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}} valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -76,6 +91,37 @@ var ( }, }, }} + valAttrMap = &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "bytes", Value: valAttrBytes}, + {Key: "empty", Value: &cpb.AnyValue{}}, + {Key: "nested", Value: &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "ok", Value: valBoolTrue}, + }, + }, + }}}, + {Key: "number", Value: valIntTwo}, + {Key: "slice", Value: &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: []*cpb.AnyValue{ + valBoolTrue, + {Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "inner", Value: valStrValue}, + }, + }, + }}, + }, + }, + }}}, + {Key: "string", Value: valStrO}, + }, + }, + }} valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}} valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -94,6 +140,7 @@ var ( kvString = &cpb.KeyValue{Key: "string", Value: valStrO} kvAttrBytes = &cpb.KeyValue{Key: "bytes", Value: valAttrBytes} kvAttrSlice = &cpb.KeyValue{Key: "slice", Value: valSlice} + kvAttrMap = &cpb.KeyValue{Key: "map", Value: valAttrMap} kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice} kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}} ) @@ -168,6 +215,11 @@ func TestAttrTransforms(t *testing.T) { []attribute.KeyValue{attrSlice}, []*cpb.KeyValue{kvAttrSlice}, }, + { + "map", + []attribute.KeyValue{attrMap}, + []*cpb.KeyValue{kvAttrMap}, + }, { "string slice", []attribute.KeyValue{attrStringSlice}, @@ -187,6 +239,7 @@ func TestAttrTransforms(t *testing.T) { attrString, attrBytes, attrSlice, + attrMap, attrStringSlice, attrEmpty, }, @@ -202,6 +255,7 @@ func TestAttrTransforms(t *testing.T) { kvString, kvAttrBytes, kvAttrSlice, + kvAttrMap, kvStringSlice, kvEmpty, }, diff --git a/internal/shared/otlp/otlplog/transform/log.go.tmpl b/internal/shared/otlp/otlplog/transform/log.go.tmpl index 9c1f45a66..9a605f993 100644 --- a/internal/shared/otlp/otlplog/transform/log.go.tmpl +++ b/internal/shared/otlp/otlplog/transform/log.go.tmpl @@ -205,6 +205,12 @@ func AttrValue(v attribute.Value) *cpb.AnyValue { Values: attrValues(v.AsSlice()), }, } + case attribute.MAP: + av.Value = &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: Attrs(v.AsMap()), + }, + } case attribute.STRINGSLICE: av.Value = &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ diff --git a/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl b/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl index 60cd8fe53..50c5dfd22 100644 --- a/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl @@ -91,6 +91,12 @@ func Value(v attribute.Value) *cpb.AnyValue { Values: attrValues(v.AsSlice()), }, } + case attribute.MAP: + av.Value = &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: KeyValues(v.AsMap()), + }, + } case attribute.STRINGSLICE: av.Value = &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ diff --git a/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl b/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl index ef81f1541..a9a80d927 100644 --- a/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl @@ -33,6 +33,18 @@ var ( attribute.ByteSliceValue([]byte("otlp")), attribute.SliceValue(attribute.IntValue(2), attribute.Value{}), ) + attrMap = attribute.Map("map", + attribute.String("string", "o"), + attribute.Int("number", 2), + attribute.ByteSlice("bytes", []byte("otlp")), + attribute.Slice( + "slice", + attribute.BoolValue(true), + attribute.MapValue(attribute.String("inner", "value")), + ), + attribute.Map("nested", attribute.Bool("ok", true)), + attribute.KeyValue{Key: "empty"}, + ) attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"}) attrEmpty = attribute.KeyValue{ Key: attribute.Key("empty"), @@ -61,7 +73,10 @@ var ( Values: []*cpb.AnyValue{valDblNOne, valDblOne}, }, }} - valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}} + valStrValue = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{ + StringValue: "value", + }} valBytes = &cpb.AnyValue{Value: &cpb.AnyValue_BytesValue{BytesValue: []byte("otlp")}} valSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -76,6 +91,37 @@ var ( }, }, }} + valMap = &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "bytes", Value: valBytes}, + {Key: "empty", Value: &cpb.AnyValue{}}, + {Key: "nested", Value: &cpb.AnyValue{Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "ok", Value: valBoolTrue}, + }, + }, + }}}, + {Key: "number", Value: valIntTwo}, + {Key: "slice", Value: &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: []*cpb.AnyValue{ + valBoolTrue, + {Value: &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: []*cpb.KeyValue{ + {Key: "inner", Value: valStrValue}, + }, + }, + }}, + }, + }, + }}}, + {Key: "string", Value: valStrO}, + }, + }, + }} valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}} valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{ ArrayValue: &cpb.ArrayValue{ @@ -94,6 +140,7 @@ var ( kvString = &cpb.KeyValue{Key: "string", Value: valStrO} kvBytes = &cpb.KeyValue{Key: "bytes", Value: valBytes} kvSlice = &cpb.KeyValue{Key: "slice", Value: valSlice} + kvMap = &cpb.KeyValue{Key: "map", Value: valMap} kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice} kvEmpty = &cpb.KeyValue{Key: "empty", Value: &cpb.AnyValue{}} ) @@ -168,6 +215,11 @@ func TestAttributeTransforms(t *testing.T) { []attribute.KeyValue{attrSlice}, []*cpb.KeyValue{kvSlice}, }, + { + "map", + []attribute.KeyValue{attrMap}, + []*cpb.KeyValue{kvMap}, + }, { "string slice", []attribute.KeyValue{attrStringSlice}, @@ -187,6 +239,7 @@ func TestAttributeTransforms(t *testing.T) { attrString, attrBytes, attrSlice, + attrMap, attrStringSlice, attrEmpty, }, @@ -202,6 +255,7 @@ func TestAttributeTransforms(t *testing.T) { kvString, kvBytes, kvSlice, + kvMap, kvStringSlice, kvEmpty, },