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

otelzap: Add caller and stacktrace to attributes if present #6268

Merged
merged 14 commits into from
Oct 24, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
For example, `[]string{"foo", "bar"}` attribute value is now transformed to `log.SliceValue(log.StringValue("foo"), log.StringValue("bar"))` instead of `log.String("[foo bar"])`. (#6254)
- Add the `WithSource` option to the `go.opentelemetry.io/contrib/bridges/otelslog` log bridge to set the `code.*` attributes in the log record that includes the source location where the record was emitted. (#6253)
- Add `ContextWithStartTime` and `StartTimeFromContext` to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, which allows setting the start time using go context. (#6137)
- Set the `code.*` attributes in `go.opentelemetry.io/contrib/bridges/otelzap` if the `zap.Logger` was created with the `AddCaller` or `AddStacktrace` option. (#6268)

### Fixed

Expand Down
11 changes: 11 additions & 0 deletions bridges/otelzap/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (

"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/global"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

type config struct {
Expand Down Expand Up @@ -200,6 +201,16 @@ func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error {
r.SetSeverityText(ent.Level.String())

r.AddAttributes(o.attr...)
if ent.Caller.Defined {
r.AddAttributes(
log.String(string(semconv.CodeFilepathKey), ent.Caller.File),
log.Int(string(semconv.CodeLineNumberKey), ent.Caller.Line),
log.String(string(semconv.CodeFunctionKey), ent.Caller.Function),
)
}
if ent.Stack != "" {
r.AddAttributes(log.String(string(semconv.CodeStacktraceKey), ent.Stack))
}
if len(fields) > 0 {
ctx, attrbuf := convertField(fields)
if ctx != nil {
Expand Down
45 changes: 45 additions & 0 deletions bridges/otelzap/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/log/logtest"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

var (
Expand Down Expand Up @@ -164,6 +165,50 @@ func TestCoreEnabled(t *testing.T) {
assert.Equal(t, zap.InfoLevel.String(), got.SeverityText())
}

func TestCoreWithCaller(t *testing.T) {
rec := logtest.NewRecorder()
zc := NewCore(loggerName, WithLoggerProvider(rec))
logger := zap.New(zc, zap.AddCaller())

logger.Info(testMessage)
got := rec.Result()[0].Records[0]
assert.Equal(t, testMessage, got.Body().AsString())
assert.Equal(t, log.SeverityInfo, got.Severity())
assert.Equal(t, zap.InfoLevel.String(), got.SeverityText())
assert.Equal(t, 3, got.AttributesLen())
got.WalkAttributes(func(kv log.KeyValue) bool {
switch kv.Key {
case string(semconv.CodeFilepathKey):
assert.Contains(t, kv.Value.AsString(), "core_test.go")
case string(semconv.CodeLineNumberKey):
assert.Positive(t, kv.Value.AsInt64())
case string(semconv.CodeFunctionKey):
assert.Contains(t, kv.Value.AsString(), "TestCoreWithCaller")
default:
assert.Fail(t, "unexpected attribute key", kv.Key)
}
return true
})
}

func TestCoreWithStacktrace(t *testing.T) {
rec := logtest.NewRecorder()
zc := NewCore(loggerName, WithLoggerProvider(rec))
logger := zap.New(zc, zap.AddStacktrace(zapcore.ErrorLevel))

logger.Error(testMessage)
got := rec.Result()[0].Records[0]
assert.Equal(t, testMessage, got.Body().AsString())
assert.Equal(t, log.SeverityError, got.Severity())
assert.Equal(t, zap.ErrorLevel.String(), got.SeverityText())
assert.Equal(t, 1, got.AttributesLen())
got.WalkAttributes(func(kv log.KeyValue) bool {
assert.Equal(t, string(semconv.CodeStacktraceKey), kv.Key)
assert.NotEmpty(t, kv.Value.AsString())
return true
})
}

func TestNewCoreConfiguration(t *testing.T) {
t.Run("Default", func(t *testing.T) {
r := logtest.NewRecorder()
Expand Down
Loading