Skip to content

Commit 999dd0b

Browse files
committed
Add caller information to Otel log
1 parent b336d97 commit 999dd0b

File tree

2 files changed

+111
-5
lines changed

2 files changed

+111
-5
lines changed

pkg/logger/otelzap/otelzap.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,13 @@ func (o OtelZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
105105
// Start with encoder attributes
106106
attributes := encoder.attributes
107107

108-
// Add exception metadata
108+
// Add caller information if available
109+
if entry.Caller.Defined {
110+
attributes = append(attributes, attribute.String("caller", entry.Caller.String()))
111+
}
112+
113+
// Add exception metadata for error levels
109114
if entry.Level > zapcore.InfoLevel {
110-
if entry.Caller.Defined {
111-
attributes = append(attributes, semconv.ExceptionType(entry.Caller.String()))
112-
}
113115
if entry.Stack != "" {
114116
attributes = append(attributes, semconv.ExceptionStacktrace(entry.Stack))
115117
}

pkg/logger/otelzap/otelzap_test.go

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package otelzap
22

33
import (
44
"bytes"
5+
"context"
56
"encoding/json"
67
"errors"
8+
"fmt"
79
"math"
810
"testing"
911
"time"
@@ -228,7 +230,7 @@ func TestOtelZapCore_Write(t *testing.T) {
228230
wantMessage: "fail",
229231
wantAttrs: map[string]string{
230232
"err": "fail",
231-
"exception.type": "file.go:42",
233+
"caller": "file.go:42",
232234
"exception.stacktrace": "stacktrace",
233235
},
234236
},
@@ -490,3 +492,105 @@ func (t *testMixedArray) MarshalLogArray(enc zapcore.ArrayEncoder) error {
490492
}
491493
return nil
492494
}
495+
496+
func TestCallerInfo(t *testing.T) {
497+
// Setup OTEL exporter
498+
var buf bytes.Buffer
499+
exporter, err := stdoutlog.New(stdoutlog.WithWriter(&buf))
500+
require.NoError(t, err)
501+
502+
// Setup OTEL provider
503+
provider := sdklog.NewLoggerProvider(sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)))
504+
505+
// Create otel zap core
506+
logger := provider.Logger("test")
507+
core := NewCore(logger)
508+
509+
tests := []struct {
510+
name string
511+
level zapcore.Level
512+
caller zapcore.EntryCaller
513+
wantCaller bool
514+
}{
515+
{
516+
name: "info with caller",
517+
level: zapcore.InfoLevel,
518+
caller: zapcore.EntryCaller{Defined: true, File: "test.go", Line: 123, Function: "TestFunc"},
519+
wantCaller: true,
520+
},
521+
{
522+
name: "error with caller",
523+
level: zapcore.ErrorLevel,
524+
caller: zapcore.EntryCaller{Defined: true, File: "error.go", Line: 456, Function: "ErrorFunc"},
525+
wantCaller: true,
526+
},
527+
{
528+
name: "debug without caller",
529+
level: zapcore.DebugLevel,
530+
caller: zapcore.EntryCaller{Defined: false},
531+
wantCaller: false,
532+
},
533+
}
534+
535+
for _, tt := range tests {
536+
t.Run(tt.name, func(t *testing.T) {
537+
buf.Reset()
538+
539+
entry := zapcore.Entry{
540+
Message: "test message",
541+
Level: tt.level,
542+
Time: time.Now(),
543+
Caller: tt.caller,
544+
}
545+
546+
err := core.Write(entry, nil)
547+
require.NoError(t, err)
548+
549+
// Force flush
550+
require.NoError(t, provider.ForceFlush(context.Background()))
551+
552+
if !tt.wantCaller {
553+
// If no caller expected, we just check that no caller attributes exist
554+
var logEntry struct {
555+
Attributes []struct {
556+
Key string `json:"key"`
557+
Value struct {
558+
Value interface{} `json:"value"`
559+
} `json:"value"`
560+
} `json:"attributes"`
561+
}
562+
err = json.Unmarshal(buf.Bytes(), &logEntry)
563+
require.NoError(t, err)
564+
565+
for _, attr := range logEntry.Attributes {
566+
assert.NotContains(t, []string{"caller", "source.file", "source.line", "source.function"}, attr.Key)
567+
}
568+
return
569+
}
570+
571+
// Parse JSON output and check for caller attributes
572+
var logEntry struct {
573+
Attributes []struct {
574+
Key string `json:"key"`
575+
Value struct {
576+
Value string `json:"value"`
577+
} `json:"value"`
578+
} `json:"attributes"`
579+
}
580+
err = json.Unmarshal(buf.Bytes(), &logEntry)
581+
require.NoError(t, err, "failed to parse OTEL JSON log output")
582+
583+
got := map[string]string{}
584+
for _, attr := range logEntry.Attributes {
585+
got[attr.Key] = attr.Value.Value
586+
}
587+
588+
// Check required caller attributes
589+
assert.Contains(t, got, "caller")
590+
591+
expectedCaller := fmt.Sprintf("%s:%d", tt.caller.File, tt.caller.Line)
592+
assert.Equal(t, expectedCaller, fmt.Sprintf("%v", got["caller"]))
593+
594+
})
595+
}
596+
}

0 commit comments

Comments
 (0)