Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix panic for nil attributes and move convert funcs to internal/shared/logutil #6237

Merged
merged 8 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Fixed

- Transform nil attribute values to `log.Value` zero value instead of panicking in `go.opentelemetry.io/contrib/bridges/otellogrus`. (#6237)
- Transform nil attribute values to `log.Value` zero value instead of panicking in `go.opentelemetry.io/contrib/bridges/otelzap`. (#6237)

<!-- Released section -->
<!-- Don't change this section unless doing release -->

Expand Down
51 changes: 9 additions & 42 deletions bridges/otellogr/convert.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// 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 otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr"

import (
"context"
"fmt"
"math"
"reflect"
Expand All @@ -14,43 +16,8 @@ import (
"go.opentelemetry.io/otel/log"
)

// convertKVs converts a list of key-value pairs to a list of [log.KeyValue].
// The last [context.Context] value is returned as the context.
// If no context is found, the original context is returned.
func convertKVs(ctx context.Context, keysAndValues ...any) (context.Context, []log.KeyValue) {
if len(keysAndValues) == 0 {
return ctx, nil
}
if len(keysAndValues)%2 != 0 {
// Ensure an odd number of items here does not corrupt the list.
keysAndValues = append(keysAndValues, nil)
}

kvs := make([]log.KeyValue, 0, len(keysAndValues)/2)
for i := 0; i < len(keysAndValues); i += 2 {
k, ok := keysAndValues[i].(string)
if !ok {
// Ensure that the key is a string.
k = fmt.Sprintf("%v", keysAndValues[i])
}

v := keysAndValues[i+1]
if vCtx, ok := v.(context.Context); ok {
// Special case when a field is of context.Context type.
ctx = vCtx
continue
}

kvs = append(kvs, log.KeyValue{
Key: k,
Value: convertValue(v),
})
}

return ctx, kvs
}

func convertValue(v any) log.Value {
// Convert 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:
Expand Down Expand Up @@ -112,7 +79,7 @@ func convertValue(v any) log.Value {
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()))
items = append(items, ConvertValue(val.Index(i).Interface()))
}
return log.SliceValue(items...)
case reflect.Map:
Expand All @@ -127,15 +94,15 @@ func convertValue(v any) log.Value {
}
kvs = append(kvs, log.KeyValue{
Key: key,
Value: convertValue(val.MapIndex(k).Interface()),
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())
return ConvertValue(val.Elem().Interface())
}

// Try to handle this as gracefully as possible.
Expand All @@ -146,7 +113,7 @@ func convertValue(v any) log.Value {
return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v))
}

// convertUintValue converts a uint64 to a log.Value.
// 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 {
Expand Down
84 changes: 18 additions & 66 deletions bridges/otellogr/convert_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// 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

Expand All @@ -15,70 +18,6 @@ import (
"go.opentelemetry.io/otel/log"
)

func TestConvertKVs(t *testing.T) {
ctx := context.WithValue(context.Background(), "key", "value") // nolint: revive,staticcheck // test context

for _, tt := range []struct {
name string
kvs []any
wantKVs []log.KeyValue
wantCtx context.Context
}{
{
name: "empty",
kvs: []any{},
},
{
name: "single_value",
kvs: []any{"key", "value"},
wantKVs: []log.KeyValue{
log.String("key", "value"),
},
},
{
name: "multiple_values",
kvs: []any{"key1", "value1", "key2", "value2"},
wantKVs: []log.KeyValue{
log.String("key1", "value1"),
log.String("key2", "value2"),
},
},
{
name: "missing_value",
kvs: []any{"key1", "value1", "key2"},
wantKVs: []log.KeyValue{
log.String("key1", "value1"),
{Key: "key2", Value: log.Value{}},
},
},
{
name: "key_not_string",
kvs: []any{42, "value"},
wantKVs: []log.KeyValue{
log.String("42", "value"),
},
},
{
name: "context",
kvs: []any{"ctx", ctx, "key", "value"},
wantKVs: []log.KeyValue{log.String("key", "value")},
wantCtx: ctx,
},
{
name: "last_context",
kvs: []any{"key", context.Background(), "ctx", ctx},
wantKVs: []log.KeyValue{},
wantCtx: ctx,
},
} {
t.Run(tt.name, func(t *testing.T) {
ctx, kvs := convertKVs(nil, tt.kvs...) // nolint: staticcheck // pass nil context
assert.Equal(t, tt.wantKVs, kvs)
assert.Equal(t, tt.wantCtx, ctx)
})
}
}

func TestConvertValue(t *testing.T) {
for _, tt := range []struct {
name string
Expand Down Expand Up @@ -293,20 +232,33 @@ func TestConvertValue(t *testing.T) {
},
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))
assert.Equal(t, tt.wantValue, ConvertValue(tt.value))
})
}
}

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

assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001)
Expand Down
8 changes: 8 additions & 0 deletions bridges/otellogr/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 otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr"

// Generate convert:
//go:generate gotmpl --body=../../internal/shared/logutil/convert_test.go.tmpl "--data={ \"pkg\": \"otellogr\" }" --out=convert_test.go
//go:generate gotmpl --body=../../internal/shared/logutil/convert.go.tmpl "--data={ \"pkg\": \"otellogr\" }" --out=convert.go
37 changes: 37 additions & 0 deletions bridges/otellogr/logsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ package otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr"

import (
"context"
"fmt"

"github.com/go-logr/logr"

Expand Down Expand Up @@ -198,3 +199,39 @@ func (l LogSink) WithValues(keysAndValues ...any) logr.LogSink {
l.ctx = ctx
return &l
}

// convertKVs converts a list of key-value pairs to a list of [log.KeyValue].
// The last [context.Context] value is returned as the context.
// If no context is found, the original context is returned.
func convertKVs(ctx context.Context, keysAndValues ...any) (context.Context, []log.KeyValue) {
if len(keysAndValues) == 0 {
return ctx, nil
}
if len(keysAndValues)%2 != 0 {
// Ensure an odd number of items here does not corrupt the list.
keysAndValues = append(keysAndValues, nil)
}

kvs := make([]log.KeyValue, 0, len(keysAndValues)/2)
for i := 0; i < len(keysAndValues); i += 2 {
k, ok := keysAndValues[i].(string)
if !ok {
// Ensure that the key is a string.
k = fmt.Sprintf("%v", keysAndValues[i])
}

v := keysAndValues[i+1]
if vCtx, ok := v.(context.Context); ok {
// Special case when a field is of context.Context type.
ctx = vCtx
continue
}

kvs = append(kvs, log.KeyValue{
Key: k,
Value: ConvertValue(v),
})
}

return ctx, kvs
}
80 changes: 80 additions & 0 deletions bridges/otellogr/logsink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package otellogr

import (
"context"
"testing"
"time"

Expand Down Expand Up @@ -216,6 +217,21 @@ func TestLogSink(t *testing.T) {
},
},
},
{
name: "info_with_normal_attr_and_nil_pointer_attr",
f: func(l *logr.Logger) {
var p *int
l.WithValues("key", "value", "nil_pointer", p).Info("info message with attrs")
},
wantRecords: map[string][]log.Record{
name: {
buildRecord(log.StringValue("info message with attrs"), time.Time{}, log.SeverityInfo, []log.KeyValue{
log.String("key", "value"),
log.Empty("nil_pointer"),
}),
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
rec := logtest.NewRecorder()
Expand Down Expand Up @@ -263,3 +279,67 @@ func assertRecords(t *testing.T, want, got []logtest.EmittedRecord) {
logtest.AssertRecordEqual(t, j.Record, got[i].Record)
}
}

func TestConvertKVs(t *testing.T) {
ctx := context.WithValue(context.Background(), "key", "value") // nolint: revive,staticcheck // test context

for _, tt := range []struct {
name string
kvs []any
wantKVs []log.KeyValue
wantCtx context.Context
}{
{
name: "empty",
kvs: []any{},
},
{
name: "single_value",
kvs: []any{"key", "value"},
wantKVs: []log.KeyValue{
log.String("key", "value"),
},
},
{
name: "multiple_values",
kvs: []any{"key1", "value1", "key2", "value2"},
wantKVs: []log.KeyValue{
log.String("key1", "value1"),
log.String("key2", "value2"),
},
},
{
name: "missing_value",
kvs: []any{"key1", "value1", "key2"},
wantKVs: []log.KeyValue{
log.String("key1", "value1"),
{Key: "key2", Value: log.Value{}},
},
},
{
name: "key_not_string",
kvs: []any{42, "value"},
wantKVs: []log.KeyValue{
log.String("42", "value"),
},
},
{
name: "context",
kvs: []any{"ctx", ctx, "key", "value"},
wantKVs: []log.KeyValue{log.String("key", "value")},
wantCtx: ctx,
},
{
name: "last_context",
kvs: []any{"key", context.Background(), "ctx", ctx},
wantKVs: []log.KeyValue{},
wantCtx: ctx,
},
} {
t.Run(tt.name, func(t *testing.T) {
ctx, kvs := convertKVs(nil, tt.kvs...) // nolint: staticcheck // pass nil context
assert.Equal(t, tt.wantKVs, kvs)
assert.Equal(t, tt.wantCtx, ctx)
})
}
}
Loading
Loading