diff --git a/CHANGELOG.md b/CHANGELOG.md index f0e971d22dc..b5b402cf391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Added `ErrorHandlerFunc` to use a function as an `"go.opentelemetry.io/otel".ErrorHandler`. (#2149) +- Added typed slice attribute types and functionality to the `go.opentelemetry.io/otel/attribute` package to replace the existing array type and functions. (#2162) + - `BoolSlice`, `IntSlice`, `Int64Slice`, `Float64Slice`, and `StringSlice` replace the use of the `Array` function in the package. ### Changed @@ -21,6 +23,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `go.opentelemetry.io/otel/bridge/opencensus/utils` package is deprecated. All functionality from this package now exists in the `go.opentelemetry.io/otel/bridge/opencensus` package. The functions from that package should be used instead. (#2166) +- The `"go.opentelemetry.io/otel/attribute".Array` function and the related `ARRAY` value type is deprecated. + Use the typed `*Slice` functions and types added to the package instead. (#2162) ### Removed diff --git a/attribute/benchmark_test.go b/attribute/benchmark_test.go index 4a037d283f7..497f5df9346 100644 --- a/attribute/benchmark_test.go +++ b/attribute/benchmark_test.go @@ -26,10 +26,14 @@ var ( outV attribute.Value outKV attribute.KeyValue - outBool bool - outInt64 int64 - outFloat64 float64 - outStr string + outBool bool + outBoolSlice []bool + outInt64 int64 + outInt64Slice []int64 + outFloat64 float64 + outFloat64Slice []float64 + outStr string + outStrSlice []string ) func benchmarkAny(k string, v interface{}) func(*testing.B) { @@ -91,6 +95,32 @@ func BenchmarkBool(b *testing.B) { b.Run("Array", benchmarkArray(k, array)) } +func BenchmarkBoolSlice(b *testing.B) { + k, v := "bool slice", []bool{true, false, true} + kv := attribute.BoolSlice(k, v) + + b.Run("Value", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outV = attribute.BoolSliceValue(v) + } + }) + b.Run("KeyValue", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outKV = attribute.BoolSlice(k, v) + } + }) + b.Run("AsBoolSlice", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outBoolSlice = kv.Value.AsBoolSlice() + } + }) + b.Run("Any", benchmarkAny(k, v)) + b.Run("Emit", benchmarkEmit(kv)) +} + func BenchmarkInt(b *testing.B) { k, v := "int", int(42) kv := attribute.Int(k, v) @@ -113,6 +143,26 @@ func BenchmarkInt(b *testing.B) { b.Run("Array", benchmarkArray(k, array)) } +func BenchmarkIntSlice(b *testing.B) { + k, v := "int slice", []int{42, -3, 12} + kv := attribute.IntSlice(k, v) + + b.Run("Value", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outV = attribute.IntSliceValue(v) + } + }) + b.Run("KeyValue", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outKV = attribute.IntSlice(k, v) + } + }) + b.Run("Any", benchmarkAny(k, v)) + b.Run("Emit", benchmarkEmit(kv)) +} + func BenchmarkInt8(b *testing.B) { b.Run("Any", benchmarkAny("int8", int8(42))) } @@ -153,6 +203,32 @@ func BenchmarkInt64(b *testing.B) { b.Run("Array", benchmarkArray(k, array)) } +func BenchmarkInt64Slice(b *testing.B) { + k, v := "int64 slice", []int64{42, -3, 12} + kv := attribute.Int64Slice(k, v) + + b.Run("Value", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outV = attribute.Int64SliceValue(v) + } + }) + b.Run("KeyValue", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outKV = attribute.Int64Slice(k, v) + } + }) + b.Run("AsInt64Slice", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outInt64Slice = kv.Value.AsInt64Slice() + } + }) + b.Run("Any", benchmarkAny(k, v)) + b.Run("Emit", benchmarkEmit(kv)) +} + func BenchmarkFloat64(b *testing.B) { k, v := "float64", float64(42) kv := attribute.Float64(k, v) @@ -181,6 +257,32 @@ func BenchmarkFloat64(b *testing.B) { b.Run("Array", benchmarkArray(k, array)) } +func BenchmarkFloat64Slice(b *testing.B) { + k, v := "float64 slice", []float64{42, -3, 12} + kv := attribute.Float64Slice(k, v) + + b.Run("Value", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outV = attribute.Float64SliceValue(v) + } + }) + b.Run("KeyValue", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outKV = attribute.Float64Slice(k, v) + } + }) + b.Run("AsFloat64Slice", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outFloat64Slice = kv.Value.AsFloat64Slice() + } + }) + b.Run("Any", benchmarkAny(k, v)) + b.Run("Emit", benchmarkEmit(kv)) +} + func BenchmarkString(b *testing.B) { k, v := "string", "42" kv := attribute.String(k, v) @@ -209,12 +311,32 @@ func BenchmarkString(b *testing.B) { b.Run("Array", benchmarkArray(k, array)) } -func BenchmarkBytes(b *testing.B) { - b.Run("Any", benchmarkAny("bytes", []byte("bytes"))) -} +func BenchmarkStringSlice(b *testing.B) { + k, v := "float64 slice", []string{"forty-two", "negative three", "twelve"} + kv := attribute.StringSlice(k, v) -type test struct{} + b.Run("Value", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outV = attribute.StringSliceValue(v) + } + }) + b.Run("KeyValue", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outKV = attribute.StringSlice(k, v) + } + }) + b.Run("AsStringSlice", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + outStrSlice = kv.Value.AsStringSlice() + } + }) + b.Run("Any", benchmarkAny(k, v)) + b.Run("Emit", benchmarkEmit(kv)) +} -func BenchmarkStruct(b *testing.B) { - b.Run("Any", benchmarkAny("struct", test{})) +func BenchmarkBytes(b *testing.B) { + b.Run("Any", benchmarkAny("bytes", []byte("bytes"))) } diff --git a/attribute/key.go b/attribute/key.go index 0021d7092be..81877813bd7 100644 --- a/attribute/key.go +++ b/attribute/key.go @@ -29,6 +29,17 @@ func (k Key) Bool(v bool) KeyValue { } } +// BoolSlice creates a KeyValue instance with a BOOLSLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- BoolSlice(name, value). +func (k Key) BoolSlice(v []bool) KeyValue { + return KeyValue{ + Key: k, + Value: BoolSliceValue(v), + } +} + // Int creates a KeyValue instance with an INT64 Value. // // If creating both a key and value at the same time, use the provided @@ -40,6 +51,17 @@ func (k Key) Int(v int) KeyValue { } } +// IntSlice creates a KeyValue instance with an INT64SLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- IntSlice(name, value). +func (k Key) IntSlice(v []int) KeyValue { + return KeyValue{ + Key: k, + Value: IntSliceValue(v), + } +} + // Int64 creates a KeyValue instance with an INT64 Value. // // If creating both a key and value at the same time, use the provided @@ -51,6 +73,17 @@ func (k Key) Int64(v int64) KeyValue { } } +// Int64Slice creates a KeyValue instance with an INT64SLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Int64Slice(name, value). +func (k Key) Int64Slice(v []int64) KeyValue { + return KeyValue{ + Key: k, + Value: Int64SliceValue(v), + } +} + // Float64 creates a KeyValue instance with a FLOAT64 Value. // // If creating both a key and value at the same time, use the provided @@ -62,6 +95,17 @@ func (k Key) Float64(v float64) KeyValue { } } +// Float64Slice creates a KeyValue instance with a FLOAT64SLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Float64(name, value). +func (k Key) Float64Slice(v []float64) KeyValue { + return KeyValue{ + Key: k, + Value: Float64SliceValue(v), + } +} + // String creates a KeyValue instance with a STRING Value. // // If creating both a key and value at the same time, use the provided @@ -73,10 +117,23 @@ func (k Key) String(v string) KeyValue { } } +// StringSlice creates a KeyValue instance with a STRINGSLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- StringSlice(name, value). +func (k Key) StringSlice(v []string) KeyValue { + return KeyValue{ + Key: k, + Value: StringSliceValue(v), + } +} + // Array creates a KeyValue instance with an ARRAY Value. // // If creating both a key and value at the same time, use the provided // convenience function instead -- Array(name, value). +// +// Deprecated: Use the typed *Slice methods instead. func (k Key) Array(v interface{}) KeyValue { return KeyValue{ Key: k, diff --git a/attribute/kv.go b/attribute/kv.go index 7a2e0dddce7..a3b37b07a5e 100644 --- a/attribute/kv.go +++ b/attribute/kv.go @@ -36,26 +36,51 @@ func Bool(k string, v bool) KeyValue { return Key(k).Bool(v) } +// BoolSlice creates a KeyValue with a BOOLSLICE Value type. +func BoolSlice(k string, v []bool) KeyValue { + return Key(k).BoolSlice(v) +} + // Int creates a KeyValue with an INT64 Value type. func Int(k string, v int) KeyValue { return Key(k).Int(v) } -// Int64 creates a KeyValue with a INT64 Value type. +// IntSlice creates a KeyValue with an INT64SLICE Value type. +func IntSlice(k string, v []int) KeyValue { + return Key(k).IntSlice(v) +} + +// Int64 creates a KeyValue with an INT64 Value type. func Int64(k string, v int64) KeyValue { return Key(k).Int64(v) } +// Int64Slice creates a KeyValue with an INT64SLICE Value type. +func Int64Slice(k string, v []int64) KeyValue { + return Key(k).Int64Slice(v) +} + // Float64 creates a KeyValue with a FLOAT64 Value type. func Float64(k string, v float64) KeyValue { return Key(k).Float64(v) } +// Float64Slice creates a KeyValue with a FLOAT64SLICE Value type. +func Float64Slice(k string, v []float64) KeyValue { + return Key(k).Float64Slice(v) +} + // String creates a KeyValue with a STRING Value type. func String(k, v string) KeyValue { return Key(k).String(v) } +// StringSlice creates a KeyValue with a STRINGSLICE Value type. +func StringSlice(k string, v []string) KeyValue { + return Key(k).StringSlice(v) +} + // Stringer creates a new key-value pair with a passed name and a string // value generated by the passed Stringer interface. func Stringer(k string, v fmt.Stringer) KeyValue { @@ -64,6 +89,8 @@ func Stringer(k string, v fmt.Stringer) KeyValue { // Array creates a new key-value pair with a passed name and a array. // Only arrays of primitive type are supported. +// +// Deprecated: Use the typed *Slice functions instead. func Array(k string, v interface{}) KeyValue { return Key(k).Array(v) } @@ -82,8 +109,24 @@ func Any(k string, value interface{}) KeyValue { rv := reflect.ValueOf(value) switch rv.Kind() { - case reflect.Array, reflect.Slice: - return Array(k, value) + case reflect.Array: + rv = rv.Slice(0, rv.Len()) + fallthrough + case reflect.Slice: + switch reflect.TypeOf(value).Elem().Kind() { + case reflect.Bool: + return BoolSlice(k, rv.Interface().([]bool)) + case reflect.Int: + return IntSlice(k, rv.Interface().([]int)) + case reflect.Int64: + return Int64Slice(k, rv.Interface().([]int64)) + case reflect.Float64: + return Float64Slice(k, rv.Interface().([]float64)) + case reflect.String: + return StringSlice(k, rv.Interface().([]string)) + default: + return KeyValue{Key: Key(k), Value: Value{vtype: INVALID}} + } case reflect.Bool: return Bool(k, rv.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: diff --git a/attribute/type_string.go b/attribute/type_string.go index 1f2c7dccfa5..88ec502b61d 100644 --- a/attribute/type_string.go +++ b/attribute/type_string.go @@ -13,12 +13,16 @@ func _() { _ = x[INT64-2] _ = x[FLOAT64-3] _ = x[STRING-4] - _ = x[ARRAY-5] + _ = x[BOOLSLICE-5] + _ = x[INT64SLICE-6] + _ = x[FLOAT64SLICE-7] + _ = x[STRINGSLICE-8] + _ = x[ARRAY-9] } -const _Type_name = "INVALIDBOOLINT64FLOAT64STRINGARRAY" +const _Type_name = "INVALIDBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICEARRAY" -var _Type_index = [...]uint8{0, 7, 11, 16, 23, 29, 34} +var _Type_index = [...]uint8{0, 7, 11, 16, 23, 29, 38, 48, 60, 71, 76} func (i Type) String() string { if i < 0 || i >= Type(len(_Type_index)-1) { diff --git a/attribute/value.go b/attribute/value.go index 1118b6a48a8..24c81ea6582 100644 --- a/attribute/value.go +++ b/attribute/value.go @@ -33,9 +33,7 @@ type Value struct { vtype Type numeric uint64 stringly string - // TODO Lazy value type? - - array interface{} + slice interface{} } const ( @@ -49,9 +47,19 @@ const ( 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 // ARRAY is an array Type Value used to store 1-dimensional slices or // arrays of bool, int, int32, int64, uint, uint32, uint64, float, // float32, float64, or string types. + // + // Deprecated: Use slice types instead. ARRAY ) @@ -63,11 +71,33 @@ func BoolValue(v bool) Value { } } +// BoolSliceValue creates a BOOLSLICE Value. +func BoolSliceValue(v []bool) Value { + cp := make([]bool, len(v)) + copy(cp, v) + return Value{ + vtype: BOOLSLICE, + slice: cp, + } +} + // IntValue creates an INT64 Value. func IntValue(v int) Value { return Int64Value(int64(v)) } +// IntSliceValue creates an INTSLICE Value. +func IntSliceValue(v []int) Value { + cp := make([]int64, 0, len(v)) + for _, i := range v { + cp = append(cp, int64(i)) + } + return Value{ + vtype: INT64SLICE, + slice: cp, + } +} + // Int64Value creates an INT64 Value. func Int64Value(v int64) Value { return Value{ @@ -76,6 +106,16 @@ func Int64Value(v int64) Value { } } +// Int64SliceValue creates an INT64SLICE Value. +func Int64SliceValue(v []int64) Value { + cp := make([]int64, len(v)) + copy(cp, v) + return Value{ + vtype: INT64SLICE, + slice: cp, + } +} + // Float64Value creates a FLOAT64 Value. func Float64Value(v float64) Value { return Value{ @@ -84,6 +124,16 @@ func Float64Value(v float64) Value { } } +// Float64SliceValue creates a FLOAT64SLICE Value. +func Float64SliceValue(v []float64) Value { + cp := make([]float64, len(v)) + copy(cp, v) + return Value{ + vtype: FLOAT64SLICE, + slice: cp, + } +} + // StringValue creates a STRING Value. func StringValue(v string) Value { return Value{ @@ -92,11 +142,23 @@ func StringValue(v string) Value { } } +// StringSliceValue creates a STRINGSLICE Value. +func StringSliceValue(v []string) Value { + cp := make([]string, len(v)) + copy(cp, v) + return Value{ + vtype: STRINGSLICE, + slice: cp, + } +} + // ArrayValue creates an ARRAY value from an array or slice. // Only arrays or slices of bool, int, int64, float, float64, or string types are allowed. // Specifically, arrays and slices can not contain other arrays, slices, structs, or non-standard // types. If the passed value is not an array or slice of these types an // INVALID value is returned. +// +// Deprecated: Use the typed *SliceValue functions instead. func ArrayValue(v interface{}) Value { switch reflect.TypeOf(v).Kind() { case reflect.Array, reflect.Slice: @@ -112,7 +174,7 @@ func ArrayValue(v interface{}) Value { reflect.Copy(frozen, val) return Value{ vtype: ARRAY, - array: frozen.Interface(), + slice: frozen.Interface(), } default: return Value{vtype: INVALID} @@ -132,27 +194,65 @@ func (v Value) AsBool() bool { return internal.RawToBool(v.numeric) } +// AsBoolSlice returns the []bool value. Make sure that the Value's type is +// BOOLSLICE. +func (v Value) AsBoolSlice() []bool { + if s, ok := v.slice.([]bool); ok { + return s + } + return nil +} + // AsInt64 returns the int64 value. Make sure that the Value's type is // INT64. func (v Value) AsInt64() int64 { return internal.RawToInt64(v.numeric) } +// AsInt64Slice returns the []int64 value. Make sure that the Value's type is +// INT64SLICE. +func (v Value) AsInt64Slice() []int64 { + if s, ok := v.slice.([]int64); ok { + return s + } + return nil +} + // AsFloat64 returns the float64 value. Make sure that the Value's // type is FLOAT64. func (v Value) AsFloat64() float64 { return internal.RawToFloat64(v.numeric) } +// AsFloat64Slice returns the []float64 value. Make sure that the Value's type is +// INT64SLICE. +func (v Value) AsFloat64Slice() []float64 { + if s, ok := v.slice.([]float64); ok { + return s + } + return nil +} + // 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 +// INT64SLICE. +func (v Value) AsStringSlice() []string { + if s, ok := v.slice.([]string); ok { + return s + } + return nil +} + // AsArray returns the array Value as an interface{}. +// +// Deprecated: Use the typed As*Slice functions instead. func (v Value) AsArray() interface{} { - return v.array + return v.slice } type unknownValueType struct{} @@ -164,12 +264,20 @@ func (v Value) AsInterface() interface{} { return v.AsArray() 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() } return unknownValueType{} } @@ -177,8 +285,8 @@ func (v Value) AsInterface() interface{} { // Emit returns a string representation of Value's data. func (v Value) Emit() string { switch v.Type() { - case ARRAY: - return fmt.Sprint(v.array) + case ARRAY, BOOLSLICE, INT64SLICE, FLOAT64SLICE, STRINGSLICE: + return fmt.Sprint(v.slice) case BOOL: return strconv.FormatBool(v.AsBool()) case INT64: diff --git a/attribute/value_test.go b/attribute/value_test.go index 3fed8b7d3b8..38f916017be 100644 --- a/attribute/value_test.go +++ b/attribute/value_test.go @@ -25,7 +25,6 @@ import ( func TestValue(t *testing.T) { k := attribute.Key("test") - bli := getBitlessInfo(42) for _, testcase := range []struct { name string value attribute.Value @@ -38,6 +37,12 @@ func TestValue(t *testing.T) { wantType: attribute.BOOL, wantValue: true, }, + { + name: "Key.BoolSlice() correctly returns keys's internal []bool value", + value: k.BoolSlice([]bool{true, false, true}).Value, + wantType: attribute.BOOLSLICE, + wantValue: []bool{true, false, true}, + }, { name: "Key.Array([]bool) correctly return key's internal bool values", value: k.Array([]bool{true, false}).Value, @@ -51,28 +56,46 @@ func TestValue(t *testing.T) { wantValue: int64(42), }, { - name: "Key.Float64() correctly returns keys's internal float64 value", - value: k.Float64(42.1).Value, - wantType: attribute.FLOAT64, - wantValue: 42.1, + name: "Key.Int64Slice() correctly returns keys's internal []int64 value", + value: k.Int64Slice([]int64{42, -3, 12}).Value, + wantType: attribute.INT64SLICE, + wantValue: []int64{42, -3, 12}, }, { - name: "Key.String() correctly returns keys's internal string value", - value: k.String("foo").Value, - wantType: attribute.STRING, - wantValue: "foo", + name: "Key.Array([]int64) correctly returns keys's internal int64 values", + value: k.Array([]int64{42, 43}).Value, + wantType: attribute.ARRAY, + wantValue: [2]int64{42, 43}, }, { name: "Key.Int() correctly returns keys's internal signed integral value", - value: k.Int(bli.intValue).Value, - wantType: bli.signedType, - wantValue: bli.signedValue, + value: k.Int(42).Value, + wantType: attribute.INT64, + wantValue: int64(42), }, { - name: "Key.Array([]int64) correctly returns keys's internal int64 values", - value: k.Array([]int64{42, 43}).Value, + name: "Key.IntSlice() correctly returns keys's internal []int64 value", + value: k.IntSlice([]int{42, -3, 12}).Value, + wantType: attribute.INT64SLICE, + wantValue: []int64{42, -3, 12}, + }, + { + name: "Key.Array([]int) correctly returns keys's internal signed integral values", + value: k.Array([]int{42, 43}).Value, wantType: attribute.ARRAY, - wantValue: [2]int64{42, 43}, + wantValue: [2]int{42, 43}, + }, + { + name: "Key.Float64() correctly returns keys's internal float64 value", + value: k.Float64(42.1).Value, + wantType: attribute.FLOAT64, + wantValue: 42.1, + }, + { + name: "Key.Float64Slice() correctly returns keys's internal []float64 value", + value: k.Float64Slice([]float64{42, -3, 12}).Value, + wantType: attribute.FLOAT64SLICE, + wantValue: []float64{42, -3, 12}, }, { name: "Key.Array([]float64) correctly returns keys's internal float64 values", @@ -80,18 +103,24 @@ func TestValue(t *testing.T) { wantType: attribute.ARRAY, wantValue: [2]float64{42, 43}, }, + { + name: "Key.String() correctly returns keys's internal string value", + value: k.String("foo").Value, + wantType: attribute.STRING, + wantValue: "foo", + }, + { + name: "Key.StringSlice() correctly returns keys's internal []string value", + value: k.StringSlice([]string{"forty-two", "negative three", "twelve"}).Value, + wantType: attribute.STRINGSLICE, + wantValue: []string{"forty-two", "negative three", "twelve"}, + }, { name: "Key.Array([]string) correctly return key's internal string values", value: k.Array([]string{"foo", "bar"}).Value, wantType: attribute.ARRAY, wantValue: [2]string{"foo", "bar"}, }, - { - name: "Key.Array([]int) correctly returns keys's internal signed integral values", - value: k.Array([]int{42, 43}).Value, - wantType: attribute.ARRAY, - wantValue: [2]int{42, 43}, - }, { name: "Key.Array([][]int) refuses to create multi-dimensional array", value: k.Array([][]int{{1, 2}, {3, 4}}).Value, @@ -113,22 +142,6 @@ func TestValue(t *testing.T) { } } -type bitlessInfo struct { - intValue int - uintValue uint - signedType attribute.Type - signedValue interface{} -} - -func getBitlessInfo(i int) bitlessInfo { - return bitlessInfo{ - intValue: i, - uintValue: uint(i), - signedType: attribute.INT64, - signedValue: int64(i), - } -} - func TestAsArrayValue(t *testing.T) { v := attribute.ArrayValue([]int{1, 2, 3}).AsArray() // Ensure the returned dynamic type is stable. diff --git a/exporters/jaeger/jaeger.go b/exporters/jaeger/jaeger.go index 10ef350dc1d..88364e0a8b9 100644 --- a/exporters/jaeger/jaeger.go +++ b/exporters/jaeger/jaeger.go @@ -248,8 +248,11 @@ func keyValueToTag(keyValue attribute.KeyValue) *gen.Tag { VDouble: &f, VType: gen.TagType_DOUBLE, } - case attribute.ARRAY: - json, _ := json.Marshal(keyValue.Value.AsArray()) + case attribute.BOOLSLICE, + attribute.INT64SLICE, + attribute.FLOAT64SLICE, + attribute.STRINGSLICE: + json, _ := json.Marshal(keyValue.Value.AsInterface()) a := (string)(json) tag = &gen.Tag{ Key: string(keyValue.Key), diff --git a/exporters/jaeger/jaeger_test.go b/exporters/jaeger/jaeger_test.go index b9844b7d2f7..367cc8d6290 100644 --- a/exporters/jaeger/jaeger_test.go +++ b/exporters/jaeger/jaeger_test.go @@ -323,7 +323,7 @@ func Test_spanSnapshotToThrift(t *testing.T) { StartTime: now, EndTime: now, Attributes: []attribute.KeyValue{ - attribute.Array("arr", []int{0, 1, 2, 3}), + attribute.IntSlice("arr", []int{0, 1, 2, 3}), }, Status: sdktrace.Status{ Code: codes.Unset, diff --git a/exporters/otlp/otlpmetric/internal/metrictransform/attribute.go b/exporters/otlp/otlpmetric/internal/metrictransform/attribute.go index b9791d23c2c..69be6f931d1 100644 --- a/exporters/otlp/otlpmetric/internal/metrictransform/attribute.go +++ b/exporters/otlp/otlpmetric/internal/metrictransform/attribute.go @@ -15,8 +15,6 @@ package metrictransform import ( - "reflect" - "go.opentelemetry.io/otel/attribute" commonpb "go.opentelemetry.io/proto/otlp/common/v1" @@ -68,22 +66,40 @@ func Value(v attribute.Value) *commonpb.AnyValue { av.Value = &commonpb.AnyValue_BoolValue{ BoolValue: v.AsBool(), } + case attribute.BOOLSLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: boolSliceValues(v.AsBoolSlice()), + }, + } case attribute.INT64: av.Value = &commonpb.AnyValue_IntValue{ IntValue: v.AsInt64(), } + case attribute.INT64SLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: int64SliceValues(v.AsInt64Slice()), + }, + } case attribute.FLOAT64: av.Value = &commonpb.AnyValue_DoubleValue{ DoubleValue: v.AsFloat64(), } + case attribute.FLOAT64SLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: float64SliceValues(v.AsFloat64Slice()), + }, + } case attribute.STRING: av.Value = &commonpb.AnyValue_StringValue{ StringValue: v.AsString(), } - case attribute.ARRAY: + case attribute.STRINGSLICE: av.Value = &commonpb.AnyValue_ArrayValue{ ArrayValue: &commonpb.ArrayValue{ - Values: arrayValues(v), + Values: stringSliceValues(v.AsStringSlice()), }, } default: @@ -94,56 +110,50 @@ func Value(v attribute.Value) *commonpb.AnyValue { return av } -func arrayValues(v attribute.Value) []*commonpb.AnyValue { - a := v.AsArray() - aType := reflect.TypeOf(a) - var valueFunc func(reflect.Value) *commonpb.AnyValue - switch aType.Elem().Kind() { - case reflect.Bool: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_BoolValue{ - BoolValue: v.Bool(), - }, - } - } - case reflect.Int, reflect.Int64: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_IntValue{ - IntValue: v.Int(), - }, - } - } - case reflect.Uintptr: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_IntValue{ - IntValue: int64(v.Uint()), - }, - } +func boolSliceValues(vals []bool) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_BoolValue{ + BoolValue: v, + }, } - case reflect.Float64: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_DoubleValue{ - DoubleValue: v.Float(), - }, - } + } + return converted +} + +func int64SliceValues(vals []int64) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_IntValue{ + IntValue: v, + }, } - case reflect.String: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_StringValue{ - StringValue: v.String(), - }, - } + } + return converted +} + +func float64SliceValues(vals []float64) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_DoubleValue{ + DoubleValue: v, + }, } } + return converted +} - results := make([]*commonpb.AnyValue, aType.Len()) - for i, aValue := 0, reflect.ValueOf(a); i < aValue.Len(); i++ { - results[i] = valueFunc(aValue.Index(i)) +func stringSliceValues(vals []string) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_StringValue{ + StringValue: v, + }, + } } - return results + return converted } diff --git a/exporters/otlp/otlpmetric/internal/metrictransform/attribute_test.go b/exporters/otlp/otlpmetric/internal/metrictransform/attribute_test.go index 652fadb29ee..4c468da694c 100644 --- a/exporters/otlp/otlpmetric/internal/metrictransform/attribute_test.go +++ b/exporters/otlp/otlpmetric/internal/metrictransform/attribute_test.go @@ -112,7 +112,10 @@ func TestArrayAttributes(t *testing.T) { {nil, nil}, { []attribute.KeyValue{ - attribute.Array("invalid", [][]string{{"1", "2"}, {"a"}}), + { + Key: attribute.Key("invalid"), + Value: attribute.Value{}, + }, }, []*commonpb.KeyValue{ { @@ -127,18 +130,18 @@ func TestArrayAttributes(t *testing.T) { }, { []attribute.KeyValue{ - attribute.Array("bool array to bool array", []bool{true, false}), - attribute.Array("int array to int64 array", []int{1, 2, 3}), - attribute.Array("int64 array to int64 array", []int64{1, 2, 3}), - attribute.Array("float64 array to double array", []float64{1.11, 2.22, 3.33}), - attribute.Array("string array to string array", []string{"foo", "bar", "baz"}), + attribute.BoolSlice("bool slice to bool array", []bool{true, false}), + attribute.IntSlice("int slice to int64 array", []int{1, 2, 3}), + attribute.Int64Slice("int64 slice to int64 array", []int64{1, 2, 3}), + attribute.Float64Slice("float64 slice to double array", []float64{1.11, 2.22, 3.33}), + attribute.StringSlice("string slice to string array", []string{"foo", "bar", "baz"}), }, []*commonpb.KeyValue{ - newOTelBoolArray("bool array to bool array", []bool{true, false}), - newOTelIntArray("int array to int64 array", []int64{1, 2, 3}), - newOTelIntArray("int64 array to int64 array", []int64{1, 2, 3}), - newOTelDoubleArray("float64 array to double array", []float64{1.11, 2.22, 3.33}), - newOTelStringArray("string array to string array", []string{"foo", "bar", "baz"}), + newOTelBoolArray("bool slice to bool array", []bool{true, false}), + newOTelIntArray("int slice to int64 array", []int64{1, 2, 3}), + newOTelIntArray("int64 slice to int64 array", []int64{1, 2, 3}), + newOTelDoubleArray("float64 slice to double array", []float64{1.11, 2.22, 3.33}), + newOTelStringArray("string slice to string array", []string{"foo", "bar", "baz"}), }, }, } { diff --git a/exporters/otlp/otlptrace/internal/tracetransform/attribute.go b/exporters/otlp/otlptrace/internal/tracetransform/attribute.go index ece8ef2eb92..01579fac7bd 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/attribute.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/attribute.go @@ -15,8 +15,6 @@ package tracetransform import ( - "reflect" - "go.opentelemetry.io/otel/attribute" commonpb "go.opentelemetry.io/proto/otlp/common/v1" @@ -68,22 +66,40 @@ func Value(v attribute.Value) *commonpb.AnyValue { av.Value = &commonpb.AnyValue_BoolValue{ BoolValue: v.AsBool(), } + case attribute.BOOLSLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: boolSliceValues(v.AsBoolSlice()), + }, + } case attribute.INT64: av.Value = &commonpb.AnyValue_IntValue{ IntValue: v.AsInt64(), } + case attribute.INT64SLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: int64SliceValues(v.AsInt64Slice()), + }, + } case attribute.FLOAT64: av.Value = &commonpb.AnyValue_DoubleValue{ DoubleValue: v.AsFloat64(), } + case attribute.FLOAT64SLICE: + av.Value = &commonpb.AnyValue_ArrayValue{ + ArrayValue: &commonpb.ArrayValue{ + Values: float64SliceValues(v.AsFloat64Slice()), + }, + } case attribute.STRING: av.Value = &commonpb.AnyValue_StringValue{ StringValue: v.AsString(), } - case attribute.ARRAY: + case attribute.STRINGSLICE: av.Value = &commonpb.AnyValue_ArrayValue{ ArrayValue: &commonpb.ArrayValue{ - Values: arrayValues(v), + Values: stringSliceValues(v.AsStringSlice()), }, } default: @@ -94,56 +110,50 @@ func Value(v attribute.Value) *commonpb.AnyValue { return av } -func arrayValues(v attribute.Value) []*commonpb.AnyValue { - a := v.AsArray() - aType := reflect.TypeOf(a) - var valueFunc func(reflect.Value) *commonpb.AnyValue - switch aType.Elem().Kind() { - case reflect.Bool: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_BoolValue{ - BoolValue: v.Bool(), - }, - } - } - case reflect.Int, reflect.Int64: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_IntValue{ - IntValue: v.Int(), - }, - } - } - case reflect.Uintptr: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_IntValue{ - IntValue: int64(v.Uint()), - }, - } +func boolSliceValues(vals []bool) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_BoolValue{ + BoolValue: v, + }, } - case reflect.Float64: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_DoubleValue{ - DoubleValue: v.Float(), - }, - } + } + return converted +} + +func int64SliceValues(vals []int64) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_IntValue{ + IntValue: v, + }, } - case reflect.String: - valueFunc = func(v reflect.Value) *commonpb.AnyValue { - return &commonpb.AnyValue{ - Value: &commonpb.AnyValue_StringValue{ - StringValue: v.String(), - }, - } + } + return converted +} + +func float64SliceValues(vals []float64) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_DoubleValue{ + DoubleValue: v, + }, } } + return converted +} - results := make([]*commonpb.AnyValue, aType.Len()) - for i, aValue := 0, reflect.ValueOf(a); i < aValue.Len(); i++ { - results[i] = valueFunc(aValue.Index(i)) +func stringSliceValues(vals []string) []*commonpb.AnyValue { + converted := make([]*commonpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &commonpb.AnyValue{ + Value: &commonpb.AnyValue_StringValue{ + StringValue: v, + }, + } } - return results + return converted } diff --git a/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go b/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go index 156670b2af5..09f6a4c335d 100644 --- a/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go +++ b/exporters/otlp/otlptrace/internal/tracetransform/attribute_test.go @@ -112,7 +112,10 @@ func TestArrayAttributes(t *testing.T) { {nil, nil}, { []attribute.KeyValue{ - attribute.Array("invalid", [][]string{{"1", "2"}, {"a"}}), + { + Key: attribute.Key("invalid"), + Value: attribute.Value{}, + }, }, []*commonpb.KeyValue{ { @@ -127,18 +130,18 @@ func TestArrayAttributes(t *testing.T) { }, { []attribute.KeyValue{ - attribute.Array("bool array to bool array", []bool{true, false}), - attribute.Array("int array to int64 array", []int{1, 2, 3}), - attribute.Array("int64 array to int64 array", []int64{1, 2, 3}), - attribute.Array("float64 array to double array", []float64{1.11, 2.22, 3.33}), - attribute.Array("string array to string array", []string{"foo", "bar", "baz"}), + attribute.BoolSlice("bool slice to bool array", []bool{true, false}), + attribute.IntSlice("int slice to int64 array", []int{1, 2, 3}), + attribute.Int64Slice("int64 slice to int64 array", []int64{1, 2, 3}), + attribute.Float64Slice("float64 slice to double array", []float64{1.11, 2.22, 3.33}), + attribute.StringSlice("string slice to string array", []string{"foo", "bar", "baz"}), }, []*commonpb.KeyValue{ - newOTelBoolArray("bool array to bool array", []bool{true, false}), - newOTelIntArray("int array to int64 array", []int64{1, 2, 3}), - newOTelIntArray("int64 array to int64 array", []int64{1, 2, 3}), - newOTelDoubleArray("float64 array to double array", []float64{1.11, 2.22, 3.33}), - newOTelStringArray("string array to string array", []string{"foo", "bar", "baz"}), + newOTelBoolArray("bool slice to bool array", []bool{true, false}), + newOTelIntArray("int slice to int64 array", []int64{1, 2, 3}), + newOTelIntArray("int64 slice to int64 array", []int64{1, 2, 3}), + newOTelDoubleArray("float64 slice to double array", []float64{1.11, 2.22, 3.33}), + newOTelStringArray("string slice to string array", []string{"foo", "bar", "baz"}), }, }, } { diff --git a/exporters/zipkin/model.go b/exporters/zipkin/model.go index 93f5e64ada3..ebd581fcf8b 100644 --- a/exporters/zipkin/model.go +++ b/exporters/zipkin/model.go @@ -181,9 +181,18 @@ func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { m := make(map[string]string, len(attr)+len(extraZipkinTags)) for _, kv := range attr { switch kv.Value.Type() { - // For array attributes, serialize as JSON list string. - case attribute.ARRAY: - json, _ := json.Marshal(kv.Value.AsArray()) + // For slice attributes, serialize as JSON list string. + case attribute.BOOLSLICE: + json, _ := json.Marshal(kv.Value.AsBoolSlice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.INT64SLICE: + json, _ := json.Marshal(kv.Value.AsInt64Slice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.FLOAT64SLICE: + json, _ := json.Marshal(kv.Value.AsFloat64Slice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.STRINGSLICE: + json, _ := json.Marshal(kv.Value.AsStringSlice()) m[(string)(kv.Key)] = (string)(json) default: m[(string)(kv.Key)] = kv.Value.Emit() diff --git a/exporters/zipkin/model_test.go b/exporters/zipkin/model_test.go index 7ee136951a7..14629402585 100644 --- a/exporters/zipkin/model_test.go +++ b/exporters/zipkin/model_test.go @@ -59,7 +59,7 @@ func TestModelConversion(t *testing.T) { Attributes: []attribute.KeyValue{ attribute.Int64("attr1", 42), attribute.String("attr2", "bar"), - attribute.Array("attr3", []int{0, 1, 2}), + attribute.IntSlice("attr3", []int{0, 1, 2}), }, Events: []tracesdk.Event{ { diff --git a/oteltest/tracestate_test.go b/oteltest/tracestate_test.go index df3c926f574..e4afdf5039c 100644 --- a/oteltest/tracestate_test.go +++ b/oteltest/tracestate_test.go @@ -33,9 +33,8 @@ func TestTraceStateFromKeyValues(t *testing.T) { attribute.Bool("key1", true), attribute.Int64("key2", 1), attribute.Float64("key3", 1.1), - attribute.Array("key4", []int{1, 1}), ) require.NoError(t, err) - expected := "key0=string,key1=true,key2=1,key3=1.1,key4=[1 1]" + expected := "key0=string,key1=true,key2=1,key3=1.1" assert.Equal(t, expected, ts.String()) } diff --git a/sdk/resource/process.go b/sdk/resource/process.go index c75c5ef0f15..60f33e5cab6 100644 --- a/sdk/resource/process.go +++ b/sdk/resource/process.go @@ -138,7 +138,7 @@ func (processExecutablePathDetector) Detect(ctx context.Context) (*Resource, err // Detect returns a *Resource that describes all the command arguments as received // by the process. func (processCommandArgsDetector) Detect(ctx context.Context) (*Resource, error) { - return NewWithAttributes(semconv.SchemaURL, semconv.ProcessCommandArgsKey.Array(commandArgs())), nil + return NewWithAttributes(semconv.SchemaURL, semconv.ProcessCommandArgsKey.StringSlice(commandArgs())), nil } // Detect returns a *Resource that describes the username of the user that owns the