1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-11-27 22:49:15 +02:00
Files
opentelemetry-go/sdk/log/provider.go
Robert Pająk 1ee7c79b73 sdk/log: Add FilterProcessor and EnabledParameters (#6317)
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
2025-02-18 22:35:14 +01:00

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
})
}