Skip to content

Commit

Permalink
Fix some unexpected behaviors
Browse files Browse the repository at this point in the history
  • Loading branch information
ebi-yade committed Nov 28, 2024
1 parent 8f09627 commit 7206147
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 23 deletions.
52 changes: 31 additions & 21 deletions pkg/otel/attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package otel
import (
"encoding/json"
"fmt"

"reflect"
"time"

Expand Down Expand Up @@ -42,7 +43,7 @@ func marshalMap(rv reflect.Value) ([]attribute.KeyValue, error) {
return []attribute.KeyValue{}, nil
}
if keys[0].Kind() != reflect.String {
return nil, fmt.Errorf("unsupport map key type %s", keys[0].Type())
return nil, fmt.Errorf("unsupported map key type %s", keys[0].Type())
}
attrs := make([]attribute.KeyValue, 0, len(keys))
for index, key := range keys {
Expand Down Expand Up @@ -145,9 +146,13 @@ func marshalMap(rv reflect.Value) ([]attribute.KeyValue, error) {
attrs = append(attrs, attribute.Float64Slice(keyString, value))
case []string:
attrs = append(attrs, attribute.StringSlice(keyString, value))
case time.Time:
attrs = append(attrs, attribute.String(keyString, value.Format(time.RFC3339)))
default:
if mv.Type().ConvertibleTo(timeType) {
t := mv.Convert(timeType).Interface().(time.Time)
attrs = append(attrs, attribute.String(keyString, t.Format(time.RFC3339Nano)))
continue
}

kvs, err := marshalField(structFiled{
attributeName: keyString,
filedIndex: index,
Expand Down Expand Up @@ -197,25 +202,20 @@ func marshalField(f structFiled, fv reflect.Value) ([]attribute.KeyValue, error)
case reflect.Struct:
// convert time.Time to string
if fv.Type().ConvertibleTo(timeType) {
return []attribute.KeyValue{
attribute.String(f.attributeName, fv.Interface().(time.Time).Format(time.RFC3339)),
}, nil
t := fv.Convert(timeType).Interface().(time.Time)
return []attribute.KeyValue{attribute.String(f.attributeName, t.Format(time.RFC3339Nano))}, nil
}
return f.marshalNested(fv)

// delve into normal struct types
kvs, err := MarshalOtelAttributes(fv.Interface())
if err != nil {
return nil, err
}
for i := range kvs {
kvs[i].Key = attribute.Key(f.attributePrefix) + kvs[i].Key
}
return kvs, nil
case reflect.Ptr:
if fv.IsNil() {
return nil, nil
}
return marshalField(f, fv.Elem())

case reflect.Map:
return f.marshalNested(fv)

default:
bs, err := json.Marshal(fv.Interface())
if err != nil {
Expand All @@ -225,6 +225,17 @@ func marshalField(f structFiled, fv reflect.Value) ([]attribute.KeyValue, error)
}
}

func (s structFiled) marshalNested(fv reflect.Value) ([]attribute.KeyValue, error) {
attrs, err := MarshalOtelAttributes(fv.Interface())
if err != nil {
return []attribute.KeyValue{}, err
}
for i := range attrs {
attrs[i].Key = attribute.Key(s.attributePrefix) + attrs[i].Key
}
return attrs, nil
}

func marshalSlice(f structFiled, fv reflect.Value) ([]attribute.KeyValue, error) {
switch fv.Type().Elem().Kind() {
case reflect.Bool:
Expand All @@ -239,15 +250,14 @@ func marshalSlice(f structFiled, fv reflect.Value) ([]attribute.KeyValue, error)
return []attribute.KeyValue{attribute.StringSlice(f.attributeName, reflectValueToSlice[string](fv))}, nil
case reflect.Struct:
if fv.Type().Elem().ConvertibleTo(timeType) {
// TODO: consider reimplemeting it with samber/lo.Map
times := reflectValueToSlice[time.Time](fv)
strs := make([]string, len(times))
for i, t := range times {
strs[i] = t.Format(time.RFC3339)
strs := make([]string, fv.Len())
for i := 0; i < fv.Len(); i++ {
t := fv.Index(i).Convert(timeType).Interface().(time.Time)
strs[i] = t.Format(time.RFC3339Nano)
}
return []attribute.KeyValue{attribute.StringSlice(f.attributeName, strs)}, nil
}
fallthrough
fallthrough // OpenTelemetry プロトコルのレベルで、そもそも合成型の配列は非対応のため、いずれにせよ文字列化以外の選択肢は与えない
default:
bs, err := json.Marshal(fv.Interface())
if err != nil {
Expand Down
34 changes: 34 additions & 0 deletions pkg/otel/attr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"fmt"
"slices"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
)

Expand Down Expand Up @@ -49,6 +51,38 @@ func TestMarshalOtelAttributes__PrimitiveTypes(t *testing.T) {
assertAttributes(t, want, got)
}

func TestMarshalOtelAttributes__Time(t *testing.T) {
const layout = "2006-01-02T15:04:05.999999999"
const testTimeStr = "2021-01-01T00:00:00.123456789"
testTime, err := time.ParseInLocation(layout, testTimeStr, time.FixedZone("Asia/Tokyo", 9*60*60))
require.NoError(t, err)

type Wrapper time.Time
args := struct {
StdTime time.Time
WrapperTime Wrapper
Map map[string]interface{}
}{
StdTime: testTime,
WrapperTime: Wrapper(testTime),
Map: map[string]interface{}{
"std_time": testTime,
"wrapper_time": Wrapper(testTime),
},
}

const expectedStr = "2021-01-01T00:00:00.123456789+09:00"
want := []attribute.KeyValue{
attribute.String("std_time", expectedStr),
attribute.String("wrapper_time", expectedStr),
attribute.String("map.std_time", expectedStr),
attribute.String("map.wrapper_time", expectedStr),
}
got, err := MarshalOtelAttributes(args)
assert.NoError(t, err)
assertAttributes(t, want, got)
}

type structWithNameTags struct {
BoolValue bool `otel:"b"`
BoolSlice []bool `otel:"bs"`
Expand Down
3 changes: 1 addition & 2 deletions pkg/otel/struct_field.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ func getStructFields(t reflect.Type) []structFiled {
continue
}
attributeName := tagParts[0]
attributePrefix := tagParts[0] + "."
if attributeName == "" {
attributeName = camelToSnake(f.Name)
attributePrefix = ""
}
attributePrefix := attributeName + "."
var omitEmpty bool
for _, part := range tagParts[1:] {
if part == "omitempty" {
Expand Down

0 comments on commit 7206147

Please sign in to comment.