You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-27 22:49:15 +02:00
Per https://github.com/open-telemetry/opentelemetry-go/pull/6271#issuecomment-2657554647 > We agreed that we can move `FilterProcessor` directly to `sdk/log` as Logs SDK does not look to be stabilized soon. - Add the possibility to filter based on the resource and scope which is available for the SDK. The scope information is the most important as it gives the possibility to e.g. filter out logs emitted for a given logger. Thus e.g. https://github.com/open-telemetry/opentelemetry-specification/issues/4364 is not necessary. See https://github.com/open-telemetry/opentelemetry-specification/pull/4290#discussion_r1927546170 for more context. - It is going be an example for https://github.com/open-telemetry/opentelemetry-specification/issues/4363 There is a little overhead (IMO totally acceptable) because of data transformation. Most importantly, there is no new heap allocation. ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/sdk/log cpu: 13th Gen Intel(R) Core(TM) i7-13800H │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ LoggerEnabled-20 4.589n ± 1% 319.750n ± 16% +6867.75% (p=0.000 n=10) │ old.txt │ new.txt │ │ B/op │ B/op vs base │ LoggerEnabled-20 0.000Ki ± 0% 1.093Ki ± 13% ? (p=0.000 n=10) │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ LoggerEnabled-20 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ¹ all samples are equal ``` `Logger.Enabled` is still more efficient than `Logger.Emit` (benchmarks from https://github.com/open-telemetry/opentelemetry-go/pull/6315). ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/sdk/log cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkLoggerEmit/5_attributes-20 559934 2391 ns/op 39088 B/op 1 allocs/op BenchmarkLoggerEmit/10_attributes-20 1000000 5910 ns/op 49483 B/op 5 allocs/op BenchmarkLoggerEnabled-20 1605697 968.7 ns/op 1272 B/op 0 allocs/op PASS ok go.opentelemetry.io/otel/sdk/log 10.789s ``` Prior art: - https://github.com/open-telemetry/opentelemetry-go/pull/6271 - https://github.com/open-telemetry/opentelemetry-go/pull/6286 I also created for tracking purposes: - https://github.com/open-telemetry/opentelemetry-go/issues/6328
257 lines
7.4 KiB
Go
257 lines
7.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]
|
|
}
|
|
|
|
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
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
})
|
|
}
|
|
|
|
// AttributeValueLengthLimit 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
|
|
})
|
|
}
|