1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-07-07 00:35:52 +02:00

Update label.ArrayValue to store copies of 1D arrays (#1226)

* Update label.ArrayValue to store copies of 1D arrays

* Update OTLP transform of array attributes

* Add changes to CHANGELOG

* Add PR number to changes

* Update value function documentation

* Remove redundant checks

* Add Array test for invalid array types

* Add test to ensure return from AsArray is a type

* Clean up iteration
This commit is contained in:
Tyler Yahn
2020-10-05 17:09:03 -07:00
committed by GitHub
parent 577b21745b
commit 5660b0b59f
5 changed files with 138 additions and 201 deletions

View File

@ -25,6 +25,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- The `StatusCode` field of the `SpanData` struct in the `go.opentelemetry.io/otel/sdk/export/trace` package now uses the codes package from this package instead of the gRPC project. (#1214) - The `StatusCode` field of the `SpanData` struct in the `go.opentelemetry.io/otel/sdk/export/trace` package now uses the codes package from this package instead of the gRPC project. (#1214)
- Move the `go.opentelemetry.io/otel/api/baggage` package into `go.opentelemetry.io/otel/propagators`. (#1217) - Move the `go.opentelemetry.io/otel/api/baggage` package into `go.opentelemetry.io/otel/propagators`. (#1217)
### Fixed
- Copies of data from arrays and slices passed to `go.opentelemetry.io/otel/label.ArrayValue()` are now used in the returned `Value` instead of using the mutable data itself. (#1226)
### Removed ### Removed
- The `ExtractHTTP` and `InjectHTTP` fuctions from the `go.opentelemetry.io/otel/api/propagation` package were removed. (#1212) - The `ExtractHTTP` and `InjectHTTP` fuctions from the `go.opentelemetry.io/otel/api/propagation` package were removed. (#1212)
@ -33,6 +37,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- The `SetAttribute` method of the `Span` from the `go.opentelemetry.io/otel/api/trace` package was removed given its redundancy with the `SetAttributes` method. (#1216) - The `SetAttribute` method of the `Span` from the `go.opentelemetry.io/otel/api/trace` package was removed given its redundancy with the `SetAttributes` method. (#1216)
- The internal implementation of Baggage storage is removed in favor of using the new Baggage API functionality. (#1217) - The internal implementation of Baggage storage is removed in favor of using the new Baggage API functionality. (#1217)
- Remove duplicate hostname key `HostHostNameKey` in Resource semantic conventions. (#1219) - Remove duplicate hostname key `HostHostNameKey` in Resource semantic conventions. (#1219)
- Nested array/slice support has been removed. (#1226)
## [0.12.0] - 2020-09-24 ## [0.12.0] - 2020-09-24

View File

@ -15,6 +15,8 @@
package transform package transform
import ( import (
"reflect"
commonpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/common/v1" commonpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/common/v1"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
@ -75,7 +77,11 @@ func toAttribute(v label.KeyValue) *commonpb.KeyValue {
StringValue: v.Value.AsString(), StringValue: v.Value.AsString(),
} }
case label.ARRAY: case label.ARRAY:
result.Value.Value = toArrayAttribute(v) result.Value.Value = &commonpb.AnyValue_ArrayValue{
ArrayValue: &commonpb.ArrayValue{
Values: arrayValues(v),
},
}
default: default:
result.Value.Value = &commonpb.AnyValue_StringValue{ result.Value.Value = &commonpb.AnyValue_StringValue{
StringValue: "INVALID", StringValue: "INVALID",
@ -84,164 +90,56 @@ func toAttribute(v label.KeyValue) *commonpb.KeyValue {
return result return result
} }
func toArrayAttribute(v label.KeyValue) *commonpb.AnyValue_ArrayValue { func arrayValues(kv label.KeyValue) []*commonpb.AnyValue {
array := v.Value.AsArray() a := kv.Value.AsArray()
var resultValues []*commonpb.AnyValue aType := reflect.TypeOf(a)
var valueFunc func(reflect.Value) *commonpb.AnyValue
switch typedArray := array.(type) { switch aType.Elem().Kind() {
case []bool: case reflect.Bool:
resultValues = getValuesFromBoolArray(typedArray) valueFunc = func(v reflect.Value) *commonpb.AnyValue {
case []int: return &commonpb.AnyValue{
resultValues = getValuesFromIntArray(typedArray)
case []int32:
resultValues = getValuesFromInt32Array(typedArray)
case []int64:
resultValues = getValuesFromInt64Array(typedArray)
case []uint:
resultValues = getValuesFromUIntArray(typedArray)
case []uint32:
resultValues = getValuesFromUInt32Array(typedArray)
case []uint64:
resultValues = getValuesFromUInt64Array(typedArray)
case []float32:
resultValues = getValuesFromFloat32Array(typedArray)
case []float64:
resultValues = getValuesFromFloat64Array(typedArray)
case []string:
resultValues = getValuesFromStringArray(typedArray)
default:
resultValues = []*commonpb.AnyValue{
{
Value: &commonpb.AnyValue_StringValue{
StringValue: "INVALID",
},
},
}
}
return &commonpb.AnyValue_ArrayValue{
ArrayValue: &commonpb.ArrayValue{
Values: resultValues,
},
}
}
func getValuesFromBoolArray(boolArray []bool) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, b := range boolArray {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_BoolValue{ Value: &commonpb.AnyValue_BoolValue{
BoolValue: b, BoolValue: v.Bool(),
}, },
})
} }
return result }
} case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
valueFunc = func(v reflect.Value) *commonpb.AnyValue {
func getValuesFromIntArray(intArray []int) []*commonpb.AnyValue { return &commonpb.AnyValue{
result := []*commonpb.AnyValue{}
for _, i := range intArray {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_IntValue{ Value: &commonpb.AnyValue_IntValue{
IntValue: int64(i), IntValue: v.Int(),
}, },
})
} }
return result }
} case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
valueFunc = func(v reflect.Value) *commonpb.AnyValue {
func getValuesFromInt32Array(int32Array []int32) []*commonpb.AnyValue { return &commonpb.AnyValue{
result := []*commonpb.AnyValue{}
for _, i := range int32Array {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_IntValue{ Value: &commonpb.AnyValue_IntValue{
IntValue: int64(i), IntValue: int64(v.Uint()),
}, },
})
} }
return result
}
func getValuesFromInt64Array(int64Array []int64) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, i := range int64Array {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_IntValue{
IntValue: i,
},
})
} }
return result case reflect.Float32, reflect.Float64:
} valueFunc = func(v reflect.Value) *commonpb.AnyValue {
return &commonpb.AnyValue{
func getValuesFromUIntArray(uintArray []uint) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, i := range uintArray {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_IntValue{
IntValue: int64(i),
},
})
}
return result
}
func getValuesFromUInt32Array(uint32Array []uint32) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, i := range uint32Array {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_IntValue{
IntValue: int64(i),
},
})
}
return result
}
func getValuesFromUInt64Array(uint64Array []uint64) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, i := range uint64Array {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_IntValue{
IntValue: int64(i),
},
})
}
return result
}
func getValuesFromFloat32Array(float32Array []float32) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, f := range float32Array {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_DoubleValue{ Value: &commonpb.AnyValue_DoubleValue{
DoubleValue: float64(f), DoubleValue: v.Float(),
}, },
})
} }
return result
}
func getValuesFromFloat64Array(float64Array []float64) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, f := range float64Array {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_DoubleValue{
DoubleValue: f,
},
})
} }
return result case reflect.String:
} valueFunc = func(v reflect.Value) *commonpb.AnyValue {
return &commonpb.AnyValue{
func getValuesFromStringArray(stringArray []string) []*commonpb.AnyValue {
result := []*commonpb.AnyValue{}
for _, s := range stringArray {
result = append(result, &commonpb.AnyValue{
Value: &commonpb.AnyValue_StringValue{ Value: &commonpb.AnyValue_StringValue{
StringValue: s, StringValue: v.String(),
}, },
})
} }
return result }
}
results := make([]*commonpb.AnyValue, aType.Len())
for i, aValue := 0, reflect.ValueOf(a); i < aValue.Len(); i++ {
results[i] = valueFunc(aValue.Index(i))
}
return results
} }

