Skip to content

Commit 69e2358

Browse files
authored
sdk/log: SimpleProcessor synchronizes OnEmit calls (#5666)
From https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#export: > `Export` will never be called concurrently for the same exporter instance. From our SDK perspective this change will make https://pkg.go.dev/go.opentelemetry.io/otel/exporters/stdout/stdoutlog concurrent-safe when used with the simple processor. Before the change, there can be multiple goroutines calling `Write` in parallel (via `json.Encoder.Encode`). ### Remarks [Maybe we should simply state that "whole" exporter implementation does not need to be concurrent-safe?](open-telemetry/opentelemetry-specification#4173 (comment)) However: 1. we would need to make complex changes in `BatchExporter` to synchronize the `Export`, `Shutdown`, `ForceFlush` calls 2. we would need to update all exporters (remove synchronization) and simple exporter (add locks to `Shutdown`, `ForceFlush`) 3. I am 100% not sure if this would be compliant with the specification - I think it would be complaint because we would simply give stronger safety-measures We should probably discuss it separately, but I wanted to highlight my though process. Even if we decide that simple and batch processors to synchronize all calls then I would prefer to address it in a separate PR. Related spec clarification PR: - open-telemetry/opentelemetry-specification#4173
1 parent a5d1ec0 commit 69e2358

File tree

4 files changed

+36
-5
lines changed

4 files changed

+36
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2222

2323
- `Processor.OnEmit` in `go.opentelemetry.io/otel/sdk/log` now accepts a pointer to `Record` instead of a value so that the record modifications done in a processor are propagated to subsequent registered processors. (#5636)
2424
- `SimpleProcessor.Enabled` in `go.opentelemetry.io/otel/sdk/log` now returns `false` if the exporter is `nil`. (#5665)
25+
- Update the concurrency requirements of `Exporter` in `go.opentelemetry.io/otel/sdk/log`. (#5666)
26+
- `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` synchronizes `OnEmit` calls. (#5666)
2527

2628
### Fixed
2729

sdk/log/exporter.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ import (
1515
)
1616

1717
// Exporter handles the delivery of log records to external receivers.
18-
//
19-
// Any of the Exporter's methods may be called concurrently with itself
20-
// or with other methods. It is the responsibility of the Exporter to manage
21-
// this concurrency.
2218
type Exporter interface {
2319
// Export transmits log records to a receiver.
2420
//
@@ -34,6 +30,9 @@ type Exporter interface {
3430
//
3531
// Before modifying a Record, the implementation must use Record.Clone
3632
// to create a copy that shares no state with the original.
33+
//
34+
// Export should never be called concurrently with other Export calls.
35+
// However, it may be called concurrently with other methods.
3736
Export(ctx context.Context, records []Record) error
3837

3938
// Shutdown is called when the SDK shuts down. Any cleanup or release of
@@ -44,13 +43,17 @@ type Exporter interface {
4443
//
4544
// After Shutdown is called, calls to Export, Shutdown, or ForceFlush
4645
// should perform no operation and return nil error.
46+
//
47+
// Shutdown may be called concurrently with itself or with other methods.
4748
Shutdown(ctx context.Context) error
4849

4950
// ForceFlush exports log records to the configured Exporter that have not yet
5051
// been exported.
5152
//
5253
// The deadline or cancellation of the passed context must be honored. An
5354
// appropriate error should be returned in these situations.
55+
//
56+
// ForceFlush may be called concurrently with itself or with other methods.
5457
ForceFlush(ctx context.Context) error
5558
}
5659

sdk/log/simple.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var _ Processor = (*SimpleProcessor)(nil)
1515
//
1616
// Use [NewSimpleProcessor] to create a SimpleProcessor.
1717
type SimpleProcessor struct {
18+
mu sync.Mutex
1819
exporter Exporter
1920
}
2021

@@ -43,6 +44,9 @@ func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) error {
4344
return nil
4445
}
4546

47+
s.mu.Lock()
48+
defer s.mu.Unlock()
49+
4650
records := simpleProcRecordsPool.Get().(*[]Record)
4751
(*records)[0] = *r
4852
defer func() {

sdk/log/simple_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package log_test
55

66
import (
77
"context"
8+
"io"
9+
"strings"
810
"sync"
911
"testing"
1012

@@ -70,6 +72,25 @@ func TestSimpleProcessorForceFlush(t *testing.T) {
7072
require.True(t, e.forceFlushCalled, "exporter ForceFlush not called")
7173
}
7274

75+
type writerExporter struct {
76+
io.Writer
77+
}
78+
79+
func (e *writerExporter) Export(_ context.Context, records []log.Record) error {
80+
for _, r := range records {
81+
_, _ = io.WriteString(e.Writer, r.Body().String())
82+
}
83+
return nil
84+
}
85+
86+
func (e *writerExporter) Shutdown(context.Context) error {
87+
return nil
88+
}
89+
90+
func (e *writerExporter) ForceFlush(context.Context) error {
91+
return nil
92+
}
93+
7394
func TestSimpleProcessorEmpty(t *testing.T) {
7495
assert.NotPanics(t, func() {
7596
var s log.SimpleProcessor
@@ -91,7 +112,8 @@ func TestSimpleProcessorConcurrentSafe(t *testing.T) {
91112
r := new(log.Record)
92113
r.SetSeverityText("test")
93114
ctx := context.Background()
94-
s := log.NewSimpleProcessor(nil)
115+
e := &writerExporter{new(strings.Builder)}
116+
s := log.NewSimpleProcessor(e)
95117
for i := 0; i < goRoutineN; i++ {
96118
go func() {
97119
defer wg.Done()

0 commit comments

Comments
 (0)