From 0481fcbccb360d000f07960866532bacd03e7fbf Mon Sep 17 00:00:00 2001 From: yumosx Date: Mon, 27 Oct 2025 10:00:54 +0800 Subject: [PATCH 01/11] feat(sdk/log): add observability instrumentation to SimpleLogProcessor --- sdk/log/simple.go | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/sdk/log/simple.go b/sdk/log/simple.go index 002e52cae66..a8b93e04ced 100644 --- a/sdk/log/simple.go +++ b/sdk/log/simple.go @@ -6,6 +6,10 @@ package log // import "go.opentelemetry.io/otel/sdk/log" import ( "context" "sync" + "sync/atomic" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/log/internal/observ" ) // Compile-time check SimpleProcessor implements Processor. @@ -17,8 +21,8 @@ var _ Processor = (*SimpleProcessor)(nil) type SimpleProcessor struct { mu sync.Mutex exporter Exporter - - noCmp [0]func() //nolint: unused // This is indeed used. + inst *observ.SLP + noCmp [0]func() //nolint: unused // This is indeed used. } // NewSimpleProcessor is a simple Processor adapter. @@ -30,7 +34,23 @@ type SimpleProcessor struct { // [NewBatchProcessor] instead. However, there may be exceptions where certain // [Exporter] implementations perform better with this Processor. func NewSimpleProcessor(exporter Exporter, _ ...SimpleProcessorOption) *SimpleProcessor { - return &SimpleProcessor{exporter: exporter} + slp := &SimpleProcessor{ + exporter: exporter, + } + var err error + slp.inst, err = observ.NewSLP(nextSimpleProcessorID()) + if err != nil { + otel.Handle(err) + } + return slp +} + +var simpleProcessorIDCounter atomic.Int64 + +// nextSimpleProcessorID returns an id for this simple log processor, +// starting with 0 and incrementing by 1 each time it is called. +func nextSimpleProcessorID() int64 { + return simpleProcessorIDCounter.Add(1) - 1 } var simpleProcRecordsPool = sync.Pool{ @@ -41,7 +61,7 @@ var simpleProcRecordsPool = sync.Pool{ } // OnEmit batches provided log record. -func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) error { +func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) (err error) { if s.exporter == nil { return nil } @@ -55,6 +75,11 @@ func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) error { simpleProcRecordsPool.Put(records) }() + if s.inst != nil { + defer func() { + s.inst.LogProcessed(ctx, err) + }() + } return s.exporter.Export(ctx, *records) } From 402892c9aad4e98e1633bf6bf22f2d41a4d9def5 Mon Sep 17 00:00:00 2001 From: yumosx Date: Tue, 28 Oct 2025 10:32:18 +0800 Subject: [PATCH 02/11] added TestSimpleLogProcessorObsevability. --- sdk/log/simple_test.go | 100 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/sdk/log/simple_test.go b/sdk/log/simple_test.go index 0c59c68a284..81a5bf2e9e7 100644 --- a/sdk/log/simple_test.go +++ b/sdk/log/simple_test.go @@ -6,6 +6,7 @@ package log_test import ( "context" "io" + "strconv" "strings" "sync" "testing" @@ -13,7 +14,17 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk" + "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/log/internal/observ" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + "go.opentelemetry.io/otel/semconv/v1.37.0/otelconv" ) type exporter struct { @@ -138,3 +149,92 @@ func BenchmarkSimpleProcessorOnEmit(b *testing.B) { _ = out }) } + +func TestSimpleLogProcessorObsevability(t *testing.T) { + testcases := []struct { + name string + enabled bool + assertMetrics func(t *testing.T, rm metricdata.ResourceMetrics) + }{ + { + name: "diabled", + enabled: false, + assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Empty(t, rm.ScopeMetrics) + }, + }, + { + name: "enabled", + enabled: true, + assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Len(t, rm.ScopeMetrics, 1) + sm := rm.ScopeMetrics[0] + + p := otelconv.SDKProcessorLogProcessed{} + + want := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: observ.ScopeName, + Version: sdk.Version(), + SchemaURL: semconv.SchemaURL, + }, + Metrics: []metricdata.Metrics{ + { + Name: p.Name(), + Description: p.Description(), + Unit: p.Unit(), + Data: metricdata.Sum[int64]{ + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 1, + Attributes: attribute.NewSet( + observ.GetSLPComponentName(0), + semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeSimpleLogProcessor)), + ), + }, + }, + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + }, + }, + }, + } + + metricdatatest.AssertEqual( + t, + want, + sm, + metricdatatest.IgnoreExemplars(), + metricdatatest.IgnoreTimestamp(), + ) + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + t.Setenv("OTEL_GO_X_OBSERVABILITY", strconv.FormatBool(tc.enabled)) + + original := otel.GetMeterProvider() + t.Cleanup(func() { + otel.SetMeterProvider(original) + }) + + r := metric.NewManualReader() + mp := metric.NewMeterProvider(metric.WithReader(r)) + otel.SetMeterProvider(mp) + + e := new(exporter) + slp := log.NewSimpleProcessor(e) + record := new(log.Record) + record.SetSeverityText("test") + err := slp.OnEmit(t.Context(), record) + require.NoError(t, err) + + var rm metricdata.ResourceMetrics + require.NoError(t, r.Collect(t.Context(), &rm)) + tc.assertMetrics(t, rm) + //todo: set the counter to zero. + }) + } +} From 66c7301a3ccb6092ea67b8f7f6bf764b1c02853c Mon Sep 17 00:00:00 2001 From: yumosx Date: Wed, 29 Oct 2025 16:09:31 +0800 Subject: [PATCH 03/11] add failingTestExporter --- sdk/log/simple_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sdk/log/simple_test.go b/sdk/log/simple_test.go index 81a5bf2e9e7..18b9506027d 100644 --- a/sdk/log/simple_test.go +++ b/sdk/log/simple_test.go @@ -5,6 +5,7 @@ package log_test import ( "context" + "errors" "io" "strconv" "strings" @@ -51,6 +52,17 @@ func (e *exporter) ForceFlush(context.Context) error { return nil } +var _ log.Exporter = (*failingTestExporter)(nil) + +type failingTestExporter struct { + exporter +} + +func (f *failingTestExporter) Export(ctx context.Context, r []log.Record) error { + _ = f.Export(ctx, r) + return errors.New("failed to export logs") +} + func TestSimpleProcessorOnEmit(t *testing.T) { e := new(exporter) s := log.NewSimpleProcessor(e) From 0788d3f731caa09c890d08e1ce18c39862624e8f Mon Sep 17 00:00:00 2001 From: yumosx Date: Thu, 30 Oct 2025 22:44:58 +0800 Subject: [PATCH 04/11] use counter --- sdk/log/internal/counter/counter.go | 31 +++++++++++ sdk/log/internal/counter/counter_test.go | 65 ++++++++++++++++++++++ sdk/log/internal/gen.go | 3 + sdk/log/simple.go | 12 +--- sdk/log/simple_test.go | 71 ++++++++++++++++++------ 5 files changed, 156 insertions(+), 26 deletions(-) create mode 100644 sdk/log/internal/counter/counter.go create mode 100644 sdk/log/internal/counter/counter_test.go diff --git a/sdk/log/internal/counter/counter.go b/sdk/log/internal/counter/counter.go new file mode 100644 index 00000000000..4eed9ae9c4e --- /dev/null +++ b/sdk/log/internal/counter/counter.go @@ -0,0 +1,31 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/counter/counter.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package counter provides a simple counter for generating unique IDs. +// +// This package is used to generate unique IDs while allowing testing packages +// to reset the counter. +package counter // import "go.opentelemetry.io/otel/sdk/log/internal/counter" + +import "sync/atomic" + +// exporterN is a global 0-based count of the number of exporters created. +var exporterN atomic.Int64 + +// NextExporterID returns the next unique ID for an exporter. +func NextExporterID() int64 { + const inc = 1 + return exporterN.Add(inc) - inc +} + +// SetExporterID sets the exporter ID counter to v and returns the previous +// value. +// +// This function is useful for testing purposes, allowing you to reset the +// counter. It should not be used in production code. +func SetExporterID(v int64) int64 { + return exporterN.Swap(v) +} diff --git a/sdk/log/internal/counter/counter_test.go b/sdk/log/internal/counter/counter_test.go new file mode 100644 index 00000000000..f3e380d3325 --- /dev/null +++ b/sdk/log/internal/counter/counter_test.go @@ -0,0 +1,65 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/counter/counter_test.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package counter + +import ( + "sync" + "testing" +) + +func TestNextExporterID(t *testing.T) { + SetExporterID(0) + + var expected int64 + for range 10 { + id := NextExporterID() + if id != expected { + t.Errorf("NextExporterID() = %d; want %d", id, expected) + } + expected++ + } +} + +func TestSetExporterID(t *testing.T) { + SetExporterID(0) + + prev := SetExporterID(42) + if prev != 0 { + t.Errorf("SetExporterID(42) returned %d; want 0", prev) + } + + id := NextExporterID() + if id != 42 { + t.Errorf("NextExporterID() = %d; want 42", id) + } +} + +func TestNextExporterIDConcurrentSafe(t *testing.T) { + SetExporterID(0) + + const goroutines = 100 + const increments = 10 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for range goroutines { + go func() { + defer wg.Done() + for range increments { + NextExporterID() + } + }() + } + + wg.Wait() + + expected := int64(goroutines * increments) + if id := NextExporterID(); id != expected { + t.Errorf("NextExporterID() = %d; want %d", id, expected) + } +} \ No newline at end of file diff --git a/sdk/log/internal/gen.go b/sdk/log/internal/gen.go index dee3f808f95..46b98797fd7 100644 --- a/sdk/log/internal/gen.go +++ b/sdk/log/internal/gen.go @@ -4,5 +4,8 @@ // Package internal provides internal functionality for the sdk/log package. package internal // import "go.opentelemetry.io/otel/sdk/log/internal" +//go:generate gotmpl --body=../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/sdk/log/internal/counter\" }" --out=counter/counter.go +//go:generate gotmpl --body=../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go + //go:generate gotmpl --body=../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/sdk/log\" }" --out=x/x.go //go:generate gotmpl --body=../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go diff --git a/sdk/log/simple.go b/sdk/log/simple.go index a8b93e04ced..6102818366a 100644 --- a/sdk/log/simple.go +++ b/sdk/log/simple.go @@ -6,9 +6,9 @@ package log // import "go.opentelemetry.io/otel/sdk/log" import ( "context" "sync" - "sync/atomic" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/log/internal/counter" "go.opentelemetry.io/otel/sdk/log/internal/observ" ) @@ -38,21 +38,13 @@ func NewSimpleProcessor(exporter Exporter, _ ...SimpleProcessorOption) *SimplePr exporter: exporter, } var err error - slp.inst, err = observ.NewSLP(nextSimpleProcessorID()) + slp.inst, err = observ.NewSLP(counter.NextExporterID()) if err != nil { otel.Handle(err) } return slp } -var simpleProcessorIDCounter atomic.Int64 - -// nextSimpleProcessorID returns an id for this simple log processor, -// starting with 0 and incrementing by 1 each time it is called. -func nextSimpleProcessorID() int64 { - return simpleProcessorIDCounter.Add(1) - 1 -} - var simpleProcRecordsPool = sync.Pool{ New: func() any { records := make([]Record, 1) diff --git a/sdk/log/simple_test.go b/sdk/log/simple_test.go index 18b9506027d..189a03e9010 100644 --- a/sdk/log/simple_test.go +++ b/sdk/log/simple_test.go @@ -5,13 +5,14 @@ package log_test import ( "context" - "errors" "io" "strconv" "strings" "sync" "testing" + "go.opentelemetry.io/otel/sdk/log/internal/counter" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -52,17 +53,6 @@ func (e *exporter) ForceFlush(context.Context) error { return nil } -var _ log.Exporter = (*failingTestExporter)(nil) - -type failingTestExporter struct { - exporter -} - -func (f *failingTestExporter) Export(ctx context.Context, r []log.Record) error { - _ = f.Export(ctx, r) - return errors.New("failed to export logs") -} - func TestSimpleProcessorOnEmit(t *testing.T) { e := new(exporter) s := log.NewSimpleProcessor(e) @@ -162,14 +152,14 @@ func BenchmarkSimpleProcessorOnEmit(b *testing.B) { }) } -func TestSimpleLogProcessorObsevability(t *testing.T) { +func TestSimpleLogProcessorObservability(t *testing.T) { testcases := []struct { name string enabled bool assertMetrics func(t *testing.T, rm metricdata.ResourceMetrics) }{ { - name: "diabled", + name: "disabled", enabled: false, assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) @@ -201,7 +191,9 @@ func TestSimpleLogProcessorObsevability(t *testing.T) { Value: 1, Attributes: attribute.NewSet( observ.GetSLPComponentName(0), - semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeSimpleLogProcessor)), + semconv.OTelComponentTypeKey.String( + string(otelconv.ComponentTypeSimpleLogProcessor), + ), ), }, }, @@ -221,6 +213,53 @@ func TestSimpleLogProcessorObsevability(t *testing.T) { ) }, }, + { + name: "Enable Exporter error", + enabled: true, + assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Len(t, rm.ScopeMetrics, 1) + sm := rm.ScopeMetrics[0] + p := otelconv.SDKProcessorLogProcessed{} + + want := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "go.opentelemetry.io/otel/sdk/log/internal/observ", + Version: sdk.Version(), + SchemaURL: semconv.SchemaURL, + }, + Metrics: []metricdata.Metrics{ + { + Name: p.Name(), + Description: p.Description(), + Unit: p.Unit(), + Data: metricdata.Sum[int64]{ + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 1, + Attributes: attribute.NewSet( + observ.GetSLPComponentName(0), + semconv.OTelComponentTypeKey.String( + string(otelconv.ComponentTypeSimpleLogProcessor), + ), + ), + }, + }, + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + }, + }, + }, + } + + metricdatatest.AssertEqual( + t, + want, + sm, + metricdatatest.IgnoreTimestamp(), + metricdatatest.IgnoreExemplars(), + ) + }, + }, } for _, tc := range testcases { @@ -246,7 +285,7 @@ func TestSimpleLogProcessorObsevability(t *testing.T) { var rm metricdata.ResourceMetrics require.NoError(t, r.Collect(t.Context(), &rm)) tc.assertMetrics(t, rm) - //todo: set the counter to zero. + counter.SetExporterID(0) }) } } From 0541716f2193ddfbb1837b670e78716a175fe341 Mon Sep 17 00:00:00 2001 From: yumosx Date: Sat, 1 Nov 2025 23:32:08 +0800 Subject: [PATCH 05/11] fix the test and added bench --- sdk/log/simple_test.go | 60 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/sdk/log/simple_test.go b/sdk/log/simple_test.go index 189a03e9010..9b53342f9c8 100644 --- a/sdk/log/simple_test.go +++ b/sdk/log/simple_test.go @@ -53,6 +53,17 @@ func (e *exporter) ForceFlush(context.Context) error { return nil } +var _ log.Exporter = (*failingTestExporter)(nil) + +type failingTestExporter struct { + exporter +} + +func (f *failingTestExporter) Export(ctx context.Context, r []log.Record) error { + _ = f.exporter.Export(ctx, r) + return assert.AnError +} + func TestSimpleProcessorOnEmit(t *testing.T) { e := new(exporter) s := log.NewSimpleProcessor(e) @@ -152,22 +163,52 @@ func BenchmarkSimpleProcessorOnEmit(b *testing.B) { }) } +func BenchmarkSimpleProcessorInst(b *testing.B) { + run := func(b *testing.B) { + slp := log.NewSimpleProcessor(new(exporter)) + record := new(log.Record) + record.SetSeverityText("test") + + ctx := b.Context() + b.ReportAllocs() + b.ResetTimer() + + var err error + for b.Loop() { + err = slp.OnEmit(ctx, record) + } + _ = err + } + + b.Run("Observability", func(b *testing.B) { + b.Setenv("OTEL_GO_X_OBSERVABILITY", "true") + run(b) + }) + b.Run("NoObservability", run) +} + func TestSimpleLogProcessorObservability(t *testing.T) { testcases := []struct { name string enabled bool + exporter log.Exporter + wantErr error assertMetrics func(t *testing.T, rm metricdata.ResourceMetrics) }{ { - name: "disabled", - enabled: false, + name: "disabled", + enabled: false, + wantErr: nil, + exporter: new(exporter), assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { - name: "enabled", - enabled: true, + name: "enabled", + enabled: true, + wantErr: nil, + exporter: new(exporter), assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Len(t, rm.ScopeMetrics, 1) sm := rm.ScopeMetrics[0] @@ -216,6 +257,10 @@ func TestSimpleLogProcessorObservability(t *testing.T) { { name: "Enable Exporter error", enabled: true, + wantErr: assert.AnError, + exporter: &failingTestExporter{ + exporter: exporter{}, + }, assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Len(t, rm.ScopeMetrics, 1) sm := rm.ScopeMetrics[0] @@ -241,6 +286,7 @@ func TestSimpleLogProcessorObservability(t *testing.T) { semconv.OTelComponentTypeKey.String( string(otelconv.ComponentTypeSimpleLogProcessor), ), + semconv.ErrorTypeKey.String("*errors.errorString"), ), }, }, @@ -275,13 +321,11 @@ func TestSimpleLogProcessorObservability(t *testing.T) { mp := metric.NewMeterProvider(metric.WithReader(r)) otel.SetMeterProvider(mp) - e := new(exporter) - slp := log.NewSimpleProcessor(e) + slp := log.NewSimpleProcessor(tc.exporter) record := new(log.Record) record.SetSeverityText("test") err := slp.OnEmit(t.Context(), record) - require.NoError(t, err) - + require.ErrorIs(t, err, tc.wantErr) var rm metricdata.ResourceMetrics require.NoError(t, r.Collect(t.Context(), &rm)) tc.assertMetrics(t, rm) From 8d4cd7f060d347ac64196692f885ab302c723040 Mon Sep 17 00:00:00 2001 From: yumosx Date: Sat, 1 Nov 2025 23:37:24 +0800 Subject: [PATCH 06/11] add changelog entry --- CHANGELOG.md | 1 + sdk/log/simple_test.go | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d38a27d0484..051c3dfe1c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add experimental observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#7486) - Add experimental observability metrics for simple span processor in `go.opentelemetry.io/otel/sdk/trace`. (#7374) - Add experimental observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#7512) +- Add experimental observability metrics for simple log processor in `go.opentelemetry.io/otel/sdk/log`. (#7548) ### Fixed diff --git a/sdk/log/simple_test.go b/sdk/log/simple_test.go index 9b53342f9c8..608e52715f2 100644 --- a/sdk/log/simple_test.go +++ b/sdk/log/simple_test.go @@ -163,7 +163,7 @@ func BenchmarkSimpleProcessorOnEmit(b *testing.B) { }) } -func BenchmarkSimpleProcessorInst(b *testing.B) { +func BenchmarkSimpleProcessorObservability(b *testing.B) { run := func(b *testing.B) { slp := log.NewSimpleProcessor(new(exporter)) record := new(log.Record) @@ -198,7 +198,6 @@ func TestSimpleLogProcessorObservability(t *testing.T) { { name: "disabled", enabled: false, - wantErr: nil, exporter: new(exporter), assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) @@ -207,7 +206,6 @@ func TestSimpleLogProcessorObservability(t *testing.T) { { name: "enabled", enabled: true, - wantErr: nil, exporter: new(exporter), assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Len(t, rm.ScopeMetrics, 1) From ca0e0761178d964220112c3efc3dc4c1fba64bbb Mon Sep 17 00:00:00 2001 From: yumosx Date: Sat, 1 Nov 2025 23:52:21 +0800 Subject: [PATCH 07/11] fix the markdown lint --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 051c3dfe1c9..9719b7d22f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add experimental observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#7486) - Add experimental observability metrics for simple span processor in `go.opentelemetry.io/otel/sdk/trace`. (#7374) - Add experimental observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#7512) -- Add experimental observability metrics for simple log processor in `go.opentelemetry.io/otel/sdk/log`. (#7548) +- Add experimental observability metrics for simple log processor in `go.opentelemetry.io/otel/sdk/log`. (#7548) ### Fixed From a8fae0c567198a5b5c20a5a577a63c92a6e7d17e Mon Sep 17 00:00:00 2001 From: yumosx Date: Mon, 3 Nov 2025 20:28:21 +0800 Subject: [PATCH 08/11] fmt --- sdk/log/simple_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/log/simple_test.go b/sdk/log/simple_test.go index 608e52715f2..c6bd957b613 100644 --- a/sdk/log/simple_test.go +++ b/sdk/log/simple_test.go @@ -11,8 +11,6 @@ import ( "sync" "testing" - "go.opentelemetry.io/otel/sdk/log/internal/counter" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,6 +19,7 @@ import ( "go.opentelemetry.io/otel/sdk" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/log/internal/counter" "go.opentelemetry.io/otel/sdk/log/internal/observ" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" @@ -165,7 +164,7 @@ func BenchmarkSimpleProcessorOnEmit(b *testing.B) { func BenchmarkSimpleProcessorObservability(b *testing.B) { run := func(b *testing.B) { - slp := log.NewSimpleProcessor(new(exporter)) + slp := log.NewSimpleProcessor(&failingTestExporter{exporter: exporter{}}) record := new(log.Record) record.SetSeverityText("test") From 3b942bd1906feecf19916e735cb70f17a7deebc7 Mon Sep 17 00:00:00 2001 From: yumosx Date: Mon, 3 Nov 2025 22:09:57 +0800 Subject: [PATCH 09/11] move the counter to observ --- sdk/log/internal/counter/counter.go | 31 --------- sdk/log/internal/counter/counter_test.go | 65 ------------------- sdk/log/internal/gen.go | 3 - .../internal/observ/simple_log_processor.go | 19 ++++++ .../observ/simple_log_processor_test.go | 54 +++++++++++++++ sdk/log/simple.go | 3 +- sdk/log/simple_test.go | 3 +- 7 files changed, 75 insertions(+), 103 deletions(-) delete mode 100644 sdk/log/internal/counter/counter.go delete mode 100644 sdk/log/internal/counter/counter_test.go diff --git a/sdk/log/internal/counter/counter.go b/sdk/log/internal/counter/counter.go deleted file mode 100644 index 4eed9ae9c4e..00000000000 --- a/sdk/log/internal/counter/counter.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/counter/counter.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package counter provides a simple counter for generating unique IDs. -// -// This package is used to generate unique IDs while allowing testing packages -// to reset the counter. -package counter // import "go.opentelemetry.io/otel/sdk/log/internal/counter" - -import "sync/atomic" - -// exporterN is a global 0-based count of the number of exporters created. -var exporterN atomic.Int64 - -// NextExporterID returns the next unique ID for an exporter. -func NextExporterID() int64 { - const inc = 1 - return exporterN.Add(inc) - inc -} - -// SetExporterID sets the exporter ID counter to v and returns the previous -// value. -// -// This function is useful for testing purposes, allowing you to reset the -// counter. It should not be used in production code. -func SetExporterID(v int64) int64 { - return exporterN.Swap(v) -} diff --git a/sdk/log/internal/counter/counter_test.go b/sdk/log/internal/counter/counter_test.go deleted file mode 100644 index f3e380d3325..00000000000 --- a/sdk/log/internal/counter/counter_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/counter/counter_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package counter - -import ( - "sync" - "testing" -) - -func TestNextExporterID(t *testing.T) { - SetExporterID(0) - - var expected int64 - for range 10 { - id := NextExporterID() - if id != expected { - t.Errorf("NextExporterID() = %d; want %d", id, expected) - } - expected++ - } -} - -func TestSetExporterID(t *testing.T) { - SetExporterID(0) - - prev := SetExporterID(42) - if prev != 0 { - t.Errorf("SetExporterID(42) returned %d; want 0", prev) - } - - id := NextExporterID() - if id != 42 { - t.Errorf("NextExporterID() = %d; want 42", id) - } -} - -func TestNextExporterIDConcurrentSafe(t *testing.T) { - SetExporterID(0) - - const goroutines = 100 - const increments = 10 - - var wg sync.WaitGroup - wg.Add(goroutines) - - for range goroutines { - go func() { - defer wg.Done() - for range increments { - NextExporterID() - } - }() - } - - wg.Wait() - - expected := int64(goroutines * increments) - if id := NextExporterID(); id != expected { - t.Errorf("NextExporterID() = %d; want %d", id, expected) - } -} \ No newline at end of file diff --git a/sdk/log/internal/gen.go b/sdk/log/internal/gen.go index 46b98797fd7..dee3f808f95 100644 --- a/sdk/log/internal/gen.go +++ b/sdk/log/internal/gen.go @@ -4,8 +4,5 @@ // Package internal provides internal functionality for the sdk/log package. package internal // import "go.opentelemetry.io/otel/sdk/log/internal" -//go:generate gotmpl --body=../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/sdk/log/internal/counter\" }" --out=counter/counter.go -//go:generate gotmpl --body=../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go - //go:generate gotmpl --body=../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/sdk/log\" }" --out=x/x.go //go:generate gotmpl --body=../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go diff --git a/sdk/log/internal/observ/simple_log_processor.go b/sdk/log/internal/observ/simple_log_processor.go index f69bc5f1d38..9d709d62419 100644 --- a/sdk/log/internal/observ/simple_log_processor.go +++ b/sdk/log/internal/observ/simple_log_processor.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "sync" + "sync/atomic" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -33,6 +34,24 @@ var measureAttrsPool = sync.Pool{ }, } +// simpleProcessorN is a global 0-based count of the number of simple processor created. +var simpleProcessorN atomic.Int64 + +// NextSimpleProcessorID returns the next unique ID for an simpleProcessor. +func NextSimpleProcessorID() int64 { + const inc = 1 + return simpleProcessorN.Add(inc) - inc +} + +// SetSimpleProcessorID sets the exporter ID counter to v and returns the previous +// value. +// +// This function is useful for testing purposes, allowing you to reset the +// counter. It should not be used in production code. +func SetSimpleProcessorID(v int64) int64 { + return simpleProcessorN.Swap(v) +} + // GetSLPComponentName returns the component name attribute for a // SimpleLogProcessor with the given ID. func GetSLPComponentName(id int64) attribute.KeyValue { diff --git a/sdk/log/internal/observ/simple_log_processor_test.go b/sdk/log/internal/observ/simple_log_processor_test.go index a186d656c4d..cf5ab660869 100644 --- a/sdk/log/internal/observ/simple_log_processor_test.go +++ b/sdk/log/internal/observ/simple_log_processor_test.go @@ -5,6 +5,7 @@ package observ import ( "errors" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -23,6 +24,59 @@ import ( "go.opentelemetry.io/otel/semconv/v1.37.0/otelconv" ) +func TestNextExporterID(t *testing.T) { + SetSimpleProcessorID(0) + + var expected int64 + for range 10 { + id := NextSimpleProcessorID() + if id != expected { + t.Errorf("NextExporterID() = %d; want %d", id, expected) + } + expected++ + } +} + +func TestSetExporterID(t *testing.T) { + SetSimpleProcessorID(0) + + prev := SetSimpleProcessorID(42) + if prev != 0 { + t.Errorf("SetExporterID(42) returned %d; want 0", prev) + } + + id := NextSimpleProcessorID() + if id != 42 { + t.Errorf("NextExporterID() = %d; want 42", id) + } +} + +func TestNextExporterIDConcurrentSafe(t *testing.T) { + SetSimpleProcessorID(0) + + const goroutines = 100 + const increments = 10 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for range goroutines { + go func() { + defer wg.Done() + for range increments { + NextSimpleProcessorID() + } + }() + } + + wg.Wait() + + expected := int64(goroutines * increments) + if id := NextSimpleProcessorID(); id != expected { + t.Errorf("NextExporterID() = %d; want %d", id, expected) + } +} + type errMeterProvider struct { mapi.MeterProvider err error diff --git a/sdk/log/simple.go b/sdk/log/simple.go index 6102818366a..e24259057e7 100644 --- a/sdk/log/simple.go +++ b/sdk/log/simple.go @@ -8,7 +8,6 @@ import ( "sync" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/sdk/log/internal/counter" "go.opentelemetry.io/otel/sdk/log/internal/observ" ) @@ -38,7 +37,7 @@ func NewSimpleProcessor(exporter Exporter, _ ...SimpleProcessorOption) *SimplePr exporter: exporter, } var err error - slp.inst, err = observ.NewSLP(counter.NextExporterID()) + slp.inst, err = observ.NewSLP(observ.NextSimpleProcessorID()) if err != nil { otel.Handle(err) } diff --git a/sdk/log/simple_test.go b/sdk/log/simple_test.go index c6bd957b613..6704513bc3e 100644 --- a/sdk/log/simple_test.go +++ b/sdk/log/simple_test.go @@ -19,7 +19,6 @@ import ( "go.opentelemetry.io/otel/sdk" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/log" - "go.opentelemetry.io/otel/sdk/log/internal/counter" "go.opentelemetry.io/otel/sdk/log/internal/observ" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" @@ -326,7 +325,7 @@ func TestSimpleLogProcessorObservability(t *testing.T) { var rm metricdata.ResourceMetrics require.NoError(t, r.Collect(t.Context(), &rm)) tc.assertMetrics(t, rm) - counter.SetExporterID(0) + observ.SetSimpleProcessorID(0) }) } } From bd7e92f38c84f3abc1e0ea06c0bc7dcc82eeed89 Mon Sep 17 00:00:00 2001 From: ian <141902143+yumosx@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:03:03 +0800 Subject: [PATCH 10/11] Update CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Flcă‚› --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1995f13d755..b7f3623d644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add experimental observability metrics for simple span processor in `go.opentelemetry.io/otel/sdk/trace`. (#7374) - Add experimental observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#7512) - Add experimental observability metrics for manual reader in `go.opentelemetry.io/otel/sdk/metric`. (#7524) -- Add experimental observability metrics for simple log processor in `go.opentelemetry.io/otel/sdk/log`. (#7547) +- Add experimental observability metrics for simple log processor in `go.opentelemetry.io/otel/sdk/log`. (#7548) ### Fixed From 1c9ec3232337d7bf6e993c85ddaccc17a072c555 Mon Sep 17 00:00:00 2001 From: yumosx Date: Wed, 5 Nov 2025 19:23:40 +0800 Subject: [PATCH 11/11] revert. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0393e1a4ad..4b571f93b42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add experimental observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#7512) - Add experimental observability metrics for manual reader in `go.opentelemetry.io/otel/sdk/metric`. (#7524) - Add experimental observability metrics for periodic reader in `go.opentelemetry.io/otel/sdk/metric`. (#7571) +- Add experimental observability metrics for simple log processor in `go.opentelemetry.io/otel/sdk/log`. (#7548) ### Fixed