You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-25 22:41:46 +02:00
Closes #5133 This couldn't be added as an option on a processor, as that would involve moving all the attribute deduplication. logic outside of the record type. Instead this PR provides the same functionality but it is set when creating the log provider The below benchstat report shows the performance improvement when `allowDupKeys` is set ``` goos: darwin goarch: arm64 pkg: go.opentelemetry.io/otel/sdk/log cpu: Apple M2 Pro │ withoutDedup.txt │ withDedup.txt │ │ sec/op │ sec/op vs base │ SetAddAttributes/SetAttributes-12 141.3n ± 2% 167.4n ± 5% +18.51% (p=0.000 n=10) SetAddAttributes/AddAttributes-12 117.5n ± 2% 124.8n ± 5% +6.17% (p=0.000 n=10) geomean 128.9n 144.5n +12.17% │ withoutDedup.txt │ withDedup.txt │ │ B/op │ B/op vs base │ SetAddAttributes/SetAttributes-12 48.00 ± 0% 48.00 ± 0% ~ (p=1.000 n=10) ¹ SetAddAttributes/AddAttributes-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ withoutDedup.txt │ withDedup.txt │ │ allocs/op │ allocs/op vs base │ SetAddAttributes/SetAttributes-12 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ SetAddAttributes/AddAttributes-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ```
278 lines
8.4 KiB
Go
278 lines
8.4 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package log // import "go.opentelemetry.io/otel/sdk/log"
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/internal/global"
|
|
"go.opentelemetry.io/otel/log"
|
|
"go.opentelemetry.io/otel/log/embedded"
|
|
"go.opentelemetry.io/otel/log/noop"
|
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
)
|
|
|
|
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
|
|
fltrProcessors []FilterProcessor
|
|
attrCntLim setting[int]
|
|
attrValLenLim setting[int]
|
|
allowDupKeys setting[bool]
|
|
}
|
|
|
|
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(
|
|
getenv[int](envarAttrCntLim),
|
|
fallback[int](defaultAttrCntLim),
|
|
)
|
|
|
|
c.attrValLenLim = c.attrValLenLim.Resolve(
|
|
getenv[int](envarAttrValLenLim),
|
|
fallback[int](defaultAttrValLenLim),
|
|
)
|
|
|
|
return c
|
|
}
|
|
|
|
// 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
|
|
fltrProcessors []FilterProcessor
|
|
attributeCountLimit int
|
|
attributeValueLengthLimit int
|
|
allowDupKeys bool
|
|
|
|
loggersMu sync.Mutex
|
|
loggers map[instrumentation.Scope]*logger
|
|
|
|
stopped atomic.Bool
|
|
|
|
noCmp [0]func() //nolint: unused // This is indeed used.
|
|
}
|
|
|
|
// Compile-time check LoggerProvider implements log.LoggerProvider.
|
|
var _ log.LoggerProvider = (*LoggerProvider)(nil)
|
|
|
|
// NewLoggerProvider returns a new and configured LoggerProvider.
|
|
//
|
|
// By default, the returned LoggerProvider is configured with the default
|
|
// Resource and no Processors. Processors cannot be added after a LoggerProvider is
|
|
// created. This means the returned LoggerProvider, one created with no
|
|
// Processors, will perform no operations.
|
|
func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider {
|
|
cfg := newProviderConfig(opts)
|
|
return &LoggerProvider{
|
|
resource: cfg.resource,
|
|
processors: cfg.processors,
|
|
fltrProcessors: cfg.fltrProcessors,
|
|
attributeCountLimit: cfg.attrCntLim.Value,
|
|
attributeValueLengthLimit: cfg.attrValLenLim.Value,
|
|
allowDupKeys: cfg.allowDupKeys.Value,
|
|
}
|
|
}
|
|
|
|
// Logger returns a new [log.Logger] with the provided name and configuration.
|
|
//
|
|
// If p is shut down, a [noop.Logger] instance is returned.
|
|
//
|
|
// This method can be called concurrently.
|
|
func (p *LoggerProvider) Logger(name string, opts ...log.LoggerOption) log.Logger {
|
|
if name == "" {
|
|
global.Warn("Invalid Logger name.", "name", name)
|
|
}
|
|
|
|
if p.stopped.Load() {
|
|
return noop.NewLoggerProvider().Logger(name, opts...)
|
|
}
|
|
|
|
cfg := log.NewLoggerConfig(opts...)
|
|
scope := instrumentation.Scope{
|
|
Name: name,
|
|
Version: cfg.InstrumentationVersion(),
|
|
SchemaURL: cfg.SchemaURL(),
|
|
Attributes: cfg.InstrumentationAttributes(),
|
|
}
|
|
|
|
p.loggersMu.Lock()
|
|
defer p.loggersMu.Unlock()
|
|
|
|
if p.loggers == nil {
|
|
l := newLogger(p, scope)
|
|
p.loggers = map[instrumentation.Scope]*logger{scope: l}
|
|
return l
|
|
}
|
|
|
|
l, ok := p.loggers[scope]
|
|
if !ok {
|
|
l = newLogger(p, scope)
|
|
p.loggers[scope] = l
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// Shutdown shuts down the provider and all processors.
|
|
//
|
|
// This method can be called concurrently.
|
|
func (p *LoggerProvider) Shutdown(ctx context.Context) error {
|
|
stopped := p.stopped.Swap(true)
|
|
if stopped {
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
for _, p := range p.processors {
|
|
err = errors.Join(err, p.Shutdown(ctx))
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ForceFlush flushes all processors.
|
|
//
|
|
// This method can be called concurrently.
|
|
func (p *LoggerProvider) ForceFlush(ctx context.Context) error {
|
|
if p.stopped.Load() {
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
for _, p := range p.processors {
|
|
err = errors.Join(err, p.ForceFlush(ctx))
|
|
}
|
|
return err
|
|
}
|
|
|
|
// LoggerProviderOption applies a configuration option value to a LoggerProvider.
|
|
type LoggerProviderOption interface {
|
|
apply(providerConfig) providerConfig
|
|
}
|
|
|
|
type loggerProviderOptionFunc func(providerConfig) providerConfig
|
|
|
|
func (fn loggerProviderOptionFunc) apply(c providerConfig) providerConfig {
|
|
return fn(c)
|
|
}
|
|
|
|
// WithResource associates a Resource with a LoggerProvider. This Resource
|
|
// represents the entity producing telemetry and is associated with all Loggers
|
|
// the LoggerProvider will create.
|
|
//
|
|
// By default, if this Option is not used, the default Resource from the
|
|
// go.opentelemetry.io/otel/sdk/resource package will be used.
|
|
func WithResource(res *resource.Resource) LoggerProviderOption {
|
|
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
|
|
var err error
|
|
cfg.resource, err = resource.Merge(resource.Environment(), res)
|
|
if err != nil {
|
|
otel.Handle(err)
|
|
}
|
|
return cfg
|
|
})
|
|
}
|
|
|
|
// WithProcessor associates Processor with a LoggerProvider.
|
|
//
|
|
// By default, if this option is not used, the LoggerProvider will perform no
|
|
// operations; no data will be exported without a processor.
|
|
//
|
|
// The SDK invokes the processors sequentially in the same order as they were
|
|
// registered.
|
|
//
|
|
// For production, use [NewBatchProcessor] to batch log records before they are exported.
|
|
// For testing and debugging, use [NewSimpleProcessor] to synchronously export log records.
|
|
//
|
|
// See [FilterProcessor] for information about how a Processor can support filtering.
|
|
func WithProcessor(processor Processor) LoggerProviderOption {
|
|
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
|
|
cfg.processors = append(cfg.processors, processor)
|
|
if f, ok := processor.(FilterProcessor); ok {
|
|
cfg.fltrProcessors = append(cfg.fltrProcessors, f)
|
|
}
|
|
return cfg
|
|
})
|
|
}
|
|
|
|
// WithAttributeCountLimit sets the maximum allowed log record attribute count.
|
|
// Any attribute added to a log record once this limit is reached will be dropped.
|
|
//
|
|
// Setting this to zero means no attributes will be recorded.
|
|
//
|
|
// Setting this to a negative value means no limit is applied.
|
|
//
|
|
// If the OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT environment variable is set,
|
|
// and this option is not passed, that variable value will be used.
|
|
//
|
|
// By default, if an environment variable is not set, and this option is not
|
|
// passed, 128 will be used.
|
|
func WithAttributeCountLimit(limit int) LoggerProviderOption {
|
|
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
|
|
cfg.attrCntLim = newSetting(limit)
|
|
return cfg
|
|
})
|
|
}
|
|
|
|
// WithAttributeValueLengthLimit sets the maximum allowed attribute value length.
|
|
//
|
|
// This limit only applies to string and string slice attribute values.
|
|
// Any string longer than this value will be truncated to this length.
|
|
//
|
|
// Setting this to a negative value means no limit is applied.
|
|
//
|
|
// If the OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT environment variable is set,
|
|
// and this option is not passed, that variable value will be used.
|
|
//
|
|
// By default, if an environment variable is not set, and this option is not
|
|
// passed, no limit (-1) will be used.
|
|
func WithAttributeValueLengthLimit(limit int) LoggerProviderOption {
|
|
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
|
|
cfg.attrValLenLim = newSetting(limit)
|
|
return cfg
|
|
})
|
|
}
|
|
|
|
// WithAllowKeyDuplication sets whether deduplication is skipped for log attributes or other key-value collections.
|
|
//
|
|
// By default, the key-value collections within a log record are deduplicated to comply with the OpenTelemetry Specification.
|
|
// Deduplication means that if multiple key–value pairs with the same key are present, only a single pair
|
|
// is retained and others are discarded.
|
|
//
|
|
// Disabling deduplication with this option can improve performance e.g. of adding attributes to the log record.
|
|
//
|
|
// Note that if you disable deduplication, you are responsible for ensuring that duplicate
|
|
// key-value pairs within in a single collection are not emitted,
|
|
// or that the telemetry receiver can handle such duplicates.
|
|
func WithAllowKeyDuplication() LoggerProviderOption {
|
|
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
|
|
cfg.allowDupKeys = newSetting(true)
|
|
return cfg
|
|
})
|
|
}
|