Skip to content

Commit

Permalink
otelslog: Add shared convertValue
Browse files Browse the repository at this point in the history
  • Loading branch information
pellared committed Oct 15, 2024
1 parent 0aad96e commit 601f98b
Show file tree
Hide file tree
Showing 4 changed files with 400 additions and 4 deletions.
123 changes: 123 additions & 0 deletions bridges/otelslog/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/logutil/convert.go.tmpl

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otelslog // import "go.opentelemetry.io/contrib/bridges/otelslog"

import (
"fmt"
"math"
"reflect"
"strconv"
"time"

"go.opentelemetry.io/otel/log"
)

// convertValue converts various types to log.Value.
func convertValue(v any) log.Value {
// Handling the most common types without reflect is a small perf win.
switch val := v.(type) {
case bool:
return log.BoolValue(val)
case string:
return log.StringValue(val)
case int:
return log.Int64Value(int64(val))
case int8:
return log.Int64Value(int64(val))
case int16:
return log.Int64Value(int64(val))
case int32:
return log.Int64Value(int64(val))
case int64:
return log.Int64Value(val)
case uint:
return convertUintValue(uint64(val))
case uint8:
return log.Int64Value(int64(val))
case uint16:
return log.Int64Value(int64(val))
case uint32:
return log.Int64Value(int64(val))
case uint64:
return convertUintValue(val)
case uintptr:
return convertUintValue(uint64(val))
case float32:
return log.Float64Value(float64(val))
case float64:
return log.Float64Value(val)
case time.Duration:
return log.Int64Value(val.Nanoseconds())
case complex64:
r := log.Float64("r", real(complex128(val)))
i := log.Float64("i", imag(complex128(val)))
return log.MapValue(r, i)
case complex128:
r := log.Float64("r", real(val))
i := log.Float64("i", imag(val))
return log.MapValue(r, i)
case time.Time:
return log.Int64Value(val.UnixNano())
case []byte:
return log.BytesValue(val)
case error:
return log.StringValue(val.Error())
}

t := reflect.TypeOf(v)
if t == nil {
return log.Value{}
}
val := reflect.ValueOf(v)
switch t.Kind() {
case reflect.Struct:
return log.StringValue(fmt.Sprintf("%+v", v))
case reflect.Slice, reflect.Array:
items := make([]log.Value, 0, val.Len())
for i := 0; i < val.Len(); i++ {
items = append(items, convertValue(val.Index(i).Interface()))
}
return log.SliceValue(items...)
case reflect.Map:
kvs := make([]log.KeyValue, 0, val.Len())
for _, k := range val.MapKeys() {
var key string
switch k.Kind() {
case reflect.String:
key = k.String()
default:
key = fmt.Sprintf("%+v", k.Interface())
}
kvs = append(kvs, log.KeyValue{
Key: key,
Value: convertValue(val.MapIndex(k).Interface()),
})
}
return log.MapValue(kvs...)
case reflect.Ptr, reflect.Interface:
if val.IsNil() {
return log.Value{}
}
return convertValue(val.Elem().Interface())
}

// Try to handle this as gracefully as possible.
//
// Don't panic here. it is preferable to have user's open issue
// asking why their attributes have a "unhandled: " prefix than
// say that their code is panicking.
return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v))
}

// convertUintValue converts a uint64 to a log.Value.
// If the value is too large to fit in an int64, it is converted to a string.
func convertUintValue(v uint64) log.Value {
if v > math.MaxInt64 {
return log.StringValue(strconv.FormatUint(v, 10))
}
return log.Int64Value(int64(v))
}
265 changes: 265 additions & 0 deletions bridges/otelslog/convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/logutil/convert_test.go.tmpl

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otelslog

import (
"context"
"errors"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"

"go.opentelemetry.io/otel/log"
)

