You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
Add global log package (#5085)
* Add the log/global package * Implement the stubbed features * Add ConcurrentSafe tests * Restructure with internal implementation * Add internal global state * Use internal state in log/global * Add TestDelegation * Fix lint * Clean log_test.go * Clean up * Add changelog entry * Simplify TestMultipleGlobalLoggerProvider * Shorten log.go * Fix comment text wrapping * Shorten state_test.go * Don't pollute output in TestSetLoggerProvider
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package global // import "go.opentelemetry.io/otel/log/internal/global"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/log/embedded"
|
||||
)
|
||||
|
||||
// instLib defines the instrumentation library a logger is created for.
|
||||
//
|
||||
// Do not use sdk/instrumentation (API cannot depend on the SDK).
|
||||
type instLib struct{ name, version string }
|
||||
|
||||
type loggerProvider struct {
|
||||
embedded.LoggerProvider
|
||||
|
||||
mu sync.Mutex
|
||||
loggers map[instLib]*logger
|
||||
delegate log.LoggerProvider
|
||||
}
|
||||
|
||||
// Compile-time guarantee loggerProvider implements LoggerProvider.
|
||||
var _ log.LoggerProvider = (*loggerProvider)(nil)
|
||||
|
||||
func (p *loggerProvider) Logger(name string, options ...log.LoggerOption) log.Logger {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.delegate != nil {
|
||||
return p.delegate.Logger(name, options...)
|
||||
}
|
||||
|
||||
cfg := log.NewLoggerConfig(options...)
|
||||
key := instLib{name, cfg.InstrumentationVersion()}
|
||||
|
||||
if p.loggers == nil {
|
||||
l := &logger{name: name, options: options}
|
||||
p.loggers = map[instLib]*logger{key: l}
|
||||
return l
|
||||
}
|
||||
|
||||
if l, ok := p.loggers[key]; ok {
|
||||
return l
|
||||
}
|
||||
|
||||
l := &logger{name: name, options: options}
|
||||
p.loggers[key] = l
|
||||
return l
|
||||
}
|
||||
|
||||
func (p *loggerProvider) setDelegate(provider log.LoggerProvider) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
p.delegate = provider
|
||||
for _, l := range p.loggers {
|
||||
l.setDelegate(provider)
|
||||
}
|
||||
p.loggers = nil // Only set logger delegates once.
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
embedded.Logger
|
||||
|
||||
name string
|
||||
options []log.LoggerOption
|
||||
|
||||
delegate atomic.Value // log.Logger
|
||||
}
|
||||
|
||||
// Compile-time guarantee logger implements Logger.
|
||||
var _ log.Logger = (*logger)(nil)
|
||||
|
||||
func (l *logger) Emit(ctx context.Context, r log.Record) {
|
||||
if del, ok := l.delegate.Load().(log.Logger); ok {
|
||||
del.Emit(ctx, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Enabled(ctx context.Context, r log.Record) bool {
|
||||
var enabled bool
|
||||
if del, ok := l.delegate.Load().(log.Logger); ok {
|
||||
enabled = del.Enabled(ctx, r)
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
|
||||
func (l *logger) setDelegate(provider log.LoggerProvider) {
|
||||
l.delegate.Store(provider.Logger(l.name, l.options...))
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package global
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/log/embedded"
|
||||
"go.opentelemetry.io/otel/log/noop"
|
||||
)
|
||||
|
||||
func TestLoggerProviderConcurrentSafe(t *testing.T) {
|
||||
p := &loggerProvider{}
|
||||
|
||||
done := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
var logger log.Logger
|
||||
for i := 0; ; i++ {
|
||||
logger = p.Logger(fmt.Sprintf("a%d", i))
|
||||
select {
|
||||
case <-stop:
|
||||
_ = logger
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
p.setDelegate(noop.NewLoggerProvider())
|
||||
close(stop)
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestLoggerConcurrentSafe(t *testing.T) {
|
||||
l := &logger{}
|
||||
|
||||
done := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
|
||||
ctx := context.Background()
|
||||
var r log.Record
|
||||
|
||||
var enabled bool
|
||||
for {
|
||||
l.Emit(ctx, r)
|
||||
enabled = l.Enabled(ctx, r)
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
_ = enabled
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
l.setDelegate(noop.NewLoggerProvider())
|
||||
close(stop)
|
||||
<-done
|
||||
}
|
||||
|
||||
type testLoggerProvider struct {
|
||||
embedded.LoggerProvider
|
||||
|
||||
loggers map[string]*testLogger
|
||||
loggerN int
|
||||
}
|
||||
|
||||
func (p *testLoggerProvider) Logger(name string, _ ...log.LoggerOption) log.Logger {
|
||||
if p.loggers == nil {
|
||||
l := &testLogger{}
|
||||
p.loggers = map[string]*testLogger{name: l}
|
||||
p.loggerN++
|
||||
return l
|
||||
}
|
||||
|
||||
if l, ok := p.loggers[name]; ok {
|
||||
return l
|
||||
}
|
||||
|
||||
p.loggerN++
|
||||
l := &testLogger{}
|
||||
p.loggers[name] = l
|
||||
return l
|
||||
}
|
||||
|
||||
type testLogger struct {
|
||||
embedded.Logger
|
||||
|
||||
emitN, enabledN int
|
||||
}
|
||||
|
||||
func (l *testLogger) Emit(context.Context, log.Record) { l.emitN++ }
|
||||
func (l *testLogger) Enabled(context.Context, log.Record) bool {
|
||||
l.enabledN++
|
||||
return true
|
||||
}
|
||||
|
||||
func emitRecord(l log.Logger) {
|
||||
ctx := context.Background()
|
||||
var r log.Record
|
||||
|
||||
_ = l.Enabled(ctx, r)
|
||||
l.Emit(ctx, r)
|
||||
}
|
||||
|
||||
func TestDelegation(t *testing.T) {
|
||||
provider := &loggerProvider{}
|
||||
|
||||
const preName = "pre"
|
||||
pre0, pre1 := provider.Logger(preName), provider.Logger(preName)
|
||||
assert.Same(t, pre0, pre1, "same logger instance not returned")
|
||||
|
||||
alt := provider.Logger("alt")
|
||||
assert.NotSame(t, pre0, alt)
|
||||
|
||||
delegate := &testLoggerProvider{}
|
||||
provider.setDelegate(delegate)
|
||||
|
||||
want := 2 // (pre0/pre1) and (alt)
|
||||
if !assert.Equal(t, want, delegate.loggerN, "previous Loggers not delegated") {
|
||||
want = delegate.loggerN
|
||||
}
|
||||
|
||||
pre2 := provider.Logger(preName)
|
||||
if !assert.Equal(t, want, delegate.loggerN, "previous Logger recreated") {
|
||||
want = delegate.loggerN
|
||||
}
|
||||
|
||||
post := provider.Logger("test")
|
||||
want++
|
||||
assert.Equal(t, want, delegate.loggerN, "new Logger not delegated")
|
||||
|
||||
emitRecord(pre0)
|
||||
emitRecord(pre2)
|
||||
|
||||
if assert.IsType(t, &testLogger{}, pre2, "wrong pre-delegation Logger type") {
|
||||
assert.Equal(t, 2, pre2.(*testLogger).emitN, "Emit not delegated")
|
||||
assert.Equal(t, 2, pre2.(*testLogger).enabledN, "Enabled not delegated")
|
||||
}
|
||||
|
||||
emitRecord(post)
|
||||
|
||||
if assert.IsType(t, &testLogger{}, post, "wrong post-delegation Logger type") {
|
||||
assert.Equal(t, 1, post.(*testLogger).emitN, "Emit not delegated")
|
||||
assert.Equal(t, 1, post.(*testLogger).enabledN, "Enabled not delegated")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package global // import "go.opentelemetry.io/otel/log/internal/global"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/log"
|
||||
)
|
||||
|
||||
var (
|
||||
globalLoggerProvider = defaultLoggerProvider()
|
||||
|
||||
delegateLoggerOnce sync.Once
|
||||
)
|
||||
|
||||
func defaultLoggerProvider() *atomic.Value {
|
||||
v := &atomic.Value{}
|
||||
v.Store(loggerProviderHolder{provider: &loggerProvider{}})
|
||||
return v
|
||||
}
|
||||
|
||||
type loggerProviderHolder struct {
|
||||
provider log.LoggerProvider
|
||||
}
|
||||
|
||||
// GetLoggerProvider returns the global LoggerProvider.
|
||||
func GetLoggerProvider() log.LoggerProvider {
|
||||
return globalLoggerProvider.Load().(loggerProviderHolder).provider
|
||||
}
|
||||
|
||||
// SetLoggerProvider sets the global LoggerProvider.
|
||||
func SetLoggerProvider(provider log.LoggerProvider) {
|
||||
current := GetLoggerProvider()
|
||||
if _, cOk := current.(*loggerProvider); cOk {
|
||||
if _, mpOk := provider.(*loggerProvider); mpOk && current == provider {
|
||||
err := errors.New("invalid delegation: LoggerProvider self-delegation")
|
||||
global.Error(err, "No delegate will be configured")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
delegateLoggerOnce.Do(func() {
|
||||
if def, ok := current.(*loggerProvider); ok {
|
||||
def.setDelegate(provider)
|
||||
}
|
||||
})
|
||||
globalLoggerProvider.Store(loggerProviderHolder{provider: provider})
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package global
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/logr/testr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/log/noop"
|
||||
)
|
||||
|
||||
func TestSetLoggerProvider(t *testing.T) {
|
||||
reset := func() {
|
||||
globalLoggerProvider = defaultLoggerProvider()
|
||||
delegateLoggerOnce = sync.Once{}
|
||||
}
|
||||
|
||||
t.Run("Set With default is a noop", func(t *testing.T) {
|
||||
t.Cleanup(reset)
|
||||
|
||||
t.Cleanup(func(orig logr.Logger) func() {
|
||||
global.SetLogger(testr.New(t)) // Don't pollute output.
|
||||
return func() { global.SetLogger(orig) }
|
||||
}(global.GetLogger()))
|
||||
SetLoggerProvider(GetLoggerProvider())
|
||||
|
||||
provider, ok := GetLoggerProvider().(*loggerProvider)
|
||||
if !ok {
|
||||
t.Fatal("Global GetLoggerProvider should be the default logger provider")
|
||||
}
|
||||
if provider.delegate != nil {
|
||||
t.Fatal("logger provider should not delegate when setting itself")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("First Set() should replace the delegate", func(t *testing.T) {
|
||||
t.Cleanup(reset)
|
||||
|
||||
SetLoggerProvider(noop.NewLoggerProvider())
|
||||
if _, ok := GetLoggerProvider().(*loggerProvider); ok {
|
||||
t.Fatal("Global GetLoggerProvider was not changed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Set() should delegate existing Logger Providers", func(t *testing.T) {
|
||||
t.Cleanup(reset)
|
||||
|
||||
provider := GetLoggerProvider()
|
||||
SetLoggerProvider(noop.NewLoggerProvider())
|
||||
|
||||
if del := provider.(*loggerProvider); del.delegate == nil {
|
||||
t.Fatal("The delegated logger providers should have a delegate")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-comparable types should not panic", func(t *testing.T) {
|
||||
t.Cleanup(reset)
|
||||
|
||||
type nonComparableLoggerProvider struct {
|
||||
log.LoggerProvider
|
||||
noCmp [0]func() //nolint:structcheck,unused // This is indeed used.
|
||||
}
|
||||
|
||||
provider := nonComparableLoggerProvider{}
|
||||
SetLoggerProvider(provider)
|
||||
assert.NotPanics(t, func() { SetLoggerProvider(provider) })
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user