1
0
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:
Tyler Yahn
2024-03-19 11:28:11 -07:00
committed by GitHub
parent 12c5651ec7
commit 335f4de960
7 changed files with 460 additions and 0 deletions
+96
View File
@@ -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...))
}
+160
View File
@@ -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")
}
}
+53
View File
@@ -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})
}
+75
View File
@@ -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) })
})
}