Skip to content

Commit

Permalink
Add basic unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
scorpionknifes committed Apr 6, 2024
1 parent 1023952 commit d3c70ae
Show file tree
Hide file tree
Showing 4 changed files with 521 additions and 63 deletions.
4 changes: 4 additions & 0 deletions bridges/otellogr/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ go 1.21

require (
github.com/go-logr/logr v1.4.1
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel/log v0.0.1-alpha.0.20240319182811-335f4de960ff
go.opentelemetry.io/otel/sdk v1.24.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions bridges/otellogr/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucg
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
89 changes: 39 additions & 50 deletions bridges/otellogr/logsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr"
import (
"context"
"fmt"
"math"
"reflect"
"strconv"
"time"
Expand All @@ -19,11 +20,23 @@ const (

// nameKey is used to log the `WithName` values as an additional attribute.
nameKey = "logger"

// errKey is used to log the error parameter of Error as an additional attribute.
errKey = "err"
)

// NewLogSink returns a new [LogSink] to be used as a [logr.LogSink].
//
// If [WithLoggerProvider] is not provided, the returned LogSink will use the
// global LoggerProvider.
func NewLogSink(options ...Option) *LogSink {
c := newConfig(options)
return &LogSink{
logger: c.logger(),
}
}

// LogSink is a [logr.LogSink] that sends all logging records it receives to
// OpenTelemetry. See package documentation for how conversions are made.
type LogSink struct {
name string
logger log.Logger
Expand All @@ -33,13 +46,7 @@ type LogSink struct {
// Compile-time check *Handler implements logr.LogSink.
var _ logr.LogSink = (*LogSink)(nil)

func NewLogSink(options ...Option) *LogSink {
c := newConfig(options)
return &LogSink{
logger: c.logger(),
}
}

// log sends a log record to the OpenTelemetry logger.
func (l *LogSink) log(err error, msg string, serverity log.Severity, kvList ...any) {
var record log.Record
record.SetTimestamp(time.Now())
Expand All @@ -61,7 +68,7 @@ func (l *LogSink) log(err error, msg string, serverity log.Severity, kvList ...a
record.AddAttributes(l.values...)
}

kv := convertKVList(kvList)
kv := convertKVs(kvList)
if len(kv) > 0 {
record.AddAttributes(kv...)
}
Expand All @@ -81,15 +88,14 @@ func (l *LogSink) Enabled(level int) bool {
return l.logger.Enabled(ctx, record)
}

// Error logs an error, with the given message and key/value pairs as
// context.
// Error logs an error, with the given message and key/value pairs.
func (l *LogSink) Error(err error, msg string, keysAndValues ...any) {
const severity = log.SeverityError

l.log(err, msg, severity, keysAndValues...)
}

// Info logs a non-error message with the given key/value pairs as context.
// Info logs a non-error message with the given key/value pairs.
func (l *LogSink) Info(level int, msg string, keysAndValues ...any) {
const sevOffset = int(log.SeverityInfo)
severity := log.Severity(sevOffset + level)
Expand All @@ -102,7 +108,7 @@ func (l *LogSink) Info(level int, msg string, keysAndValues ...any) {
func (l *LogSink) Init(info logr.RuntimeInfo) {
// We don't need to do anything here.
// CallDepth is used to calculate the caller's PC.
// PC is dropped.
// PC is dropped as part of the conversion to the OpenTelemetry log.Record.
}

// WithName returns a new LogSink with the specified name appended.
Expand All @@ -116,36 +122,36 @@ func (l LogSink) WithName(name string) logr.LogSink {

// WithValues returns a new LogSink with additional key/value pairs.
func (l LogSink) WithValues(keysAndValues ...any) logr.LogSink {
attrs := convertKVList(keysAndValues)
attrs := convertKVs(keysAndValues)
l.values = append(l.values, attrs...)
return &l
}

func convertKVList(kvList []any) []log.KeyValue {
if len(kvList) == 0 {
func convertKVs(keysAndValues []any) []log.KeyValue {
if len(keysAndValues) == 0 {
return nil
}
if len(kvList)%2 != 0 {
if len(keysAndValues)%2 != 0 {
// Ensure an odd number of items here does not corrupt the list
kvList = append(kvList, nil)
keysAndValues = append(keysAndValues, nil)
}

kv := make([]log.KeyValue, 0, len(kvList)/2)
for i := 0; i < len(kvList); i += 2 {
k, ok := kvList[i].(string)
kv := 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", kvList[i])
k = fmt.Sprintf("%v", keysAndValues[i])
}
kv = append(kv, log.KeyValue{
Key: k,
Value: convertValue(kvList[i+1]),
Value: convertValue(keysAndValues[i+1]),
})
}
return kv
}

func convertValue(v interface{}) 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 All @@ -163,29 +169,27 @@ func convertValue(v interface{}) log.Value {
case int64:
return log.Int64Value(val)
case uint:
return assignUintValue(uint64(val))
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 assignUintValue(val)
return convertUintValue(val)
case uintptr:
return assignUintValue(uint64(val))
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:
stringValue := `"` + strconv.FormatComplex(complex128(val), 'f', -1, 64) + `"`
return log.StringValue(stringValue)
return log.StringValue(strconv.FormatComplex(complex128(val), 'f', -1, 64))
case complex128:
stringValue := `"` + strconv.FormatComplex(val, 'f', -1, 128) + `"`
return log.StringValue(stringValue)
return log.StringValue(strconv.FormatComplex(val, 'f', -1, 128))
case time.Time:
return log.Int64Value(val.UnixNano())
case []byte:
Expand All @@ -200,19 +204,6 @@ func convertValue(v interface{}) log.Value {
}
val := reflect.ValueOf(v)
switch t.Kind() {
case reflect.Bool:
return log.BoolValue(val.Bool())
case reflect.String:
return log.StringValue(val.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return log.Int64Value(val.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return assignUintValue(val.Uint())
case reflect.Float32, reflect.Float64:
return log.Float64Value(val.Float())
case reflect.Complex64, reflect.Complex128:
stringValue := `"` + strconv.FormatComplex(complex128(val.Complex()), 'f', -1, 64) + `"`
return log.StringValue(stringValue)
case reflect.Struct:
return log.StringValue(fmt.Sprintf("%+v", v))
case reflect.Slice, reflect.Array:
Expand All @@ -225,7 +216,7 @@ func convertValue(v interface{}) log.Value {
kvs := make([]log.KeyValue, 0, val.Len())
for _, k := range val.MapKeys() {
kvs = append(kvs, log.KeyValue{
Key: k.String(),
Key: fmt.Sprintf("%v", k.Interface()),
Value: convertValue(val.MapIndex(k).Interface()),
})
}
Expand All @@ -245,11 +236,9 @@ func convertValue(v interface{}) log.Value {
return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v))
}

func assignUintValue(v uint64) log.Value {
const maxInt64 = ^uint64(0) >> 1
if v > maxInt64 {
value := strconv.FormatUint(v, 10)
return log.StringValue(value)
func convertUintValue(v uint64) log.Value {
if v > math.MaxInt64 {
return log.StringValue(strconv.FormatUint(v, 10))
}
return log.Int64Value(int64(v))
}
Loading

0 comments on commit d3c70ae

Please sign in to comment.