You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
07a91dd2b0
Per https://github.com/open-telemetry/opentelemetry-go/pull/7266#issuecomment-3237027300 Related to https://github.com/open-telemetry/opentelemetry-go/issues/7217 ## What This PR adds `WithInstrumentationAttributeSet` option functions to the `log`, `metric`, and `trace` packages as suggested in https://github.com/open-telemetry/opentelemetry-go/pull/7266#issuecomment-3237027300. These new functions provide a more concurrent-safe alternative to the existing `WithInstrumentationAttributes` functions by accepting a pre-constructed `attribute.Set` instead of variadic `attribute.KeyValue` parameters. ## Why As discussed in #7266, the existing `WithInstrumentationAttributes` functions can lead to data races when used concurrently because `attribute.NewSet()` may mutate the passed slice in-place. While the issue was partially addressed by moving the `attribute.NewSet()` call outside the closure, the best long-term solution is to provide an alternative that accepts an immutable `attribute.Set`. **Benefits:** 1. **Concurrent Safety**: Since `attribute.Set` is immutable, these functions are inherently safe for concurrent use 2. **Performance**: Avoids repeated calls to `attribute.NewSet()` when the same attributes are used multiple times 3. **Consistency**: Matches the existing pattern used in `metric.WithAttributeSet()` 4. **Flexibility**: Allows users to pre-compute attribute sets and reuse them Deprecating `WithInstrumentationAttributes` is out of scope. See https://github.com/open-telemetry/opentelemetry-go/pull/7287#issuecomment-3245820459. ## Benchmarks ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/trace cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkNewTracerConfig/with_no_options-20 280298306 4.268 ns/op 0 B/op 0 allocs/op BenchmarkNewTracerConfig/with_an_instrumentation_version-20 33389427 30.84 ns/op 0 B/op 0 allocs/op BenchmarkNewTracerConfig/with_a_schema_url-20 35441077 30.46 ns/op 0 B/op 0 allocs/op BenchmarkNewTracerConfig/with_instrumentation_attribute-20 17607649 88.23 ns/op 64 B/op 1 allocs/op BenchmarkNewTracerConfig/with_instrumentation_attribute_set-20 38336211 31.30 ns/op 0 B/op 0 allocs/op ``` ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/metric cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkNewMeterConfig/with_no_options-20 262998199 4.525 ns/op 0 B/op 0 allocs/op BenchmarkNewMeterConfig/with_an_instrumentation_version-20 40483780 29.31 ns/op 0 B/op 0 allocs/op BenchmarkNewMeterConfig/with_a_schema_url-20 39162420 30.58 ns/op 0 B/op 0 allocs/op BenchmarkNewMeterConfig/with_instrumentation_attribute-20 19900275 77.50 ns/op 64 B/op 1 allocs/op BenchmarkNewMeterConfig/with_instrumentation_attribute_set-20 37519020 31.93 ns/op 0 B/op 0 allocs/op ``` ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/log cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkNewLoggerConfig/with_no_options-20 271100760 4.322 ns/op 0 B/op 0 allocs/op BenchmarkNewLoggerConfig/with_an_instrumentation_version-20 38392390 30.77 ns/op 0 B/op 0 allocs/op BenchmarkNewLoggerConfig/with_a_schema_url-20 39615074 30.25 ns/op 0 B/op 0 allocs/op BenchmarkNewLoggerConfig/with_instrumentation_attribute-20 17108463 82.51 ns/op 64 B/op 1 allocs/op BenchmarkNewLoggerConfig/with_instrumentation_attribute_set-20 37746534 31.70 ns/op 0 B/op 0 allocs/op ```
195 lines
6.3 KiB
Go
195 lines
6.3 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package log // import "go.opentelemetry.io/otel/log"
|
|
|
|
import (
|
|
"context"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/log/embedded"
|
|
)
|
|
|
|
// Logger emits log records.
|
|
//
|
|
// Warning: Methods may be added to this interface in minor releases. See
|
|
// package documentation on API implementation for information on how to set
|
|
// default behavior for unimplemented methods.
|
|
type Logger interface {
|
|
// Users of the interface can ignore this. This embedded type is only used
|
|
// by implementations of this interface. See the "API Implementations"
|
|
// section of the package documentation for more information.
|
|
embedded.Logger
|
|
|
|
// Emit emits a log record.
|
|
//
|
|
// The record may be held by the implementation. Callers should not mutate
|
|
// the record after passed.
|
|
//
|
|
// Implementations of this method need to be safe for a user to call
|
|
// concurrently.
|
|
Emit(ctx context.Context, record Record)
|
|
|
|
// Enabled reports whether the Logger emits for the given context and
|
|
// param.
|
|
//
|
|
// This is useful for users that want to know if a [Record]
|
|
// will be processed or dropped before they perform complex operations to
|
|
// construct the [Record].
|
|
//
|
|
// The passed param is likely to be a partial record information being
|
|
// provided (e.g a param with only the Severity set).
|
|
// If a Logger needs more information than is provided, it
|
|
// is said to be in an indeterminate state (see below).
|
|
//
|
|
// The returned value will be true when the Logger will emit for the
|
|
// provided context and param, and will be false if the Logger will not
|
|
// emit. The returned value may be true or false in an indeterminate state.
|
|
// An implementation should default to returning true for an indeterminate
|
|
// state, but may return false if valid reasons in particular circumstances
|
|
// exist (e.g. performance, correctness).
|
|
//
|
|
// The param should not be held by the implementation. A copy should be
|
|
// made if the param needs to be held after the call returns.
|
|
//
|
|
// Implementations of this method need to be safe for a user to call
|
|
// concurrently.
|
|
Enabled(ctx context.Context, param EnabledParameters) bool
|
|
}
|
|
|
|
// LoggerOption applies configuration options to a [Logger].
|
|
type LoggerOption interface {
|
|
// applyLogger is used to set a LoggerOption value of a LoggerConfig.
|
|
applyLogger(LoggerConfig) LoggerConfig
|
|
}
|
|
|
|
// LoggerConfig contains options for a [Logger].
|
|
type LoggerConfig struct {
|
|
// Ensure forward compatibility by explicitly making this not comparable.
|
|
noCmp [0]func() //nolint: unused // This is indeed used.
|
|
|
|
version string
|
|
schemaURL string
|
|
attrs attribute.Set
|
|
}
|
|
|
|
// NewLoggerConfig returns a new [LoggerConfig] with all the options applied.
|
|
func NewLoggerConfig(options ...LoggerOption) LoggerConfig {
|
|
var c LoggerConfig
|
|
for _, opt := range options {
|
|
c = opt.applyLogger(c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// InstrumentationVersion returns the version of the library providing
|
|
// instrumentation.
|
|
func (cfg LoggerConfig) InstrumentationVersion() string {
|
|
return cfg.version
|
|
}
|
|
|
|
// InstrumentationAttributes returns the attributes associated with the library
|
|
// providing instrumentation.
|
|
func (cfg LoggerConfig) InstrumentationAttributes() attribute.Set {
|
|
return cfg.attrs
|
|
}
|
|
|
|
// SchemaURL returns the schema URL of the library providing instrumentation.
|
|
func (cfg LoggerConfig) SchemaURL() string {
|
|
return cfg.schemaURL
|
|
}
|
|
|
|
type loggerOptionFunc func(LoggerConfig) LoggerConfig
|
|
|
|
func (fn loggerOptionFunc) applyLogger(cfg LoggerConfig) LoggerConfig {
|
|
return fn(cfg)
|
|
}
|
|
|
|
// WithInstrumentationVersion returns a [LoggerOption] that sets the
|
|
// instrumentation version of a [Logger].
|
|
func WithInstrumentationVersion(version string) LoggerOption {
|
|
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
|
config.version = version
|
|
return config
|
|
})
|
|
}
|
|
|
|
// mergeSets returns the union of keys between a and b. Any duplicate keys will
|
|
// use the value associated with b.
|
|
func mergeSets(a, b attribute.Set) attribute.Set {
|
|
// NewMergeIterator uses the first value for any duplicates.
|
|
iter := attribute.NewMergeIterator(&b, &a)
|
|
merged := make([]attribute.KeyValue, 0, a.Len()+b.Len())
|
|
for iter.Next() {
|
|
merged = append(merged, iter.Attribute())
|
|
}
|
|
return attribute.NewSet(merged...)
|
|
}
|
|
|
|
// WithInstrumentationAttributes returns a [LoggerOption] that sets the
|
|
// instrumentation attributes of a [Logger].
|
|
//
|
|
// The passed attributes will be de-duplicated.
|
|
//
|
|
// Note that [WithInstrumentationAttributeSet] is recommended as
|
|
// it is more efficient and also allows safely reusing the passed argument.
|
|
//
|
|
// If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet]
|
|
// options are passed, the attributes will be merged together in the order
|
|
// they are passed. Attributes with duplicate keys will use the last value passed.
|
|
func WithInstrumentationAttributes(attr ...attribute.KeyValue) LoggerOption {
|
|
if len(attr) == 0 {
|
|
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
|
return config
|
|
})
|
|
}
|
|
|
|
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
|
newAttrs := attribute.NewSet(attr...)
|
|
if config.attrs.Len() == 0 {
|
|
config.attrs = newAttrs
|
|
} else {
|
|
config.attrs = mergeSets(config.attrs, newAttrs)
|
|
}
|
|
return config
|
|
})
|
|
}
|
|
|
|
// WithInstrumentationAttributeSet returns a [LoggerOption] that adds the
|
|
// instrumentation attributes of a [Logger].
|
|
//
|
|
// If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet]
|
|
// options are passed, the attributes will be merged together in the order
|
|
// they are passed. Attributes with duplicate keys will use the last value passed.
|
|
func WithInstrumentationAttributeSet(set attribute.Set) LoggerOption {
|
|
if set.Len() == 0 {
|
|
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
|
return config
|
|
})
|
|
}
|
|
|
|
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
|
if config.attrs.Len() == 0 {
|
|
config.attrs = set
|
|
} else {
|
|
config.attrs = mergeSets(config.attrs, set)
|
|
}
|
|
return config
|
|
})
|
|
}
|
|
|
|
// WithSchemaURL returns a [LoggerOption] that sets the schema URL for a
|
|
// [Logger].
|
|
func WithSchemaURL(schemaURL string) LoggerOption {
|
|
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
|
config.schemaURL = schemaURL
|
|
return config
|
|
})
|
|
}
|
|
|
|
// EnabledParameters represents payload for [Logger]'s Enabled method.
|
|
type EnabledParameters struct {
|
|
Severity Severity
|
|
EventName string
|
|
}
|