Skip to content

Commit 1ee7c79

Browse files
authored
sdk/log: Add FilterProcessor and EnabledParameters (#6317)
Per #6271 (comment) > 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. open-telemetry/opentelemetry-specification#4364 is not necessary. See open-telemetry/opentelemetry-specification#4290 (comment) for more context. - It is going be an example for open-telemetry/opentelemetry-specification#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 #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: - #6271 - #6286 I also created for tracking purposes: - https://github.com/open-telemetry/opentelemetry-go/issues/6328
1 parent b80639c commit 1ee7c79

File tree

12 files changed

+145
-127
lines changed

12 files changed

+145
-127
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ The next release will require at least [Go 1.23].
3131
- Document the pitfalls of using `Resource` as a comparable type.
3232
`Resource.Equal` and `Resource.Equivalent` should be used instead. (#6272)
3333
- Support [Go 1.24]. (#6304)
34+
- Add `FilterProcessor` and `EnabledParameters` in `go.opentelemetry.io/otel/sdk/log`.
35+
It replaces `go.opentelemetry.io/otel/sdk/log/internal/x.FilterProcessor`.
36+
Compared to previous version it additionally gives the possibility to filter by resource and instrumentation scope. (#6317)
3437

3538
### Changed
3639

log/logger.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ type Logger interface {
3333
// Enabled returns whether the Logger emits for the given context and
3434
// param.
3535
//
36-
// The passed param is likely to be a partial record with only the
37-
// bridge-relevant information being provided (e.g a param with only the
38-
// Severity set). If a Logger needs more information than is provided, it
36+
// This is useful for users that want to know if a [Record]
37+
// will be processed or dropped before they perform complex operations to
38+
// construct the [Record].
39+
//
40+
// The passed param is likely to be a partial record information being
41+
// provided (e.g a param with only the Severity set).
42+
// If a Logger needs more information than is provided, it
3943
// is said to be in an indeterminate state (see below).
4044
//
4145
// The returned value will be true when the Logger will emit for the
@@ -46,7 +50,7 @@ type Logger interface {
4650
// exist (e.g. performance, correctness).
4751
//
4852
// The param should not be held by the implementation. A copy should be
49-
// made if the record needs to be held after the call returns.
53+
// made if the param needs to be held after the call returns.
5054
//
5155
// Implementations of this method need to be safe for a user to call
5256
// concurrently.

sdk/log/doc.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,5 @@ 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.
3835
*/
3936
package log // import "go.opentelemetry.io/otel/sdk/log"

sdk/log/example_test.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func Example() {
5252
}
5353

5454
// Use a processor that filters out records based on the provided context.
55-
func ExampleProcessor_filtering() {
55+
func ExampleFilterProcessor() {
5656
// Existing processor that emits telemetry.
5757
var processor log.Processor = log.NewBatchProcessor(nil)
5858

@@ -84,14 +84,12 @@ type ContextFilterProcessor struct {
8484
log.Processor
8585

8686
lazyFilter sync.Once
87-
// Use the experimental FilterProcessor interface
88-
// (go.opentelemetry.io/otel/sdk/log/internal/x).
89-
filter filter
87+
// Support the FilterProcessor interface for the embedded processor.
88+
filter log.FilterProcessor
9089
}
9190

92-
type filter interface {
93-
Enabled(ctx context.Context, param logapi.EnabledParameters) bool
94-
}
91+
// Compile time check.
92+
var _ log.FilterProcessor = (*ContextFilterProcessor)(nil)
9593

9694
func (p *ContextFilterProcessor) OnEmit(ctx context.Context, record *log.Record) error {
9795
if ignoreLogs(ctx) {
@@ -100,9 +98,9 @@ func (p *ContextFilterProcessor) OnEmit(ctx context.Context, record *log.Record)
10098
return p.Processor.OnEmit(ctx, record)
10199
}
102100

103-
func (p *ContextFilterProcessor) Enabled(ctx context.Context, param logapi.EnabledParameters) bool {
101+
func (p *ContextFilterProcessor) Enabled(ctx context.Context, param log.EnabledParameters) bool {
104102
p.lazyFilter.Do(func() {
105-
if f, ok := p.Processor.(filter); ok {
103+
if f, ok := p.Processor.(log.FilterProcessor); ok {
106104
p.filter = f
107105
}
108106
})
@@ -115,7 +113,7 @@ func ignoreLogs(ctx context.Context) bool {
115113
}
116114

117115
// Use a processor which redacts sensitive data from some attributes.
118-
func ExampleProcessor_redact() {
116+
func ExampleProcessor() {
119117
// Existing processor that emits telemetry.
120118
var processor log.Processor = log.NewBatchProcessor(nil)
121119

sdk/log/filter_processor.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package log // import "go.opentelemetry.io/otel/sdk/log"
5+
6+
import (
7+
"context"
8+
9+
"go.opentelemetry.io/otel/log"
10+
"go.opentelemetry.io/otel/sdk/instrumentation"
11+
"go.opentelemetry.io/otel/sdk/resource"
12+
)
13+
14+
// FilterProcessor is a [Processor] that knows, and can identify, what [Record]
15+
// it will process or drop when it is passed to [Processor.OnEmit].
16+
//
17+
// This is useful for users that want to know if a [log.Record]
18+
// will be processed or dropped before they perform complex operations to
19+
// construct the [log.Record].
20+
//
21+
// The SDK's Logger.Enabled returns false
22+
// if all the registered Processors implement FilterProcessor
23+
// and they all return false.
24+
//
25+
// Processor implementations that choose to support this by satisfying this
26+
// interface are expected to re-evaluate the [Record] passed to [Processor.OnEmit],
27+
// it is not expected that the caller to OnEmit will use the functionality
28+
// from this interface prior to calling OnEmit.
29+
//
30+
// See the [go.opentelemetry.io/contrib/processors/minsev] for an example use-case.
31+
// It provides a Processor used to filter out [Record]
32+
// that has a [log.Severity] below a threshold.
33+
type FilterProcessor interface {
34+
// Enabled returns whether the Processor will process for the given context
35+
// and param.
36+
//
37+
// The passed param is likely to be a partial record information being
38+
// provided (e.g a param with only the Severity set).
39+
// If a Processor needs more information than is provided, it
40+
// is said to be in an indeterminate state (see below).
41+
//
42+
// The returned value will be true when the Processor will process for the
43+
// provided context and param, and will be false if the Logger will not
44+
// emit. The returned value may be true or false in an indeterminate state.
45+
// An implementation should default to returning true for an indeterminate
46+
// state, but may return false if valid reasons in particular circumstances
47+
// exist (e.g. performance, correctness).
48+
//
49+
// The param should not be held by the implementation. A copy should be
50+
// made if the param needs to be held after the call returns.
51+
//
52+
// Implementations of this method need to be safe for a user to call
53+
// concurrently.
54+
Enabled(ctx context.Context, param EnabledParameters) bool
55+
}
56+
57+
// EnabledParameters represents payload for [FilterProcessor]'s Enabled method.
58+
type EnabledParameters struct {
59+
Resource resource.Resource
60+
InstrumentationScope instrumentation.Scope
61+
Severity log.Severity
62+
}

sdk/log/internal/x/README.md

Lines changed: 0 additions & 35 deletions
This file was deleted.

sdk/log/internal/x/x.go

Lines changed: 0 additions & 47 deletions
This file was deleted.

sdk/log/logger.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ 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"
1514
"go.opentelemetry.io/otel/trace"
1615
)
1716

@@ -50,14 +49,22 @@ func (l *logger) Emit(ctx context.Context, r log.Record) {
5049
// processed, true will be returned by default. A value of false will only be
5150
// returned if it can be positively verified that no Processor will process.
5251
func (l *logger) Enabled(ctx context.Context, param log.EnabledParameters) bool {
53-
// If there are more Processors than FilterProcessors we cannot be sure
54-
// that all Processors will drop the record. Therefore, return true.
52+
p := EnabledParameters{
53+
Resource: *l.provider.resource,
54+
InstrumentationScope: l.instrumentationScope,
55+
Severity: param.Severity,
56+
}
57+
58+
// If there are more Processors than FilterProcessors,
59+
// which means not all Processors are FilterProcessors,
60+
// we cannot be sure that all Processors will drop the record.
61+
// Therefore, return true.
5562
//
5663
// If all Processors are FilterProcessors, check if any is enabled.
57-
return len(l.provider.processors) > len(l.provider.fltrProcessors) || anyEnabled(ctx, param, l.provider.fltrProcessors)
64+
return len(l.provider.processors) > len(l.provider.fltrProcessors) || anyEnabled(ctx, p, l.provider.fltrProcessors)
5865
}
5966

60-
func anyEnabled(ctx context.Context, param log.EnabledParameters, fltrs []x.FilterProcessor) bool {
67+
func anyEnabled(ctx context.Context, param EnabledParameters, fltrs []FilterProcessor) bool {
6168
for _, f := range fltrs {
6269
if f.Enabled(ctx, param) {
6370
// At least one Processor will process the Record.

sdk/log/logger_test.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,17 @@ func TestLoggerEnabled(t *testing.T) {
224224
p1 := newFltrProcessor("1", true)
225225
p2WithDisabled := newFltrProcessor("2", false)
226226

227+
emptyResource := resource.Empty()
228+
res := resource.NewSchemaless(attribute.String("key", "value"))
229+
227230
testCases := []struct {
228-
name string
229-
logger *logger
230-
ctx context.Context
231-
expected bool
231+
name string
232+
logger *logger
233+
ctx context.Context
234+
expected bool
235+
expectedP0Params []EnabledParameters
236+
expectedP1Params []EnabledParameters
237+
expectedP2Params []EnabledParameters
232238
}{
233239
{
234240
name: "NoProcessors",
@@ -241,41 +247,63 @@ func TestLoggerEnabled(t *testing.T) {
241247
logger: newLogger(NewLoggerProvider(
242248
WithProcessor(p0),
243249
WithProcessor(p1),
244-
), instrumentation.Scope{}),
250+
WithResource(res),
251+
), instrumentation.Scope{Name: "scope"}),
245252
ctx: context.Background(),
246253
expected: true,
254+
expectedP0Params: []EnabledParameters{{
255+
Resource: *res,
256+
InstrumentationScope: instrumentation.Scope{Name: "scope"},
257+
}},
258+
expectedP1Params: nil,
247259
},
248260
{
249261
name: "WithDisabledProcessors",
250262
logger: newLogger(NewLoggerProvider(
251263
WithProcessor(p2WithDisabled),
264+
WithResource(emptyResource),
252265
), instrumentation.Scope{}),
253-
ctx: context.Background(),
254-
expected: false,
266+
ctx: context.Background(),
267+
expected: false,
268+
expectedP2Params: []EnabledParameters{{}},
255269
},
256270
{
257271
name: "ContainsDisabledProcessor",
258272
logger: newLogger(NewLoggerProvider(
259273
WithProcessor(p2WithDisabled),
260274
WithProcessor(p0),
275+
WithResource(emptyResource),
261276
), instrumentation.Scope{}),
262-
ctx: context.Background(),
263-
expected: true,
277+
ctx: context.Background(),
278+
expected: true,
279+
expectedP2Params: []EnabledParameters{{}},
280+
expectedP0Params: []EnabledParameters{{}},
264281
},
265282
{
266283
name: "WithNilContext",
267284
logger: newLogger(NewLoggerProvider(
268285
WithProcessor(p0),
269286
WithProcessor(p1),
287+
WithResource(emptyResource),
270288
), instrumentation.Scope{}),
271-
ctx: nil,
272-
expected: true,
289+
ctx: nil,
290+
expected: true,
291+
expectedP0Params: []EnabledParameters{{}},
292+
expectedP1Params: nil,
273293
},
274294
}
275295

276296
for _, tc := range testCases {
277297
t.Run(tc.name, func(t *testing.T) {
298+
// Clean up the records before the test.
299+
p0.params = nil
300+
p1.params = nil
301+
p2WithDisabled.params = nil
302+
278303
assert.Equal(t, tc.expected, tc.logger.Enabled(tc.ctx, log.EnabledParameters{}))
304+
assert.Equal(t, tc.expectedP0Params, p0.params)
305+
assert.Equal(t, tc.expectedP1Params, p1.params)
306+
assert.Equal(t, tc.expectedP2Params, p2WithDisabled.params)
279307
})
280308
}
281309
}

sdk/log/processor.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import (
1313
// or with other methods. It is the responsibility of the Processor to manage
1414
// this concurrency.
1515
//
16-
// See [go.opentelemetry.io/otel/sdk/log/internal/x] for information about how
17-
// a Processor can be extended to support experimental features.
16+
// See [FilterProcessor] for information about how a Processor can support filtering.
1817
type Processor interface {
1918
// OnEmit is called when a Record is emitted.
2019
//
@@ -30,11 +29,11 @@ type Processor interface {
3029
// Handler.
3130
//
3231
// The SDK invokes the processors sequentially in the same order as
33-
// they were registered using [WithProcessor].
32+
// they were registered using WithProcessor.
3433
// Implementations may synchronously modify the record so that the changes
3534
// are visible in the next registered processor.
36-
// Notice that [Record] is not concurrent safe. Therefore, asynchronous
37-
// processing may cause race conditions. Use [Record.Clone]
35+
// Notice that Record is not concurrent safe. Therefore, asynchronous
36+
// processing may cause race conditions. Use Record.Clone
3837
// to create a copy that shares no state with the original.
3938
OnEmit(ctx context.Context, record *Record) error
4039

0 commit comments

Comments
 (0)