From 3a72c5ea94bf843beeaa044b0dda2ce4d627bb7b Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 15 Mar 2024 10:24:32 -0700 Subject: [PATCH] Implement the providerConfig (#5074) * Implement the providerConfig * Add test for NewLoggerProvider configuration * Add TestLimitValueFailsOpen * Fix merge --- sdk/log/provider.go | 106 +++++++++++++++++++++++++++++--- sdk/log/provider_test.go | 126 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 9 deletions(-) create mode 100644 sdk/log/provider_test.go diff --git a/sdk/log/provider.go b/sdk/log/provider.go index d0c6b23e8..1ab2affd6 100644 --- a/sdk/log/provider.go +++ b/sdk/log/provider.go @@ -5,22 +5,105 @@ package log // import "go.opentelemetry.io/otel/sdk/log" import ( "context" + "fmt" + "os" + "strconv" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" "go.opentelemetry.io/otel/sdk/resource" ) -// Compile-time check LoggerProvider implements log.LoggerProvider. -var _ log.LoggerProvider = (*LoggerProvider)(nil) +const ( + defaultAttrCntLim = 128 + defaultAttrValLenLim = -1 + + envarAttrCntLim = "OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT" + envarAttrValLenLim = "OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT" +) + +type providerConfig struct { + resource *resource.Resource + processors []Processor + attrCntLim limit + attrValLenLim limit +} + +func newProviderConfig(opts []LoggerProviderOption) providerConfig { + var c providerConfig + for _, opt := range opts { + c = opt.apply(c) + } + + if c.resource == nil { + c.resource = resource.Default() + } + + c.attrCntLim = c.attrCntLim.Resolve( + envarAttrCntLim, + defaultAttrCntLim, + ) + + c.attrValLenLim = c.attrValLenLim.Resolve( + envarAttrValLenLim, + defaultAttrValLenLim, + ) + + return c +} + +type limit struct { + value int + set bool +} + +func newLimit(value int) limit { + return limit{value: value, set: true} +} + +// Resolve returns the resolved form of the limit l. If l's value is set, it +// will return l. If the l's value is not set, a new limit based on the +// environment variable envar will be returned if that environment variable is +// set. Otherwise, fallback is used to construct a new limit that is returned. +func (l limit) Resolve(envar string, fallback int) limit { + if l.set { + return l + } + + if v := os.Getenv(envar); v != "" { + n, err := strconv.Atoi(v) + if err == nil { + return newLimit(n) + } + otel.Handle(fmt.Errorf("invalid %s value %s: %w", envar, v, err)) + } + + return newLimit(fallback) +} + +// Value returns the limit value if set. Otherwise, it returns -1. +func (l limit) Value() int { + if l.set { + return l.value + } + // Fail open, not closed (-1 == unlimited). + return -1 +} // LoggerProvider handles the creation and coordination of Loggers. All Loggers // created by a LoggerProvider will be associated with the same Resource. type LoggerProvider struct { embedded.LoggerProvider + + resource *resource.Resource + processors []Processor + attributeCountLimit int + attributeValueLengthLimit int } -type providerConfig struct{} +// Compile-time check LoggerProvider implements log.LoggerProvider. +var _ log.LoggerProvider = (*LoggerProvider)(nil) // NewLoggerProvider returns a new and configured LoggerProvider. // @@ -29,8 +112,13 @@ type providerConfig struct{} // created. This means the returned LoggerProvider, one created with no // Processors, will perform no operations. func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider { - // TODO (#5060): Implement. - return nil + cfg := newProviderConfig(opts) + return &LoggerProvider{ + resource: cfg.resource, + processors: cfg.processors, + attributeCountLimit: cfg.attrCntLim.Value(), + attributeValueLengthLimit: cfg.attrValLenLim.Value(), + } } // Logger returns a new [log.Logger] with the provided name and configuration. @@ -76,7 +164,7 @@ func (fn loggerProviderOptionFunc) apply(c providerConfig) providerConfig { // go.opentelemetry.io/otel/sdk/resource package will be used. func WithResource(res *resource.Resource) LoggerProviderOption { return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { - // TODO (#5060): Implement. + cfg.resource = res return cfg }) } @@ -93,7 +181,7 @@ func WithResource(res *resource.Resource) LoggerProviderOption { // For testing and debugging, use [NewSimpleProcessor] to synchronously export log records. func WithProcessor(processor Processor) LoggerProviderOption { return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { - // TODO (#5060): Implement. + cfg.processors = append(cfg.processors, processor) return cfg }) } @@ -112,7 +200,7 @@ func WithProcessor(processor Processor) LoggerProviderOption { // passed, 128 will be used. func WithAttributeCountLimit(limit int) LoggerProviderOption { return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { - // TODO (#5060): Implement. + cfg.attrCntLim = newLimit(limit) return cfg }) } @@ -131,7 +219,7 @@ func WithAttributeCountLimit(limit int) LoggerProviderOption { // passed, no limit (-1) will be used. func WithAttributeValueLengthLimit(limit int) LoggerProviderOption { return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { - // TODO (#5060): Implement. + cfg.attrValLenLim = newLimit(limit) return cfg }) } diff --git a/sdk/log/provider_test.go b/sdk/log/provider_test.go new file mode 100644 index 000000000..a53ea1aa8 --- /dev/null +++ b/sdk/log/provider_test.go @@ -0,0 +1,126 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource" +) + +type processor struct { + name string +} + +func (processor) OnEmit(context.Context, Record) error { return nil } +func (processor) Enabled(context.Context, Record) bool { return true } +func (processor) Shutdown(context.Context) error { return nil } +func (processor) ForceFlush(context.Context) error { return nil } + +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")) + p0, p1 := processor{name: "0"}, processor{name: "1"} + 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...)) + }) + } +} + +func TestLimitValueFailsOpen(t *testing.T) { + var l limit + assert.Equal(t, -1, l.Value(), "limit value should default to unlimited") +}