2024-03-15 19:24:32 +02:00
|
|
|
// Copyright The OpenTelemetry Authors
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package log // import "go.opentelemetry.io/otel/sdk/log"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-06-27 13:39:58 +02:00
|
|
|
"fmt"
|
2024-03-15 19:24:32 +02:00
|
|
|
"strconv"
|
2024-03-18 07:57:43 +02:00
|
|
|
"sync"
|
2024-03-15 19:24:32 +02:00
|
|
|
"testing"
|
|
|
|
|
2024-03-18 07:57:43 +02:00
|
|
|
"github.com/go-logr/logr"
|
|
|
|
"github.com/go-logr/logr/testr"
|
2024-09-06 19:19:44 +02:00
|
|
|
"github.com/google/go-cmp/cmp"
|
2024-03-15 19:24:32 +02:00
|
|
|
"github.com/stretchr/testify/assert"
|
2024-03-18 07:57:43 +02:00
|
|
|
"github.com/stretchr/testify/require"
|
2024-03-15 19:24:32 +02:00
|
|
|
|
|
|
|
"go.opentelemetry.io/otel"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
2024-03-18 07:57:43 +02:00
|
|
|
"go.opentelemetry.io/otel/internal/global"
|
2024-06-27 13:39:58 +02:00
|
|
|
"go.opentelemetry.io/otel/log"
|
2024-03-18 07:57:43 +02:00
|
|
|
"go.opentelemetry.io/otel/log/noop"
|
2024-09-06 19:19:44 +02:00
|
|
|
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
|
2024-08-22 18:12:23 +02:00
|
|
|
"go.opentelemetry.io/otel/sdk/log/internal/x"
|
2024-03-15 19:24:32 +02:00
|
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
|
|
)
|
|
|
|
|
2024-09-06 19:19:44 +02:00
|
|
|
const envVarResourceAttributes = "OTEL_RESOURCE_ATTRIBUTES"
|
|
|
|
|
2024-03-15 19:24:32 +02:00
|
|
|
type processor struct {
|
2024-03-18 07:57:43 +02:00
|
|
|
Name string
|
|
|
|
Err error
|
|
|
|
|
|
|
|
shutdownCalls int
|
|
|
|
forceFlushCalls int
|
2024-03-19 01:07:43 +02:00
|
|
|
|
|
|
|
records []Record
|
2024-03-18 07:57:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func newProcessor(name string) *processor {
|
2024-08-22 18:12:23 +02:00
|
|
|
return &processor{Name: name}
|
2024-03-18 07:57:43 +02:00
|
|
|
}
|
|
|
|
|
2024-08-01 10:13:43 +02:00
|
|
|
func (p *processor) OnEmit(ctx context.Context, r *Record) error {
|
2024-03-19 01:07:43 +02:00
|
|
|
if p.Err != nil {
|
|
|
|
return p.Err
|
|
|
|
}
|
|
|
|
|
2024-08-01 10:13:43 +02:00
|
|
|
p.records = append(p.records, *r)
|
2024-03-19 01:07:43 +02:00
|
|
|
return nil
|
2024-03-18 07:57:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *processor) Shutdown(context.Context) error {
|
|
|
|
p.shutdownCalls++
|
|
|
|
return p.Err
|
2024-03-15 19:24:32 +02:00
|
|
|
}
|
|
|
|
|
2024-03-18 07:57:43 +02:00
|
|
|
func (p *processor) ForceFlush(context.Context) error {
|
|
|
|
p.forceFlushCalls++
|
|
|
|
return p.Err
|
|
|
|
}
|
2024-03-15 19:24:32 +02:00
|
|
|
|
2024-08-22 18:12:23 +02:00
|
|
|
type fltrProcessor struct {
|
|
|
|
*processor
|
|
|
|
|
|
|
|
enabled bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ x.FilterProcessor = (*fltrProcessor)(nil)
|
|
|
|
|
|
|
|
func newFltrProcessor(name string, enabled bool) *fltrProcessor {
|
|
|
|
return &fltrProcessor{
|
|
|
|
processor: newProcessor(name),
|
|
|
|
enabled: enabled,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-13 06:35:01 +02:00
|
|
|
func (p *fltrProcessor) Enabled(context.Context, log.EnabledParameters) bool {
|
2024-08-22 18:12:23 +02:00
|
|
|
return p.enabled
|
|
|
|
}
|
|
|
|
|
2024-03-15 19:24:32 +02:00
|
|
|
func TestNewLoggerProviderConfiguration(t *testing.T) {
|
|
|
|
t.Cleanup(func(orig otel.ErrorHandler) func() {
|
|
|
|
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
|
|
|
|
t.Log(err)
|
|
|
|
}))
|
|
|
|
return func() { otel.SetErrorHandler(orig) }
|
|
|
|
}(otel.GetErrorHandler()))
|
|
|
|
|
|
|
|
res := resource.NewSchemaless(attribute.String("key", "value"))
|
2024-03-18 07:57:43 +02:00
|
|
|
p0, p1 := newProcessor("0"), newProcessor("1")
|
2024-03-15 19:24:32 +02:00
|
|
|
attrCntLim := 12
|
|
|
|
attrValLenLim := 21
|
|
|
|
|
|
|
|
testcases := []struct {
|
|
|
|
name string
|
|
|
|
envars map[string]string
|
|
|
|
options []LoggerProviderOption
|
|
|
|
want *LoggerProvider
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Defaults",
|
|
|
|
want: &LoggerProvider{
|
|
|
|
resource: resource.Default(),
|
|
|
|
attributeCountLimit: defaultAttrCntLim,
|
|
|
|
attributeValueLengthLimit: defaultAttrValLenLim,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Options",
|
|
|
|
options: []LoggerProviderOption{
|
|
|
|
WithResource(res),
|
|
|
|
WithProcessor(p0),
|
|
|
|
WithProcessor(p1),
|
|
|
|
WithAttributeCountLimit(attrCntLim),
|
|
|
|
WithAttributeValueLengthLimit(attrValLenLim),
|
|
|
|
},
|
|
|
|
want: &LoggerProvider{
|
|
|
|
resource: res,
|
|
|
|
processors: []Processor{p0, p1},
|
|
|
|
attributeCountLimit: attrCntLim,
|
|
|
|
attributeValueLengthLimit: attrValLenLim,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Environment",
|
|
|
|
envars: map[string]string{
|
|
|
|
envarAttrCntLim: strconv.Itoa(attrCntLim),
|
|
|
|
envarAttrValLenLim: strconv.Itoa(attrValLenLim),
|
|
|
|
},
|
|
|
|
want: &LoggerProvider{
|
|
|
|
resource: resource.Default(),
|
|
|
|
attributeCountLimit: attrCntLim,
|
|
|
|
attributeValueLengthLimit: attrValLenLim,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "InvalidEnvironment",
|
|
|
|
envars: map[string]string{
|
|
|
|
envarAttrCntLim: "invalid attributeCountLimit",
|
|
|
|
envarAttrValLenLim: "invalid attributeValueLengthLimit",
|
|
|
|
},
|
|
|
|
want: &LoggerProvider{
|
|
|
|
resource: resource.Default(),
|
|
|
|
attributeCountLimit: defaultAttrCntLim,
|
|
|
|
attributeValueLengthLimit: defaultAttrValLenLim,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Precedence",
|
|
|
|
envars: map[string]string{
|
|
|
|
envarAttrCntLim: strconv.Itoa(100),
|
|
|
|
envarAttrValLenLim: strconv.Itoa(101),
|
|
|
|
},
|
|
|
|
options: []LoggerProviderOption{
|
|
|
|
// These override the environment variables.
|
|
|
|
WithAttributeCountLimit(attrCntLim),
|
|
|
|
WithAttributeValueLengthLimit(attrValLenLim),
|
|
|
|
},
|
|
|
|
want: &LoggerProvider{
|
|
|
|
resource: resource.Default(),
|
|
|
|
attributeCountLimit: attrCntLim,
|
|
|
|
attributeValueLengthLimit: attrValLenLim,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testcases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
for key, value := range tc.envars {
|
|
|
|
t.Setenv(key, value)
|
|
|
|
}
|
|
|
|
assert.Equal(t, tc.want, NewLoggerProvider(tc.options...))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-06 19:19:44 +02:00
|
|
|
func mergeResource(t *testing.T, r1, r2 *resource.Resource) *resource.Resource {
|
|
|
|
r, err := resource.Merge(r1, r2)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWithResource(t *testing.T) {
|
|
|
|
store, err := ottest.SetEnvVariables(map[string]string{
|
|
|
|
envVarResourceAttributes: "key=value,rk5=7",
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() { require.NoError(t, store.Restore()) }()
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
name string
|
|
|
|
options []LoggerProviderOption
|
|
|
|
want *resource.Resource
|
|
|
|
msg string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "explicitly empty resource",
|
|
|
|
options: []LoggerProviderOption{WithResource(resource.Empty())},
|
|
|
|
want: resource.Environment(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "uses default if no resource option",
|
|
|
|
options: []LoggerProviderOption{},
|
|
|
|
want: resource.Default(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "explicit resource",
|
|
|
|
options: []LoggerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)))},
|
|
|
|
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5))),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "last resource wins",
|
|
|
|
options: []LoggerProviderOption{
|
|
|
|
WithResource(resource.NewSchemaless(attribute.String("rk1", "vk1"), attribute.Int64("rk2", 5))),
|
|
|
|
WithResource(resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
|
|
|
|
},
|
|
|
|
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "overlapping attributes with environment resource",
|
|
|
|
options: []LoggerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10)))},
|
|
|
|
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10))),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
|
|
tc := tc
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
got := newProviderConfig(tc.options).resource
|
|
|
|
if diff := cmp.Diff(got, tc.want); diff != "" {
|
|
|
|
t.Errorf("WithResource:\n -got +want %s", diff)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-18 07:57:43 +02:00
|
|
|
func TestLoggerProviderConcurrentSafe(t *testing.T) {
|
|
|
|
const goRoutineN = 10
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(goRoutineN)
|
|
|
|
|
|
|
|
p := NewLoggerProvider(WithProcessor(newProcessor("0")))
|
|
|
|
const name = "testLogger"
|
|
|
|
ctx := context.Background()
|
|
|
|
for i := 0; i < goRoutineN; i++ {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
_ = p.Logger(name)
|
|
|
|
_ = p.Shutdown(ctx)
|
|
|
|
_ = p.ForceFlush(ctx)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
type logSink struct {
|
|
|
|
logr.LogSink
|
|
|
|
|
|
|
|
level int
|
|
|
|
msg string
|
|
|
|
keysAndValues []interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *logSink) Enabled(int) bool { return true }
|
|
|
|
|
|
|
|
func (l *logSink) Info(level int, msg string, keysAndValues ...any) {
|
|
|
|
l.level, l.msg, l.keysAndValues = level, msg, keysAndValues
|
2024-07-31 08:41:11 +02:00
|
|
|
l.LogSink.Info(level, msg, keysAndValues...)
|
2024-03-18 07:57:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestLoggerProviderLogger(t *testing.T) {
|
|
|
|
t.Run("InvalidName", func(t *testing.T) {
|
|
|
|
l := &logSink{LogSink: testr.New(t).GetSink()}
|
|
|
|
t.Cleanup(func(orig logr.Logger) func() {
|
|
|
|
global.SetLogger(logr.New(l))
|
|
|
|
return func() { global.SetLogger(orig) }
|
|
|
|
}(global.GetLogger()))
|
|
|
|
|
|
|
|
_ = NewLoggerProvider().Logger("")
|
|
|
|
assert.Equal(t, 1, l.level, "logged level")
|
|
|
|
assert.Equal(t, "Invalid Logger name.", l.msg, "logged message")
|
|
|
|
require.Len(t, l.keysAndValues, 2, "logged key values")
|
|
|
|
assert.Equal(t, "", l.keysAndValues[1], "logged name")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Stopped", func(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
p := NewLoggerProvider()
|
|
|
|
_ = p.Shutdown(ctx)
|
|
|
|
l := p.Logger("testing")
|
|
|
|
|
|
|
|
assert.NotNil(t, l)
|
|
|
|
assert.IsType(t, noop.Logger{}, l)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("SameLoggers", func(t *testing.T) {
|
|
|
|
p := NewLoggerProvider()
|
|
|
|
|
2024-10-29 14:47:14 +02:00
|
|
|
l0, l1, l2 := p.Logger("l0"), p.Logger("l1"), p.Logger("l0", log.WithInstrumentationAttributes(attribute.String("foo", "bar")))
|
|
|
|
assert.NotSame(t, l0, l1)
|
2024-10-30 07:34:24 +02:00
|
|
|
assert.NotSame(t, l0, l2)
|
2024-10-29 14:47:14 +02:00
|
|
|
assert.NotSame(t, l1, l2)
|
|
|
|
|
|
|
|
l3, l4, l5 := p.Logger("l0"), p.Logger("l1"), p.Logger("l0", log.WithInstrumentationAttributes(attribute.String("foo", "bar")))
|
|
|
|
assert.Same(t, l0, l3)
|
|
|
|
assert.Same(t, l1, l4)
|
|
|
|
assert.Same(t, l2, l5)
|
2024-03-18 07:57:43 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLoggerProviderShutdown(t *testing.T) {
|
|
|
|
t.Run("Once", func(t *testing.T) {
|
|
|
|
proc := newProcessor("")
|
|
|
|
p := NewLoggerProvider(WithProcessor(proc))
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
require.NoError(t, p.Shutdown(ctx))
|
|
|
|
require.Equal(t, 1, proc.shutdownCalls, "processor Shutdown not called")
|
|
|
|
|
|
|
|
require.NoError(t, p.Shutdown(ctx))
|
|
|
|
assert.Equal(t, 1, proc.shutdownCalls, "processor Shutdown called multiple times")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Error", func(t *testing.T) {
|
|
|
|
proc := newProcessor("")
|
|
|
|
proc.Err = assert.AnError
|
|
|
|
p := NewLoggerProvider(WithProcessor(proc))
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
assert.ErrorIs(t, p.Shutdown(ctx), assert.AnError, "processor error not returned")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLoggerProviderForceFlush(t *testing.T) {
|
|
|
|
t.Run("Stopped", func(t *testing.T) {
|
|
|
|
proc := newProcessor("")
|
|
|
|
p := NewLoggerProvider(WithProcessor(proc))
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
require.NoError(t, p.ForceFlush(ctx))
|
|
|
|
require.Equal(t, 1, proc.forceFlushCalls, "processor ForceFlush not called")
|
|
|
|
|
|
|
|
require.NoError(t, p.Shutdown(ctx))
|
|
|
|
|
|
|
|
require.NoError(t, p.ForceFlush(ctx))
|
|
|
|
assert.Equal(t, 1, proc.forceFlushCalls, "processor ForceFlush called after Shutdown")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Multi", func(t *testing.T) {
|
|
|
|
proc := newProcessor("")
|
|
|
|
p := NewLoggerProvider(WithProcessor(proc))
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
require.NoError(t, p.ForceFlush(ctx))
|
|
|
|
require.Equal(t, 1, proc.forceFlushCalls, "processor ForceFlush not called")
|
|
|
|
|
|
|
|
require.NoError(t, p.ForceFlush(ctx))
|
|
|
|
assert.Equal(t, 2, proc.forceFlushCalls, "processor ForceFlush not called multiple times")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Error", func(t *testing.T) {
|
|
|
|
proc := newProcessor("")
|
|
|
|
proc.Err = assert.AnError
|
|
|
|
p := NewLoggerProvider(WithProcessor(proc))
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
assert.ErrorIs(t, p.ForceFlush(ctx), assert.AnError, "processor error not returned")
|
|
|
|
})
|
|
|
|
}
|
2024-06-27 13:39:58 +02:00
|
|
|
|
|
|
|
func BenchmarkLoggerProviderLogger(b *testing.B) {
|
|
|
|
p := NewLoggerProvider()
|
|
|
|
names := make([]string, b.N)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
names[i] = fmt.Sprintf("%d logger", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
|
|
|
loggers := make([]log.Logger, b.N)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
loggers[i] = p.Logger(names[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
b.StopTimer()
|
2024-09-13 06:35:01 +02:00
|
|
|
loggers[0].Enabled(context.Background(), log.EnabledParameters{})
|
2024-06-27 13:39:58 +02:00
|
|
|
}
|