Skip to content

Commit 002c0a4

Browse files
MrAliaspellaredXSAM
authored
Move log.Processor.Enabled to independent FilterProcessor interfaced type (#5692)
Closes #5425 Our current log `Processor` interface contains more functionality than the [OTel spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#logrecordprocessor-operations). The additional functionality allows processors to report back to the API if a Record should be constructed and emitted or not, which is quite helpful[^1][^2][^3][^4][^5]. This removes the `Enabled` method from the `Processor` type. It adds this functionality a new optional and experimental `FilterProcessor` interface type. The logger and provider are updated to check for this optional interface to be implemented with the configured processors and uses them to back the `Logger.Enabled` method, preserving existing functionality. By making this change: - The `Processor` interface is now compliant with the OTel spec and does not contain any additional unspecified behavior. - All `Processor` implementations are no longer required to implement an `Enabled` method. The default, when they do not implement this method, is to assume they are enabled. ### Benchmark ```terminal goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/sdk/log cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz │ old.txt │ new7.txt │ │ sec/op │ sec/op vs base │ LoggerEnabled-8 133.30n ± 3% 32.36n ± 3% -75.72% (p=0.000 n=10) │ old.txt │ new7.txt │ │ B/op │ B/op vs base │ LoggerEnabled-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ¹ all samples are equal │ old.txt │ new7.txt │ │ allocs/op │ allocs/op vs base │ LoggerEnabled-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ¹ all samples are equal ``` This is a significant performance improvement due to the `Record` no longer being converted from the API version to the SDK version. [^1]: https://pkg.go.dev/go.opentelemetry.io/contrib/processors/minsev [^2]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/log#BatchProcessor.Enabled [^3]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/log#SimpleProcessor.Enabled [^4]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/af75717ac4fb3ba13eaea83b88301723122060cf/bridges/otelslog/handler.go#L206-L211 [^5]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/d0309ddd8c5714af1cd9dbe8b39b7e8f10485679/bridges/otelzap/core.go#L142-L146 --------- Co-authored-by: Robert Pająk <[email protected]> Co-authored-by: Sam Xie <[email protected]>
1 parent fe6c67e commit 002c0a4

File tree

14 files changed

+197
-62
lines changed

14 files changed

+197
-62
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ The next release will require at least [Go 1.22].
2222
- Zero value of `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` no longer panics. (#5665)
2323
- Add `Walk` function to `TraceState` in `go.opentelemetry.io/otel/trace` to iterate all the key-value pairs. (#5651)
2424
- Bridge the trace state in `go.opentelemetry.io/otel/bridge/opencensus`. (#5651)
25+
- The `FilterProcessor` interface type is added in `go.opentelemetry.io/otel/sdk/log/internal/x`.
26+
This is an optional and experimental interface that log `Processor`s can implement to instruct the `Logger` if a `Record` will be processed or not.
27+
It replaces the existing `Enabled` method that is removed from the `Processor` interface itself.
28+
It does not fall within the scope of the OpenTelemetry Go versioning and stability [policy](./VERSIONING.md) and it may be changed in backwards incompatible ways or removed in feature releases. (#5692)
2529
- Support [Go 1.23]. (#5720)
2630

2731
### Changed
@@ -30,6 +34,8 @@ The next release will require at least [Go 1.22].
3034
- `SimpleProcessor.Enabled` in `go.opentelemetry.io/otel/sdk/log` now returns `false` if the exporter is `nil`. (#5665)
3135
- Update the concurrency requirements of `Exporter` in `go.opentelemetry.io/otel/sdk/log`. (#5666)
3236
- `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` synchronizes `OnEmit` calls. (#5666)
37+
- The `Processor` interface in `go.opentelemetry.io/otel/sdk/log` no longer includes the `Enabled` method.
38+
See the `FilterProcessor` interface type added in `go.opentelemetry.io/otel/sdk/log/internal/x` to continue providing this functionality. (#5692)
3339
- The `SimpleProcessor` type in `go.opentelemetry.io/otel/sdk/log` is no longer comparable. (#5693)
3440
- The `BatchProcessor` type in `go.opentelemetry.io/otel/sdk/log` is no longer comparable. (#5693)
3541
- `NewMemberRaw`, `NewKeyProperty` and `NewKeyValuePropertyRaw` in `go.opentelemetry.io/otel/baggage` allow UTF-8 string in key. (#5132)
@@ -50,6 +56,11 @@ The next release will require at least [Go 1.22].
5056
- Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#5641)
5157
- Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#5650)
5258

59+
### Removed
60+
61+
- The `Enabled` method of the `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` is removed. (#5692)
62+
- The `Enabled` method of the `BatchProcessor` in `go.opentelemetry.io/otel/sdk/log` is removed. (#5692)
63+
5364
<!-- Released section -->
5465
<!-- Don't change this section unless doing release -->
5566

sdk/log/batch.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,6 @@ func (b *BatchProcessor) OnEmit(_ context.Context, r *Record) error {
196196
return nil
197197
}
198198

199-
// Enabled returns if b is enabled.
200-
func (b *BatchProcessor) Enabled(context.Context, Record) bool {
201-
return !b.stopped.Load() && b.q != nil
202-
}
203-
204199
// Shutdown flushes queued log records and shuts down the decorated exporter.
205200
func (b *BatchProcessor) Shutdown(ctx context.Context) error {
206201
if b.stopped.Swap(true) || b.q == nil {

sdk/log/batch_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func TestEmptyBatchConfig(t *testing.T) {
4747
ctx := context.Background()
4848
record := new(Record)
4949
assert.NoError(t, bp.OnEmit(ctx, record), "OnEmit")
50-
assert.False(t, bp.Enabled(ctx, *record), "Enabled")
5150
assert.NoError(t, bp.ForceFlush(ctx), "ForceFlush")
5251
assert.NoError(t, bp.Shutdown(ctx), "Shutdown")
5352
})
@@ -270,14 +269,6 @@ func TestBatchProcessor(t *testing.T) {
270269
assert.Equal(t, 3, e.ExportN())
271270
})
272271

273-
t.Run("Enabled", func(t *testing.T) {
274-
b := NewBatchProcessor(defaultNoopExporter)
275-
assert.True(t, b.Enabled(ctx, Record{}))
276-
277-
_ = b.Shutdown(ctx)
278-
assert.False(t, b.Enabled(ctx, Record{}))
279-
})
280-
281272
t.Run("Shutdown", func(t *testing.T) {
282273
t.Run("Error", func(t *testing.T) {
283274
e := newTestExporter(assert.AnError)

sdk/log/doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@ at a single endpoint their origin is decipherable.
3232
3333
See [go.opentelemetry.io/otel/log] for more information about
3434
the OpenTelemetry Logs Bridge API.
35+
36+
See [go.opentelemetry.io/otel/sdk/log/internal/x] for information about the
37+
experimental features.
3538
*/
3639
package log // import "go.opentelemetry.io/otel/sdk/log"

sdk/log/example_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"fmt"
99
"strings"
10+
"sync"
1011

1112
logapi "go.opentelemetry.io/otel/log"
1213
"go.opentelemetry.io/otel/log/global"
@@ -58,7 +59,7 @@ func ExampleProcessor_filtering() {
5859
// Wrap the processor so that it ignores processing log records
5960
// when a context deriving from WithIgnoreLogs is passed
6061
// to the logging methods.
61-
processor = &ContextFilterProcessor{processor}
62+
processor = &ContextFilterProcessor{Processor: processor}
6263

6364
// The created processor can then be registered with
6465
// the OpenTelemetry Logs SDK using the WithProcessor option.
@@ -81,6 +82,15 @@ func WithIgnoreLogs(ctx context.Context) context.Context {
8182
// [WithIgnoreLogs] is passed to its methods.
8283
type ContextFilterProcessor struct {
8384
log.Processor
85+
86+
lazyFilter sync.Once
87+
// Use the experimental FilterProcessor interface
88+
// (go.opentelemetry.io/otel/sdk/log/internal/x).
89+
filter filter
90+
}
91+
92+
type filter interface {
93+
Enabled(ctx context.Context, record log.Record) bool
8494
}
8595

8696
func (p *ContextFilterProcessor) OnEmit(ctx context.Context, record *log.Record) error {
@@ -91,7 +101,12 @@ func (p *ContextFilterProcessor) OnEmit(ctx context.Context, record *log.Record)
91101
}
92102

93103
func (p *ContextFilterProcessor) Enabled(ctx context.Context, record log.Record) bool {
94-
return !ignoreLogs(ctx) && p.Processor.Enabled(ctx, record)
104+
p.lazyFilter.Do(func() {
105+
if f, ok := p.Processor.(filter); ok {
106+
p.filter = f
107+
}
108+
})
109+
return !ignoreLogs(ctx) && (p.filter == nil || p.filter.Enabled(ctx, record))
95110
}
96111

97112
func ignoreLogs(ctx context.Context) bool {

sdk/log/internal/x/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Experimental Features
2+
3+
The Logs SDK contains features that have not yet stabilized.
4+
These features are added to the OpenTelemetry Go Logs SDK prior to stabilization so that users can start experimenting with them and provide feedback.
5+
6+
These feature may change in backwards incompatible ways as feedback is applied.
7+
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
8+
9+
## Features
10+
11+
- [Filter Processors](#filter-processor)
12+
13+
### Filter Processor
14+
15+
Users of logging libraries often want to know if a log `Record` will be processed or dropped before they perform complex operations to construct the `Record`.
16+
The [`Logger`] in the Logs Bridge API provides the `Enabled` method for just this use-case.
17+
In order for the Logs Bridge SDK to effectively implement this API, it needs to be known if the registered [`Processor`]s are enabled for the `Record` within a context.
18+
A [`Processor`] that knows, and can identify, what `Record` it will process or drop when it is passed to `OnEmit` can communicate this to the SDK `Logger` by implementing the `FilterProcessor`.
19+
20+
By default, the SDK `Logger.Enabled` will return true when called.
21+
Only if all the registered [`Processor`]s implement `FilterProcessor` and they all return `false` will `Logger.Enabled` return `false`.
22+
23+
See the [`minsev`] [`Processor`] for an example use-case.
24+
It is used to filter `Record`s out that a have a `Severity` below a threshold.
25+
26+
[`Logger`]: https://pkg.go.dev/go.opentelemetry.io/otel/log#Logger
27+
[`Processor`]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/log#Processor
28+
[`minsev`]: https://pkg.go.dev/go.opentelemetry.io/contrib/processors/minsev
29+
30+
## Compatibility and Stability
31+
32+
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../VERSIONING.md).
33+
These features may be removed or modified in successive version releases, including patch versions.
34+
35+
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.

sdk/log/internal/x/x.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package x contains support for Logs SDK experimental features.
5+
package x // import "go.opentelemetry.io/otel/sdk/log/internal/x"
6+
7+
import (
8+
"context"
9+
10+
"go.opentelemetry.io/otel/log"
11+
)
12+
13+
// FilterProcessor is a [Processor] that knows, and can identify, what
14+
// [log.Record] it will process or drop when it is passed to OnEmit.
15+
//
16+
// This is useful for users of logging libraries that want to know if a [log.Record]
17+
// will be processed or dropped before they perform complex operations to
18+
// construct the [log.Record].
19+
//
20+
// [Processor] implementations that choose to support this by satisfying this
21+
// interface are expected to re-evaluate the [log.Record]s passed to OnEmit, it is
22+
// not expected that the caller to OnEmit will use the functionality from this
23+
// interface prior to calling OnEmit.
24+
//
25+
// This should only be implemented for [Processor]s that can make reliable
26+
// enough determination of this prior to processing a [log.Record] and where
27+
// the result is dynamic.
28+
//
29+
// [Processor]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/log#Processor
30+
type FilterProcessor interface {
31+
// Enabled returns whether the Processor will process for the given context
32+
// and record.
33+
//
34+
// The passed record is likely to be a partial record with only the
35+
// bridge-relevant information being provided (e.g a record with only the
36+
// Severity set). If a Logger needs more information than is provided, it
37+
// is said to be in an indeterminate state (see below).
38+
//
39+
// The returned value will be true when the Processor will process for the
40+
// provided context and record, and will be false if the Processor will not
41+
// process. An implementation should default to returning true for an
42+
// indeterminate state.
43+
//
44+
// Implementations should not modify the record.
45+
Enabled(ctx context.Context, record log.Record) bool
46+
}

sdk/log/logger.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go.opentelemetry.io/otel/log"
1212
"go.opentelemetry.io/otel/log/embedded"
1313
"go.opentelemetry.io/otel/sdk/instrumentation"
14+
"go.opentelemetry.io/otel/sdk/log/internal/x"
1415
"go.opentelemetry.io/otel/trace"
1516
)
1617

@@ -42,13 +43,30 @@ func (l *logger) Emit(ctx context.Context, r log.Record) {
4243
}
4344
}
4445

45-
func (l *logger) Enabled(ctx context.Context, r log.Record) bool {
46-
newRecord := l.newRecord(ctx, r)
47-
for _, p := range l.provider.processors {
48-
if enabled := p.Enabled(ctx, newRecord); enabled {
46+
// Enabled returns true if at least one Processor held by the LoggerProvider
47+
// that created the logger will process the record for the provided context.
48+
//
49+
// If it is not possible to definitively determine the record will be
50+
// processed, true will be returned by default. A value of false will only be
51+
// returned if it can be positively verified that no Processor will process the
52+
// record.
53+
func (l *logger) Enabled(ctx context.Context, record log.Record) bool {
54+
fltrs := l.provider.filterProcessors()
55+
// If there are more Processors than FilterProcessors we cannot be sure
56+
// that all Processors will drop the record. Therefore, return true.
57+
//
58+
// If all Processors are FilterProcessors, check if any is enabled.
59+
return len(l.provider.processors) > len(fltrs) || anyEnabled(ctx, record, fltrs)
60+
}
61+
62+
func anyEnabled(ctx context.Context, r log.Record, fltrs []x.FilterProcessor) bool {
63+
for _, f := range fltrs {
64+
if f.Enabled(ctx, r) {
65+
// At least one Processor will process the Record.
4966
return true
5067
}
5168
}
69+
// No Processor will process the record
5270
return false
5371
}
5472

sdk/log/logger_test.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,9 @@ func TestLoggerEmit(t *testing.T) {
215215
}
216216

217217
func TestLoggerEnabled(t *testing.T) {
218-
p0, p1, p2WithDisabled := newProcessor("0"), newProcessor("1"), newProcessor("2")
219-
p2WithDisabled.enabled = false
218+
p0 := newFltrProcessor("0", true)
219+
p1 := newFltrProcessor("1", true)
220+
p2WithDisabled := newFltrProcessor("2", false)
220221

221222
testCases := []struct {
222223
name string
@@ -273,3 +274,24 @@ func TestLoggerEnabled(t *testing.T) {
273274
})
274275
}
275276
}
277+
278+
func BenchmarkLoggerEnabled(b *testing.B) {
279+
provider := NewLoggerProvider(
280+
WithProcessor(newFltrProcessor("0", false)),
281+
WithProcessor(newFltrProcessor("1", true)),
282+
)
283+
logger := provider.Logger("BenchmarkLoggerEnabled")
284+
ctx, r := context.Background(), log.Record{}
285+
r.SetSeverityText("test")
286+
287+
var enabled bool
288+
289+
b.ReportAllocs()
290+
b.ResetTimer()
291+
292+
for n := 0; n < b.N; n++ {
293+
enabled = logger.Enabled(ctx, r)
294+
}
295+
296+
_ = enabled
297+
}

sdk/log/processor.go

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import (
1212
// Any of the Processor's methods may be called concurrently with itself
1313
// or with other methods. It is the responsibility of the Processor to manage
1414
// this concurrency.
15+
//
16+
// See [go.opentelemetry.io/otel/sdk/log/internal/x] for information about how
17+
// a Processor can be extended to support experimental features.
1518
type Processor interface {
1619
// OnEmit is called when a Record is emitted.
1720
//
@@ -35,27 +38,6 @@ type Processor interface {
3538
// to create a copy that shares no state with the original.
3639
OnEmit(ctx context.Context, record *Record) error
3740

38-
// Enabled returns whether the Processor will process for the given context
39-
// and record.
40-
//
41-
// The passed record is likely to be a partial record with only the
42-
// bridge-relevant information being provided (e.g a record with only the
43-
// Severity set). If a Logger needs more information than is provided, it
44-
// is said to be in an indeterminate state (see below).
45-
//
46-
// The returned value will be true when the Processor will process for the
47-
// provided context and record, and will be false if the Processor will not
48-
// process. The returned value may be true or false in an indeterminate
49-
// state. An implementation should default to returning true for an
50-
// indeterminate state, but may return false if valid reasons in particular
51-
// circumstances exist (e.g. performance, correctness).
52-
//
53-
// The SDK invokes the processors sequentially in the same order as
54-
// they were registered using [WithProcessor] until any processor returns true.
55-
//
56-
// Implementations should not modify the record.
57-
Enabled(ctx context.Context, record Record) bool
58-
5941
// Shutdown is called when the SDK shuts down. Any cleanup or release of
6042
// resources held by the exporter should be done in this call.
6143
//

sdk/log/provider.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"go.opentelemetry.io/otel/log/embedded"
1515
"go.opentelemetry.io/otel/log/noop"
1616
"go.opentelemetry.io/otel/sdk/instrumentation"
17+
"go.opentelemetry.io/otel/sdk/log/internal/x"
1718
"go.opentelemetry.io/otel/sdk/resource"
1819
)
1920

@@ -65,6 +66,9 @@ type LoggerProvider struct {
6566
attributeCountLimit int
6667
attributeValueLengthLimit int
6768

69+
fltrProcessorsOnce sync.Once
70+
fltrProcessors []x.FilterProcessor
71+
6872
loggersMu sync.Mutex
6973
loggers map[instrumentation.Scope]*logger
7074

@@ -92,6 +96,17 @@ func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider {
9296
}
9397
}
9498

99+
func (p *LoggerProvider) filterProcessors() []x.FilterProcessor {
100+
p.fltrProcessorsOnce.Do(func() {
101+
for _, proc := range p.processors {
102+
if f, ok := proc.(x.FilterProcessor); ok {
103+
p.fltrProcessors = append(p.fltrProcessors, f)
104+
}
105+
}
106+
})
107+
return p.fltrProcessors
108+
}
109+
95110
// Logger returns a new [log.Logger] with the provided name and configuration.
96111
//
97112
// If p is shut down, a [noop.Logger] instance is returned.

0 commit comments

Comments
 (0)