Skip to content

Commit

Permalink
Add skeleton for zerolog bridge (#5782)
Browse files Browse the repository at this point in the history
This PR introduces the `otelzerolog` package, which provides a
`SeverityHook` to bridge the logging capabilities of Zerolog with
OpenTelemetry. The following key features are included:

## Changes

- Added `config` struct to handle configuration options.
- Implemented `Option` interface and related functions (`WithVersion`,
`WithSchemaURL`, `WithLoggerProvider`).
- Created `NewSeverityHook` function to initialize a `SeverityHook`.
- Defined `SeverityHook` struct to perform the logging bridge
functionality.

Part of
#5405

---------

Co-authored-by: Damien Mathieu <[email protected]>
Co-authored-by: Robert Pająk <[email protected]>
  • Loading branch information
3 people authored Jul 11, 2024
1 parent e2d29ee commit fb316b5
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 0 deletions.
23 changes: 23 additions & 0 deletions bridges/otelzerolog/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otelzerolog_test

import (
"os"

"github.com/rs/zerolog"

"go.opentelemetry.io/contrib/bridges/otelzerolog"
"go.opentelemetry.io/otel/log/noop"
)

func Example() {
// Use a working LoggerProvider implementation instead e.g. using go.opentelemetry.io/otel/sdk/log.
provider := noop.NewLoggerProvider()

// Create a logger that emits logs to both STDOUT and the OTel Go SDK.
hook := otelzerolog.NewHook("my/pkg/name", otelzerolog.WithLoggerProvider(provider))
logger := zerolog.New(os.Stdout).With().Logger()
logger = logger.Hook(hook)
}
23 changes: 23 additions & 0 deletions bridges/otelzerolog/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module go.opentelemetry.io/contrib/bridges/otelzerolog

go 1.21

require (
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel/log v0.3.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/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
golang.org/x/sys v0.12.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
40 changes: 40 additions & 0 deletions bridges/otelzerolog/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
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.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/log v0.3.0 h1:kJRFkpUFYtny37NQzL386WbznUByZx186DpEMKhEGZs=
go.opentelemetry.io/otel/log v0.3.0/go.mod h1:ziCwqZr9soYDwGNbIL+6kAvQC+ANvjgG367HVcyR/ys=
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
104 changes: 104 additions & 0 deletions bridges/otelzerolog/hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package otelzerolog provides a [Hook], a [zerolog.Hook] implementation that
// can be used to bridge between the [zerolog] API and [OpenTelemetry].
// [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/
package otelzerolog // import "go.opentelemetry.io/contrib/bridges/otelzerolog"

import (
"github.com/rs/zerolog"

"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/global"
)

type config struct {
provider log.LoggerProvider
version string
schemaURL string
}

func newConfig(options []Option) config {
var c config
for _, opt := range options {
c = opt.apply(c)
}

if c.provider == nil {
c.provider = global.GetLoggerProvider()
}
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 Hook.
type Option interface {
apply(config) config
}
type optFunc func(config) config

func (f optFunc) apply(c config) config { return f(c) }

// WithVersion returns an [Option] that configures the version of the
// [log.Logger] used by a [Hook]. The version should be the version of the
// package that is being logged.
func WithVersion(version string) Option {
return optFunc(func(c config) config {
c.version = version
return c
})
}

// WithSchemaURL returns an [Option] that configures the semantic convention
// schema URL of the [log.Logger] used by a [Hook]. The schemaURL should be
// the schema URL for the semantic conventions used in log records.
func WithSchemaURL(schemaURL string) Option {
return optFunc(func(c config) config {
c.schemaURL = schemaURL
return c
})
}

// WithLoggerProvider returns an [Option] that configures [log.LoggerProvider]
// used by a [Hook].
//
// By default if this Option is not provided, the Hook will use the global
// LoggerProvider.
func WithLoggerProvider(provider log.LoggerProvider) Option {
return optFunc(func(c config) config {
c.provider = provider
return c
})
}

// Hook is a [zerolog.Hook] that sends all logging records it receives to
// OpenTelemetry. See package documentation for how conversions are made.
type Hook struct {
logger log.Logger
}

// NewHook returns a new [Hook] to be used as a [Zerolog.Hook].
// If [WithLoggerProvider] is not provided, the returned Hook will use the
// global LoggerProvider.
func NewHook(name string, options ...Option) *Hook {
cfg := newConfig(options)
return &Hook{
logger: cfg.logger(name),
}
}

// Run handles the passed record, and sends it to OpenTelemetry.
func (h Hook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
// TODO
}
105 changes: 105 additions & 0 deletions bridges/otelzerolog/hook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelzerolog

import (
"testing"

"github.com/stretchr/testify/assert"

"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/embedded"
"go.opentelemetry.io/otel/log/global"
)

type mockLoggerProvider struct {
embedded.LoggerProvider
}

func (mockLoggerProvider) Logger(name string, options ...log.LoggerOption) log.Logger {
return nil
}

func TestNewConfig(t *testing.T) {
customLoggerProvider := mockLoggerProvider{}

for _, tt := range []struct {
name string
options []Option

wantConfig config
}{
{
name: "with no options",
wantConfig: config{
provider: global.GetLoggerProvider(),
},
},
{
name: "with a custom version",
options: []Option{
WithVersion("1.0"),
},
wantConfig: config{
version: "1.0",
provider: global.GetLoggerProvider(),
},
},
{
name: "with a custom schema URL",
options: []Option{
WithSchemaURL("https://example.com"),
},
wantConfig: config{
schemaURL: "https://example.com",
provider: global.GetLoggerProvider(),
},
},
{
name: "with a custom logger provider",
options: []Option{
WithLoggerProvider(customLoggerProvider),
},
wantConfig: config{
provider: customLoggerProvider,
},
},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.wantConfig, newConfig(tt.options))
})
}
}

func TestNewHook(t *testing.T) {
const name = "test_hook"
provider := global.GetLoggerProvider()

for _, tt := range []struct {
name string
options []Option
wantLogger log.Logger
}{
{
name: "with default options",
wantLogger: provider.Logger(name),
},
{
name: "with version and schema URL",
options: []Option{
WithVersion("1.0"),
WithSchemaURL("https://example.com"),
},
wantLogger: provider.Logger(name,
log.WithInstrumentationVersion("1.0"),
log.WithSchemaURL("https://example.com"),
),
},
} {
t.Run(tt.name, func(t *testing.T) {
hook := NewHook(name, tt.options...)
assert.NotNil(t, hook)
assert.Equal(t, tt.wantLogger, hook.logger)
})
}
}
1 change: 1 addition & 0 deletions versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ module-sets:
modules:
- go.opentelemetry.io/contrib/detectors/azure/azurevm
excluded-modules:
- go.opentelemetry.io/contrib/bridges/otelzerolog
- go.opentelemetry.io/contrib/bridges/otelzap
- go.opentelemetry.io/contrib/instrgen
- go.opentelemetry.io/contrib/instrgen/driver
Expand Down

0 comments on commit fb316b5

Please sign in to comment.