Skip to content

Commit

Permalink
Add skeleton for otellogr bridge (#6097)
Browse files Browse the repository at this point in the history
This PR is the skeleton for otellogr package, which provides a LogSink
to bridge the logging capabilities for Logr with OpenTelemetry

Part of #5192

---------

Co-authored-by: Robert Pająk <[email protected]>
  • Loading branch information
scorpionknifes and pellared authored Sep 12, 2024
1 parent 4ccc9c6 commit 4603866
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ bridges/otelslog @open-te
bridges/otellogrus/ @open-telemetry/go-approvers @dmathieu @pellared
bridges/prometheus/ @open-telemetry/go-approvers @dashpole
bridges/otelzap/ @open-telemetry/go-approvers @pellared @khushijain21
bridges/otellogr/ @open-telemetry/go-approvers @scorpionknifes @pellared

config/ @open-telemetry/go-approvers @MadVikingGod @pellared @codeboten

Expand Down
22 changes: 22 additions & 0 deletions bridges/otellogr/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package otellogr_test

import (
"github.com/go-logr/logr"

"go.opentelemetry.io/contrib/bridges/otellogr"
"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 an logr.Logger with *otellogr.LogSink and use it in your application.
logr.New(otellogr.NewLogSink(
"my/pkg/name",
otellogr.WithLoggerProvider(provider)),
)
}
19 changes: 19 additions & 0 deletions bridges/otellogr/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module go.opentelemetry.io/contrib/bridges/otellogr

go 1.22

require (
github.com/go-logr/logr v1.4.2
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel/log v0.5.0
)

require (
github.com/davecgh/go-spew v1.1.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.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
25 changes: 25 additions & 0 deletions bridges/otellogr/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/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.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/log v0.5.0 h1:x1Pr6Y3gnXgl1iFBwtGy1W/mnzENoK0w0ZoaeOI3i30=
go.opentelemetry.io/otel/log v0.5.0/go.mod h1:NU/ozXeGuOR5/mjCRXYbTC00NFJ3NYuraV/7O78F0rE=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
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=
146 changes: 146 additions & 0 deletions bridges/otellogr/logsink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

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

import (
"github.com/go-logr/logr"

"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 [LogSink].
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 [LogSink] to create its [log.Logger].
//
// By default if this Option is not provided, the LogSink will use the global
// LoggerProvider.
func WithLoggerProvider(provider log.LoggerProvider) Option {
return optFunc(func(c config) config {
c.provider = provider
return c
})
}

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

// LogSink is a [logr.LogSink] that sends all logging records it receives to
// OpenTelemetry. See package documentation for how conversions are made.
type LogSink struct {
// Ensure forward compatibility by explicitly making this not comparable.
noCmp [0]func() //nolint: unused // This is indeed used.

name string
logger log.Logger
}

// Compile-time check *Handler implements logr.LogSink.
var _ logr.LogSink = (*LogSink)(nil)

// Enabled tests whether this LogSink is enabled at the specified V-level.
// For example, commandline flags might be used to set the logging
// verbosity and disable some info logs.
func (l *LogSink) Enabled(level int) bool {
// TODO
return true
}

// Error logs an error, with the given message and key/value pairs.
func (l *LogSink) Error(err error, msg string, keysAndValues ...any) {
// TODO
}

// Info logs a non-error message with the given key/value pairs.
func (l *LogSink) Info(level int, msg string, keysAndValues ...any) {
// TODO
}

// Init initializes the LogSink.
func (l *LogSink) Init(info logr.RuntimeInfo) {
// TODO
}

// WithName returns a new LogSink with the specified name appended.
func (l LogSink) WithName(name string) logr.LogSink {
// TODO
return &l
}

// WithValues returns a new LogSink with additional key/value pairs.
func (l LogSink) WithValues(keysAndValues ...any) logr.LogSink {
// TODO
return &l
}
98 changes: 98 additions & 0 deletions bridges/otellogr/logsink_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otellogr

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 instrumentation scope",
options: []Option{
WithVersion("42.0"),
},

wantConfig: config{
version: "42.0",
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 TestNewLogSink(t *testing.T) {
const name = "test_logsink"
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 := NewLogSink(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/otellogr
- go.opentelemetry.io/contrib/instrgen
- go.opentelemetry.io/contrib/instrgen/driver
- go.opentelemetry.io/contrib/instrgen/testdata/interface

0 comments on commit 4603866

Please sign in to comment.