func TestConvertValue(t *testing.T) {
for _, tt := range []struct {
name string
value any
wantValue log.Value
}{
{
name: "bool",
value: true,
wantValue: log.BoolValue(true),
},
{
name: "string",
value: "value",
wantValue: log.StringValue("value"),
},
{
name: "int",
value: 10,
wantValue: log.Int64Value(10),
},
{
name: "int8",
value: int8(127),
wantValue: log.Int64Value(127),
},
{
name: "int16",
value: int16(32767),
wantValue: log.Int64Value(32767),
},
{
name: "int32",
value: int32(2147483647),
wantValue: log.Int64Value(2147483647),
},
{
name: "int64",
value: int64(9223372036854775807),
wantValue: log.Int64Value(9223372036854775807),
},
{
name: "uint",
value: uint(42),
wantValue: log.Int64Value(42),
},
{
name: "uint8",
value: uint8(255),
wantValue: log.Int64Value(255),
},
{
name: "uint16",
value: uint16(65535),
wantValue: log.Int64Value(65535),
},
{
name: "uint32",
value: uint32(4294967295),
wantValue: log.Int64Value(4294967295),
},
{
name: "uint64",
value: uint64(9223372036854775807),
wantValue: log.Int64Value(9223372036854775807),
},
{
name: "uint64-max",
value: uint64(18446744073709551615),
wantValue: log.StringValue("18446744073709551615"),
},
{
name: "uintptr",
value: uintptr(12345),
wantValue: log.Int64Value(12345),
},
{
name: "float64",
value: float64(3.14159),
wantValue: log.Float64Value(3.14159),
},
{
name: "time.Duration",
value: time.Second,
wantValue: log.Int64Value(1_000_000_000),
},
{
name: "complex64",
value: complex64(complex(float32(1), float32(2))),
wantValue: log.MapValue(log.Float64("r", 1), log.Float64("i", 2)),
},
{
name: "complex128",
value: complex(float64(3), float64(4)),
wantValue: log.MapValue(log.Float64("r", 3), log.Float64("i", 4)),
},
{
name: "time.Time",
value: time.Unix(1000, 1000),
wantValue: log.Int64Value(time.Unix(1000, 1000).UnixNano()),
},
{
name: "[]byte",
value: []byte("hello"),
wantValue: log.BytesValue([]byte("hello")),
},
{
name: "error",
value: errors.New("test error"),
wantValue: log.StringValue("test error"),
},
{
name: "error",
value: errors.New("test error"),
wantValue: log.StringValue("test error"),
},
{
name: "error-nested",
value: fmt.Errorf("test error: %w", errors.New("nested error")),
wantValue: log.StringValue("test error: nested error"),
},
{
name: "nil",
value: nil,
wantValue: log.Value{},
},
{
name: "nil_ptr",
value: (*int)(nil),
wantValue: log.Value{},
},
{
name: "int_ptr",
value: func() *int { i := 93; return &i }(),
wantValue: log.Int64Value(93),
},
{
name: "string_ptr",
value: func() *string { s := "hello"; return &s }(),
wantValue: log.StringValue("hello"),
},
{
name: "bool_ptr",
value: func() *bool { b := true; return &b }(),
wantValue: log.BoolValue(true),
},
{
name: "int_empty_array",
value: []int{},
wantValue: log.SliceValue([]log.Value{}...),
},
{
name: "int_array",
value: []int{1, 2, 3},
wantValue: log.SliceValue([]log.Value{
log.Int64Value(1),
log.Int64Value(2),
log.Int64Value(3),
}...),
},
{
name: "key_value_map",
value: map[string]int{"one": 1},
wantValue: log.MapValue(
log.Int64("one", 1),
),
},
{
name: "int_string_map",
value: map[int]string{1: "one"},
wantValue: log.MapValue(
log.String("1", "one"),
),
},
{
name: "nested_map",
value: map[string]map[string]int{"nested": {"one": 1}},
wantValue: log.MapValue(
log.Map("nested",
log.Int64("one", 1),
),
),
},
{
name: "struct_key_map",
value: map[struct{ Name string }]int{
{Name: "John"}: 42,
},
wantValue: log.MapValue(
log.Int64("{Name:John}", 42),
),
},
{
name: "struct",
value: struct {
Name string
Age int
}{
Name: "John",
Age: 42,
},
wantValue: log.StringValue("{Name:John Age:42}"),
},
{
name: "struct_ptr",
value: &struct {
Name string
Age int
}{
Name: "John",
Age: 42,
},
wantValue: log.StringValue("{Name:John Age:42}"),
},
{
name: "nil_struct_ptr",
value: (*struct {
Name string
Age int
})(nil),
wantValue: log.Value{},
},
{
name: "ctx",
value: context.Background(),
wantValue: log.StringValue("context.Background"),
},
{
name: "unhandled type",
value: chan int(nil),
wantValue: log.StringValue("unhandled: (chan int) <nil>"),
},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.wantValue, convertValue(tt.value))
})
}
}

func TestConvertValueFloat32(t *testing.T) {
value := convertValue(float32(3.14))
want := log.Float64Value(3.14)

assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001)
}
8 changes: 8 additions & 0 deletions bridges/otelslog/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otelslog // import "go.opentelemetry.io/contrib/bridges/otelslog"

// Generate convert:
//go:generate gotmpl --body=../../internal/shared/logutil/convert_test.go.tmpl "--data={ \"pkg\": \"otelslog\" }" --out=convert_test.go
//go:generate gotmpl --body=../../internal/shared/logutil/convert.go.tmpl "--data={ \"pkg\": \"otelslog\" }" --out=convert.go
Loading

0 comments on commit 601f98b

Please sign in to comment.