Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
217 changes: 217 additions & 0 deletions pkg/logger/otelzap/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package otelzap

import (
"fmt"
"time"

"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap/zapcore"
)

// otelAttrEncoder implements zapcore.ObjectEncoder to encode zap fields into OpenTelemetry attributes
type otelAttrEncoder struct {
attributes []attribute.KeyValue
namespace string
}

// otelArrayEncoder implements zapcore.ArrayEncoder to collect array elements as strings
type otelArrayEncoder struct {
elements []string
}

func (a *otelArrayEncoder) AppendString(v string) { a.elements = append(a.elements, v) }
func (a *otelArrayEncoder) AppendInt64(v int64) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendInt(v int) { a.elements = append(a.elements, fmt.Sprintf("%d", v)) }
func (a *otelArrayEncoder) AppendInt32(v int32) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendInt16(v int16) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendInt8(v int8) { a.elements = append(a.elements, fmt.Sprintf("%d", v)) }
func (a *otelArrayEncoder) AppendUint64(v uint64) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendUint32(v uint32) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendUint16(v uint16) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendUint8(v uint8) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendUint(v uint) { a.elements = append(a.elements, fmt.Sprintf("%d", v)) }
func (a *otelArrayEncoder) AppendUintptr(v uintptr) {
a.elements = append(a.elements, fmt.Sprintf("%d", v))
}
func (a *otelArrayEncoder) AppendFloat64(v float64) {
a.elements = append(a.elements, fmt.Sprintf("%g", v))
}
func (a *otelArrayEncoder) AppendFloat32(v float32) {
a.elements = append(a.elements, fmt.Sprintf("%g", v))
}
func (a *otelArrayEncoder) AppendBool(v bool) { a.elements = append(a.elements, fmt.Sprintf("%t", v)) }
func (a *otelArrayEncoder) AppendArray(zapcore.ArrayMarshaler) error {
a.elements = append(a.elements, "[nested array]")
return nil
}
func (a *otelArrayEncoder) AppendObject(zapcore.ObjectMarshaler) error {
a.elements = append(a.elements, "[object]")
return nil
}
func (a *otelArrayEncoder) AppendReflected(v any) error {
a.elements = append(a.elements, fmt.Sprintf("%+v", v))
return nil
}
func (a *otelArrayEncoder) AppendByteString(v []byte) { a.elements = append(a.elements, string(v)) }
func (a *otelArrayEncoder) AppendComplex128(v complex128) {
a.elements = append(a.elements, fmt.Sprintf("%v", v))
}
func (a *otelArrayEncoder) AppendComplex64(v complex64) {
a.elements = append(a.elements, fmt.Sprintf("%v", v))
}
func (a *otelArrayEncoder) AppendDuration(v time.Duration) {
a.elements = append(a.elements, v.String())
}
func (a *otelArrayEncoder) AppendTime(v time.Time) {
a.elements = append(a.elements, v.Format(time.RFC3339))
}

func (e *otelAttrEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
// Create a simple array encoder that converts everything to strings
encoder := &otelArrayEncoder{}
err := marshaler.MarshalLogArray(encoder)
if err != nil {
return err
}

e.attributes = append(e.attributes, attribute.StringSlice(e.prefixKey(key), encoder.elements))
return nil
}

func (e *otelAttrEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
// Create a nested encoder for the object
objectEncoder := &otelAttrEncoder{}
err := marshaler.MarshalLogObject(objectEncoder)
if err != nil {
return err
}

// Add all attributes from the object with the key as prefix
for _, attr := range objectEncoder.attributes {
prefixedKey := key + "." + string(attr.Key)
e.attributes = append(e.attributes, attribute.KeyValue{
Key: attribute.Key(prefixedKey),
Value: attr.Value,
})
}
return nil
}

func (e *otelAttrEncoder) AddBinary(key string, value []byte) {
e.attributes = append(e.attributes, attribute.String(e.prefixKey(key), string(value)))
}

func (e *otelAttrEncoder) AddBool(key string, value bool) {
e.attributes = append(e.attributes, attribute.Bool(e.prefixKey(key), value))
}

func (e *otelAttrEncoder) AddByteString(key string, value []byte) {
e.attributes = append(e.attributes, attribute.String(e.prefixKey(key), string(value)))
}

func (e *otelAttrEncoder) AddComplex128(key string, value complex128) {
e.attributes = append(e.attributes, attribute.String(e.prefixKey(key), fmt.Sprintf("%v", value)))
}

func (e *otelAttrEncoder) AddComplex64(key string, value complex64) {
e.attributes = append(e.attributes, attribute.String(e.prefixKey(key), fmt.Sprintf("%v", value)))
}

func (e *otelAttrEncoder) AddDuration(key string, value time.Duration) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddFloat64(key string, value float64) {
e.attributes = append(e.attributes, attribute.Float64(e.prefixKey(key), value))
}

func (e *otelAttrEncoder) AddFloat32(key string, value float32) {
e.attributes = append(e.attributes, attribute.Float64(e.prefixKey(key), float64(value)))
}

