diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7d956abcb08..b9f99171d66 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -109,6 +109,15 @@ updates: schedule: interval: weekly day: sunday + - package-ecosystem: gomod + directory: /bridges/otelzap + labels: + - dependencies + - go + - Skip Changelog + schedule: + interval: weekly + day: sunday - package-ecosystem: gomod directory: /bridges/prometheus labels: diff --git a/CODEOWNERS b/CODEOWNERS index 3810d0a66ad..5633283d58d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -24,6 +24,7 @@ CODEOWNERS @MrAlias @MadVikingGod @pellared @dashpole bridges/otelslog @open-telemetry/go-approvers @MrAlias @pellared bridges/prometheus/ @open-telemetry/go-approvers @dashpole +bridges/otelzap/ @open-telemetry/go-approvers @pellared @khushijain21 config/ @open-telemetry/go-approvers @MadVikingGod @pellared @codeboten diff --git a/bridges/otelzap/core.go b/bridges/otelzap/core.go new file mode 100644 index 00000000000..1e934666c58 --- /dev/null +++ b/bridges/otelzap/core.go @@ -0,0 +1,136 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package otelzap provides a bridge between the [go.uber.org/zap] and +// OpenTelemetry logging. +package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" + +import ( + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/global" + "go.opentelemetry.io/otel/sdk/instrumentation" +) + +const ( + bridgeName = "go.opentelemetry.io/contrib/bridges/otelzap" +) + +type config struct { + provider log.LoggerProvider + scope instrumentation.Scope +} + +func newConfig(options []Option) config { + var c config + for _, opt := range options { + c = opt.apply(c) + } + + var emptyScope instrumentation.Scope + if c.scope == emptyScope { + c.scope = instrumentation.Scope{ + Name: bridgeName, + Version: version, + } + } + + if c.provider == nil { + c.provider = global.GetLoggerProvider() + } + + return c +} + +func (c config) logger() log.Logger { + var opts []log.LoggerOption + if c.scope.Version != "" { + opts = append(opts, log.WithInstrumentationVersion(c.scope.Version)) + } + if c.scope.SchemaURL != "" { + opts = append(opts, log.WithSchemaURL(c.scope.SchemaURL)) + } + return c.provider.Logger(c.scope.Name, opts...) +} + +// Option configures a [Core]. +type Option interface { + apply(config) config +} + +type optFunc func(config) config + +func (f optFunc) apply(c config) config { return f(c) } + +// WithInstrumentationScope returns an [Option] that configures the scope of +// the [log.Logger] used by a [Core]. +// +// By default if this Option is not provided, [Core] will use a default +// instrumentation scope describing this bridge package. It is recommended to +// provide this so log data can be associated with its source package or +// module. +func WithInstrumentationScope(scope instrumentation.Scope) Option { + return optFunc(func(c config) config { + c.scope = scope + return c + }) +} + +// WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] +// used by a [Core] to create its [log.Logger]. +// +// By default if this Option is not provided, the Handler will use the global +// LoggerProvider. +func WithLoggerProvider(provider log.LoggerProvider) Option { + return optFunc(func(c config) config { + c.provider = provider + return c + }) +} + +// Core is a [zapcore.Core] that sends logging records to OpenTelemetry. +type Core struct { + logger log.Logger +} + +// 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]. +func NewCore(opts ...Option) *Core { + cfg := newConfig(opts) + return &Core{ + logger: cfg.logger(), + } +} + +// TODO +// LevelEnabler decides whether a given logging level is enabled when logging a message. +func (o *Core) Enabled(level zapcore.Level) bool { + return true +} + +// TODO +// With adds structured context to the Core. +func (o *Core) With(fields []zapcore.Field) zapcore.Core { + return o +} + +// TODO +// Sync flushes buffered logs (if any). +func (o *Core) Sync() error { + return nil +} + +// TODO +// Check determines whether the supplied Entry should be logged using core.Enabled method. +func (o *Core) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + return ce +} + +// TODO +// Write method encodes zap fields to OTel logs and emits them. +func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error { + return nil +} diff --git a/bridges/otelzap/core_test.go b/bridges/otelzap/core_test.go new file mode 100644 index 00000000000..9d41da03d20 --- /dev/null +++ b/bridges/otelzap/core_test.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelzap + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/log/global" + "go.opentelemetry.io/otel/log/logtest" + "go.opentelemetry.io/otel/sdk/instrumentation" +) + +func TestNewCoreConfiguration(t *testing.T) { + t.Run("Default", func(t *testing.T) { + r := logtest.NewRecorder() + prev := global.GetLoggerProvider() + defer global.SetLoggerProvider(prev) + global.SetLoggerProvider(r) + + var h *Core + require.NotPanics(t, func() { h = NewCore() }) + require.NotNil(t, h.logger) + require.IsType(t, &logtest.Recorder{}, h.logger) + l := h.logger.(*logtest.Recorder) + require.Len(t, l.Result(), 1) + + want := &logtest.ScopeRecords{Name: bridgeName, Version: version} + got := l.Result()[0] + assert.Equal(t, want, got) + }) + + t.Run("Options", func(t *testing.T) { + r := logtest.NewRecorder() + scope := instrumentation.Scope{Name: "name", Version: "ver", SchemaURL: "url"} + var h *Core + require.NotPanics(t, func() { + h = NewCore( + WithLoggerProvider(r), + WithInstrumentationScope(scope), + ) + }) + require.NotNil(t, h.logger) + require.IsType(t, &logtest.Recorder{}, h.logger) + l := h.logger.(*logtest.Recorder) + require.Len(t, l.Result(), 1) + + want := &logtest.ScopeRecords{Name: scope.Name, Version: scope.Version, SchemaURL: scope.SchemaURL} + got := l.Result()[0] + assert.Equal(t, want, got) + }) +} diff --git a/bridges/otelzap/go.mod b/bridges/otelzap/go.mod new file mode 100644 index 00000000000..51781478671 --- /dev/null +++ b/bridges/otelzap/go.mod @@ -0,0 +1,22 @@ +module go.opentelemetry.io/contrib/bridges/otelzap + +go 1.21 + +require ( + github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/otel/log v0.2.0-alpha + go.opentelemetry.io/otel/sdk v1.26.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.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.26.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/bridges/otelzap/go.sum b/bridges/otelzap/go.sum new file mode 100644 index 00000000000..91b4bbf7fa1 --- /dev/null +++ b/bridges/otelzap/go.sum @@ -0,0 +1,33 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel/log v0.2.0-alpha h1:ixOPvMzserpqA07SENHvRzkZOsnG0XbPr74hv1AQ+n0= +go.opentelemetry.io/otel/log v0.2.0-alpha/go.mod h1:vbFZc65yq4c4ssvXY43y/nIqkNJLxORrqw0L85P59LA= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +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= diff --git a/bridges/otelzap/version.go b/bridges/otelzap/version.go new file mode 100644 index 00000000000..2c3a0431ac4 --- /dev/null +++ b/bridges/otelzap/version.go @@ -0,0 +1,7 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" + +// Version is the current release version of otelzap in use. +const version = "0.1.0" diff --git a/versions.yaml b/versions.yaml index 2569e6d74a4..b49942eacbc 100644 --- a/versions.yaml +++ b/versions.yaml @@ -82,6 +82,7 @@ module-sets: modules: - go.opentelemetry.io/contrib/processors/baggage/baggagetrace excluded-modules: + - go.opentelemetry.io/contrib/bridges/otelzap - go.opentelemetry.io/contrib/instrgen - go.opentelemetry.io/contrib/instrgen/driver - go.opentelemetry.io/contrib/instrgen/testdata/interface