Skip to content

Commit

Permalink
otezap: Add support for zap named loggers (#5896)
Browse files Browse the repository at this point in the history
Add best effort handling for
https://pkg.go.dev/go.uber.org/zap#Logger.Named

No additional heap allocation:

```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/contrib/bridges/otelzap
cpu: Intel(R) Core(TM) i9-10885H CPU @ 2.40GHz
                           │   old.txt    │               new.txt                │
                           │    sec/op    │    sec/op     vs base                │
CoreWrite/10_fields-16       534.0n ± 14%   484.4n ±  4%   -9.29% (p=0.000 n=10)
CoreWrite/20_fields-16       1.628µ ±  7%   1.448µ ± 28%  -11.03% (p=0.000 n=10)
CoreWrite/Namespace-16       1.147µ ±  4%   1.065µ ±  4%   -7.11% (p=0.003 n=10)
CoreWrite/With10_fields-16   169.5n ±  7%   139.1n ±  4%  -17.99% (p=0.000 n=10)
CoreWrite/With20_fields-16   365.7n ±  7%   355.9n ±  6%        ~ (p=0.190 n=10)
CoreWrite/WithNamespace-16   9.114n ± 14%   7.484n ±  3%  -17.89% (p=0.000 n=10)
geomean                      287.4n         255.3n        -11.17%

                           │    old.txt     │                new.txt                │
                           │      B/op      │     B/op      vs base                 │
CoreWrite/10_fields-16         978.0 ± 0%       978.0 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/20_fields-16       2.129Ki ± 0%     2.129Ki ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/Namespace-16       1.719Ki ± 0%     1.719Ki ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/With10_fields-16     208.0 ± 0%       208.0 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/With20_fields-16     640.0 ± 0%       640.0 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/WithNamespace-16     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                   ²                 +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                           │   old.txt    │               new.txt               │
                           │  allocs/op   │ allocs/op   vs base                 │
CoreWrite/10_fields-16       13.00 ± 0%     13.00 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/20_fields-16       22.00 ± 0%     22.00 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/Namespace-16       16.00 ± 0%     16.00 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/With10_fields-16   1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/With20_fields-16   1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=10) ¹
CoreWrite/WithNamespace-16   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                 ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
  • Loading branch information
pellared authored Jul 12, 2024
1 parent 19abe08 commit 5fc9d53
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 24 deletions.
66 changes: 42 additions & 24 deletions bridges/otelzap/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,6 @@ func newConfig(options []Option) config {
return c
}

func (c config) logger(name string) log.Logger {
var opts []log.LoggerOption
if c.version != "" {
opts = append(opts, log.WithInstrumentationVersion(c.version))
}
if c.schemaURL != "" {
opts = append(opts, log.WithSchemaURL(c.schemaURL))
}
return c.provider.Logger(name, opts...)
}

// Option configures a [Core].
type Option interface {
apply(config) config
Expand Down Expand Up @@ -88,20 +77,37 @@ func WithLoggerProvider(provider log.LoggerProvider) Option {

// Core is a [zapcore.Core] that sends logging records to OpenTelemetry.
type Core struct {
logger log.Logger
attr []log.KeyValue
ctx context.Context
provider log.LoggerProvider
logger log.Logger
opts []log.LoggerOption
attr []log.KeyValue
ctx context.Context
}

// Compile-time check *Core implements zapcore.Core.
var _ zapcore.Core = (*Core)(nil)

// NewCore creates a new [zapcore.Core] that can be used with [go.uber.org/zap.New].
// The name should be the package import path that is being logged.
// The name is ignored for named loggers created using [go.uber.org/zap.Logger.Named].
func NewCore(name string, opts ...Option) *Core {
cfg := newConfig(opts)

var loggerOpts []log.LoggerOption
if cfg.version != "" {
loggerOpts = append(loggerOpts, log.WithInstrumentationVersion(cfg.version))
}
if cfg.schemaURL != "" {
loggerOpts = append(loggerOpts, log.WithSchemaURL(cfg.schemaURL))
}

logger := cfg.provider.Logger(name, loggerOpts...)

return &Core{
logger: cfg.logger(name),
ctx: context.Background(),
provider: cfg.provider,
logger: logger,
opts: loggerOpts,
ctx: context.Background(),
}
}

Expand All @@ -127,9 +133,11 @@ func (o *Core) With(fields []zapcore.Field) zapcore.Core {

func (o *Core) clone() *Core {
return &Core{
logger: o.logger,
attr: slices.Clone(o.attr),
ctx: o.ctx,
provider: o.provider,
opts: o.opts,
logger: o.logger,
attr: slices.Clone(o.attr),
ctx: o.ctx,
}
}

Expand All @@ -138,10 +146,18 @@ func (o *Core) Sync() error {
return nil
}

// Check determines whether the supplied Entry should be logged using core.Enabled method.
// Check determines whether the supplied Entry should be logged.
// If the entry should be logged, the Core adds itself to the CheckedEntry and returns the result.
func (o *Core) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if o.Enabled(ent.Level) {
r := log.Record{}
r.SetSeverity(convertLevel(ent.Level))

logger := o.logger
if ent.LoggerName != "" {
logger = o.provider.Logger(ent.LoggerName, o.opts...)
}

if logger.Enabled(context.Background(), r) {
return ce.AddCore(ent, o)
}
return ce
Expand All @@ -155,8 +171,6 @@ func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error {
r.SetSeverity(convertLevel(ent.Level))
r.SetSeverityText(ent.Level.String())

// TODO: Handle ent.LoggerName.

r.AddAttributes(o.attr...)
if len(fields) > 0 {
ctx, attrbuf := convertField(fields)
Expand All @@ -166,7 +180,11 @@ func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error {
r.AddAttributes(attrbuf...)
}

o.logger.Emit(o.ctx, r)
logger := o.logger
if ent.LoggerName != "" {
logger = o.provider.Logger(ent.LoggerName, o.opts...)
}
logger.Emit(o.ctx, r)
return nil
}

Expand Down
17 changes: 17 additions & 0 deletions bridges/otelzap/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ func TestCore(t *testing.T) {

rec.Reset()

t.Run("Named", func(t *testing.T) {
name := "my/pkg"
childlogger := logger.Named(name)
childlogger.Info(testMessage, zap.String(testKey, testValue))

found := false
for _, got := range rec.Result() {
found = got.Name == name
if found {
break
}
}
assert.True(t, found)
})

rec.Reset()

t.Run("WithMultiple", func(t *testing.T) {
testCases := [][]string{{"test1", "value1"}, {"test2", "value2"}, {"test3", "value3"}}
childlogger := logger.With(zap.String(testCases[0][0], testCases[0][1]))
Expand Down

0 comments on commit 5fc9d53

Please sign in to comment.