View File

@ -156,6 +156,21 @@ func TestArrayAttributes(t *testing.T) {
// "uint", "uint32", "uint64" // "uint", "uint32", "uint64"
for _, test := range []attributeTest{ for _, test := range []attributeTest{
{nil, nil}, {nil, nil},
{
[]label.KeyValue{
label.Array("invalid", [][]string{{"1", "2"}, {"a"}}),
},
[]*commonpb.KeyValue{
{
Key: "invalid",
Value: &commonpb.AnyValue{
Value: &commonpb.AnyValue_StringValue{
StringValue: "INVALID",
},
},
},
},
},
{ {
[]label.KeyValue{ []label.KeyValue{
label.Array("bool array to bool array", []bool{true, false}), label.Array("bool array to bool array", []bool{true, false}),
@ -191,17 +206,20 @@ func TestArrayAttributes(t *testing.T) {
for i, actualArrayAttr := range actualArrayAttributes { for i, actualArrayAttr := range actualArrayAttributes {
expectedArrayAttr := expectedArrayAttributes[i] expectedArrayAttr := expectedArrayAttributes[i]
if !assert.Equal(t, expectedArrayAttr.Key, actualArrayAttr.Key) { expectedKey, actualKey := expectedArrayAttr.Key, actualArrayAttr.Key
if !assert.Equal(t, expectedKey, actualKey) {
continue continue
} }
expectedArrayValue := expectedArrayAttr.Value.GetArrayValue() expected := expectedArrayAttr.Value.GetArrayValue()
assert.NotNil(t, expectedArrayValue) actual := actualArrayAttr.Value.GetArrayValue()
if expected == nil {
actualArrayValue := actualArrayAttr.Value.GetArrayValue() assert.Nil(t, actual)
assert.NotNil(t, actualArrayValue) continue
}
assertExpectedArrayValues(t, expectedArrayValue.Values, actualArrayValue.Values) if assert.NotNil(t, actual, "expected not nil for %s", actualKey) {
assertExpectedArrayValues(t, expected.Values, actual.Values)
}
} }
} }

View File

@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"unsafe" "unsafe"
"go.opentelemetry.io/otel/internal" "go.opentelemetry.io/otel/internal"
@ -93,7 +92,7 @@ func Int32Value(v int32) Value {
} }
} }
// Uint32 creates a UINT32 Value. // Uint32Value creates a UINT32 Value.
func Uint32Value(v uint32) Value { func Uint32Value(v uint32) Value {
return Value{ return Value{
vtype: UINT32, vtype: UINT32,
@ -101,7 +100,7 @@ func Uint32Value(v uint32) Value {
} }
} }
// Float32 creates a FLOAT32 Value. // Float32Value creates a FLOAT32 Value.
func Float32Value(v float32) Value { func Float32Value(v float32) Value {
return Value{ return Value{
vtype: FLOAT32, vtype: FLOAT32,
@ -109,7 +108,7 @@ func Float32Value(v float32) Value {
} }
} }
// String creates a STRING Value. // StringValue creates a STRING Value.
func StringValue(v string) Value { func StringValue(v string) Value {
return Value{ return Value{
vtype: STRING, vtype: STRING,
@ -117,7 +116,7 @@ func StringValue(v string) Value {
} }
} }
// Int creates either an INT32 or an INT64 Value, depending on whether // IntValue creates either an INT32 or an INT64 Value, depending on whether
// the int type is 32 or 64 bits wide. // the int type is 32 or 64 bits wide.
func IntValue(v int) Value { func IntValue(v int) Value {
if unsafe.Sizeof(v) == 4 { if unsafe.Sizeof(v) == 4 {
@ -126,8 +125,8 @@ func IntValue(v int) Value {
return Int64Value(int64(v)) return Int64Value(int64(v))
} }
// Uint creates either a UINT32 or a UINT64 Value, depending on // UintValue creates either a UINT32 or a UINT64 Value, depending on whether
// whether the uint type is 32 or 64 bits wide. // the uint type is 32 or 64 bits wide.
func UintValue(v uint) Value { func UintValue(v uint) Value {
if unsafe.Sizeof(v) == 4 { if unsafe.Sizeof(v) == 4 {
return Uint32Value(uint32(v)) return Uint32Value(uint32(v))
@ -135,27 +134,32 @@ func UintValue(v uint) Value {
return Uint64Value(uint64(v)) return Uint64Value(uint64(v))
} }
// Array creates an ARRAY value. // ArrayValue creates an ARRAY value from an array or slice.
func ArrayValue(array interface{}) Value { // Only arrays or slices of bool, int, int32, int64, uint, uint32, uint64,
switch reflect.TypeOf(array).Kind() { // float, float32, 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.
func ArrayValue(v interface{}) Value {
switch reflect.TypeOf(v).Kind() {
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
isValidType := func() bool {
// get array type regardless of dimensions // get array type regardless of dimensions
typeName := reflect.TypeOf(array).String() typ := reflect.TypeOf(v).Elem()
typeName = typeName[strings.LastIndex(typeName, "]")+1:] kind := typ.Kind()
switch typeName { switch kind {
case "bool", "int", "int32", "int64", case reflect.Bool, reflect.Int, reflect.Int32, reflect.Int64,
"float32", "float64", "string", reflect.Float32, reflect.Float64, reflect.String,
"uint", "uint32", "uint64": reflect.Uint, reflect.Uint32, reflect.Uint64:
return true val := reflect.ValueOf(v)
} length := val.Len()
return false frozen := reflect.Indirect(reflect.New(reflect.ArrayOf(length, typ)))
}() reflect.Copy(frozen, val)
if isValidType {
return Value{ return Value{
vtype: ARRAY, vtype: ARRAY,
array: array, array: frozen.Interface(),
} }
default:
return Value{vtype: INVALID}
} }
} }
return Value{vtype: INVALID} return Value{vtype: INVALID}

View File

@ -15,6 +15,7 @@
package label_test package label_test
import ( import (
"reflect"
"testing" "testing"
"unsafe" "unsafe"
@ -42,7 +43,7 @@ func TestValue(t *testing.T) {
name: "Key.Array([]bool) correctly return key's internal bool values", name: "Key.Array([]bool) correctly return key's internal bool values",
value: k.Array([]bool{true, false}).Value, value: k.Array([]bool{true, false}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []bool{true, false}, wantValue: [2]bool{true, false},
}, },
{ {
name: "Key.Int64() correctly returns keys's internal int64 value", name: "Key.Int64() correctly returns keys's internal int64 value",
@ -102,67 +103,70 @@ func TestValue(t *testing.T) {
name: "Key.Array([]int64) correctly returns keys's internal int64 values", name: "Key.Array([]int64) correctly returns keys's internal int64 values",
value: k.Array([]int64{42, 43}).Value, value: k.Array([]int64{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []int64{42, 43}, wantValue: [2]int64{42, 43},
}, },
{ {
name: "KeyArray([]uint64) correctly returns keys's internal uint64 values", name: "KeyArray([]uint64) correctly returns keys's internal uint64 values",
value: k.Array([]uint64{42, 43}).Value, value: k.Array([]uint64{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []uint64{42, 43}, wantValue: [2]uint64{42, 43},
}, },
{ {
name: "Key.Array([]float64) correctly returns keys's internal float64 values", name: "Key.Array([]float64) correctly returns keys's internal float64 values",
value: k.Array([]float64{42, 43}).Value, value: k.Array([]float64{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []float64{42, 43}, wantValue: [2]float64{42, 43},
}, },
{ {
name: "Key.Array([]int32) correctly returns keys's internal int32 values", name: "Key.Array([]int32) correctly returns keys's internal int32 values",
value: k.Array([]int32{42, 43}).Value, value: k.Array([]int32{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []int32{42, 43}, wantValue: [2]int32{42, 43},
}, },
{ {
name: "Key.Array([]uint32) correctly returns keys's internal uint32 values", name: "Key.Array([]uint32) correctly returns keys's internal uint32 values",
value: k.Array([]uint32{42, 43}).Value, value: k.Array([]uint32{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []uint32{42, 43}, wantValue: [2]uint32{42, 43},
}, },
{ {
name: "Key.Array([]float32) correctly returns keys's internal float32 values", name: "Key.Array([]float32) correctly returns keys's internal float32 values",
value: k.Array([]float32{42, 43}).Value, value: k.Array([]float32{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []float32{42, 43}, wantValue: [2]float32{42, 43},
}, },
{ {
name: "Key.Array([]string) correctly return key's internal string values", name: "Key.Array([]string) correctly return key's internal string values",
value: k.Array([]string{"foo", "bar"}).Value, value: k.Array([]string{"foo", "bar"}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []string{"foo", "bar"}, wantValue: [2]string{"foo", "bar"},
}, },
{ {
name: "Key.Array([]int) correctly returns keys's internal signed integral values", name: "Key.Array([]int) correctly returns keys's internal signed integral values",
value: k.Array([]int{42, 43}).Value, value: k.Array([]int{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []int{42, 43}, wantValue: [2]int{42, 43},
}, },
{ {
name: "Key.Array([]uint) correctly returns keys's internal unsigned integral values", name: "Key.Array([]uint) correctly returns keys's internal unsigned integral values",
value: k.Array([]uint{42, 43}).Value, value: k.Array([]uint{42, 43}).Value,
wantType: label.ARRAY, wantType: label.ARRAY,
wantValue: []uint{42, 43}, wantValue: [2]uint{42, 43},
}, },
{ {
name: "Key.Array([][]int) correctly return key's multi dimensional array", name: "Key.Array([][]int) refuses to create multi-dimensional array",
value: k.Array([][]int{{1, 2}, {3, 4}}).Value, value: k.Array([][]int{{1, 2}, {3, 4}}).Value,
wantType: label.ARRAY, wantType: label.INVALID,
wantValue: [][]int{{1, 2}, {3, 4}}, wantValue: nil,
}, },
} { } {
t.Logf("Running test case %s", testcase.name) t.Logf("Running test case %s", testcase.name)
if testcase.value.Type() != testcase.wantType { if testcase.value.Type() != testcase.wantType {
t.Errorf("wrong value type, got %#v, expected %#v", testcase.value.Type(), testcase.wantType) t.Errorf("wrong value type, got %#v, expected %#v", testcase.value.Type(), testcase.wantType)
} }
if testcase.wantType == label.INVALID {
continue
}
got := testcase.value.AsInterface() got := testcase.value.AsInterface()
if diff := cmp.Diff(testcase.wantValue, got); diff != "" { if diff := cmp.Diff(testcase.wantValue, got); diff != "" {
t.Errorf("+got, -want: %s", diff) t.Errorf("+got, -want: %s", diff)
@ -199,3 +203,11 @@ func getBitlessInfo(i int) bitlessInfo {
unsignedValue: uint64(i), unsignedValue: uint64(i),
} }
} }
func TestAsArrayValue(t *testing.T) {
v := label.ArrayValue([]uint{1, 2, 3}).AsArray()
// Ensure the returned dynamic type is stable.
if got, want := reflect.TypeOf(v).Kind(), reflect.Array; got != want {
t.Errorf("AsArray() returned %T, want %T", got, want)
}
}