func (e *otelAttrEncoder) AddInt(key string, value int) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddInt64(key string, value int64) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), value))
}

func (e *otelAttrEncoder) AddInt32(key string, value int32) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddInt16(key string, value int16) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddInt8(key string, value int8) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddString(key string, value string) {
e.attributes = append(e.attributes, attribute.String(e.prefixKey(key), value))
}

func (e *otelAttrEncoder) AddTime(key string, value time.Time) {
e.attributes = append(e.attributes, attribute.String(e.prefixKey(key), value.Format(time.RFC3339)))
}

func (e *otelAttrEncoder) AddUint(key string, value uint) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddUint64(key string, value uint64) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddUint32(key string, value uint32) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddUint16(key string, value uint16) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddUint8(key string, value uint8) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddUintptr(key string, value uintptr) {
e.attributes = append(e.attributes, attribute.Int64(e.prefixKey(key), int64(value)))
}

func (e *otelAttrEncoder) AddReflected(key string, value any) error {
e.attributes = append(e.attributes, attribute.String(e.prefixKey(key), fmt.Sprintf("%+v", value)))
return nil
}

func (e *otelAttrEncoder) OpenNamespace(key string) {
if e.namespace == "" {
e.namespace = key
} else {
e.namespace = e.namespace + "." + key
}
}

// helper method to apply namespace prefix to keys
func (e *otelAttrEncoder) prefixKey(key string) string {
if e.namespace == "" {
return key
}
return e.namespace + "." + key
}
65 changes: 6 additions & 59 deletions pkg/logger/otelzap/otelzap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package otelzap
import (
"context"
"fmt"
"math"
"time"

"go.opentelemetry.io/otel/attribute"
Expand Down Expand Up @@ -84,7 +83,7 @@ func WithLevelEnabler(levelEnabler zapcore.LevelEnabler) Option {
}

func (o OtelZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
var attributes []attribute.KeyValue
encoder := &otelAttrEncoder{}
var spanCtx *oteltrace.SpanContext

// Add core-attached fields
Expand All @@ -94,15 +93,18 @@ func (o OtelZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
spanCtx = &ctxValue
}
} else {
attributes = append(attributes, mapZapField(f))
f.AddTo(encoder)
}
}

// Add fields passed during log call
for _, f := range fields {
attributes = append(attributes, mapZapField(f))
f.AddTo(encoder)
}

// Start with encoder attributes
attributes := encoder.attributes

// Add exception metadata
if entry.Level > zapcore.InfoLevel {
if entry.Caller.Defined {
Expand Down Expand Up @@ -146,61 +148,6 @@ func (o OtelZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
return nil
}

func mapZapField(f zapcore.Field) attribute.KeyValue {
switch f.Type {
case zapcore.StringType:
return attribute.String(f.Key, f.String)

case zapcore.Int64Type, zapcore.Int32Type, zapcore.Int16Type, zapcore.Int8Type:
return attribute.Int64(f.Key, f.Integer)

case zapcore.Uint64Type, zapcore.Uint32Type, zapcore.Uint16Type, zapcore.Uint8Type, zapcore.UintptrType:
return attribute.Int64(f.Key, int64(f.Integer))

case zapcore.BoolType:
return attribute.Bool(f.Key, f.Integer == 1)

case zapcore.Float64Type:
return attribute.Float64(f.Key, math.Float64frombits(uint64(f.Integer)))

case zapcore.ErrorType:
if err, ok := f.Interface.(error); ok {
return attribute.String(f.Key, err.Error())
}
return attribute.String(f.Key, "invalid error field")

case zapcore.StringerType:
return attribute.String(f.Key, f.Interface.(fmt.Stringer).String())

case zapcore.TimeType:
if t, ok := f.Interface.(time.Time); ok {
return attribute.String(f.Key, t.Format(time.RFC3339))
}
return attribute.String(f.Key, fmt.Sprintf("invalid time: %v", f.Interface))

case zapcore.DurationType:
if d, ok := f.Interface.(time.Duration); ok {
return attribute.String(f.Key, d.String())
}
return attribute.String(f.Key, fmt.Sprintf("invalid duration: %v", f.Interface))

case zapcore.BinaryType:
if b, ok := f.Interface.([]byte); ok {
return attribute.String(f.Key, fmt.Sprintf("binary data: %x", b))
}
return attribute.String(f.Key, fmt.Sprintf("invalid binary: %v", f.Interface))

case zapcore.ByteStringType:
if b, ok := f.Interface.([]byte); ok {
return attribute.String(f.Key, fmt.Sprintf("byte string: %x", b))
}
return attribute.String(f.Key, fmt.Sprintf("invalid byte string: %v", f.Interface))

default:
return attribute.String(f.Key, f.String)
}
}

func mapZapSeverity(level zapcore.Level) otellog.Severity {
switch level {
case zapcore.DebugLevel:
Expand Down
Loading
Loading