Skip to content

Commit ada09f6

Browse files
committed
refactor (stdoutlog): implement self-observability metrics following contributing guidelines
Signed-off-by: Praful Khanduri <holiodin@gmail.com> added chloggen Signed-off-by: Praful Khanduri <holiodin@gmail.com> fix: return err Signed-off-by: Praful Khanduri <holiodin@gmail.com>
1 parent 7331187 commit ada09f6

File tree

8 files changed

+153
-58
lines changed

8 files changed

+153
-58
lines changed

CHANGELOG.md

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

1111
### Added
1212

13+
- Add experimental self-observability log exporter metrics in `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`.
14+
Check the `go.opentelemetry.io/otel/exporters/stdout/stdoutlog/internal/x` package documentation for more information.
1315
- Add `WithInstrumentationAttributeSet` option to `go.opentelemetry.io/otel/log`, `go.opentelemetry.io/otel/metric`, and `go.opentelemetry.io/otel/trace` packages.
1416
This provides a concurrent-safe and performant alternative to `WithInstrumentationAttributes` by accepting a pre-constructed `attribute.Set`. (#7287)
1517

exporters/stdout/stdoutlog/config.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ func newConfig(options []Option) config {
3838
for _, opt := range options {
3939
cfg = opt.apply(cfg)
4040
}
41-
4241
return cfg
4342
}
4443

exporters/stdout/stdoutlog/exporter.go

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ package stdoutlog // import "go.opentelemetry.io/otel/exporters/stdout/stdoutlog
66
import (
77
"context"
88
"encoding/json"
9+
"errors"
910
"fmt"
1011
"sync"
1112
"sync/atomic"
1213
"time"
1314

1415
"go.opentelemetry.io/otel"
1516
"go.opentelemetry.io/otel/attribute"
17+
"go.opentelemetry.io/otel/exporters/stdout/stdoutlog/internal/counter"
1618
"go.opentelemetry.io/otel/exporters/stdout/stdoutlog/internal/x"
1719
"go.opentelemetry.io/otel/metric"
1820
"go.opentelemetry.io/otel/sdk"
1921
"go.opentelemetry.io/otel/sdk/log"
20-
semconv "go.opentelemetry.io/otel/semconv/v1.36.0"
21-
"go.opentelemetry.io/otel/semconv/v1.36.0/otelconv"
22+
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
23+
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
2224
)
2325

2426
// otelComponentType is a name identifying the type of the OpenTelemetry component.
@@ -29,12 +31,12 @@ var _ log.Exporter = &Exporter{}
2931
// Exporter writes JSON-encoded log records to an [io.Writer] ([os.Stdout] by default).
3032
// Exporter must be created with [New].
3133
type Exporter struct {
32-
encoder atomic.Pointer[json.Encoder]
33-
timestamps bool
34-
selfObservability *selfObservability
34+
encoder atomic.Pointer[json.Encoder]
35+
timestamps bool
36+
inst *instrumentationImpl
3537
}
3638

37-
type selfObservability struct {
39+
type instrumentationImpl struct {
3840
attrs []attribute.KeyValue
3941
inflightMetric otelconv.SDKExporterLogInflight
4042
exportedMetric otelconv.SDKExporterLogExported
@@ -54,23 +56,23 @@ func New(options ...Option) (*Exporter, error) {
5456
timestamps: cfg.Timestamps,
5557
}
5658
e.encoder.Store(enc)
57-
selfObs, err := newSelfObservability()
59+
selfObs, err := newInstrumentation()
5860
if err != nil {
5961
return nil, err
6062
}
61-
e.selfObservability = selfObs
63+
e.inst = selfObs
6264

6365
return &e, nil
6466
}
6567

66-
func newSelfObservability() (*selfObservability, error) {
68+
func newInstrumentation() (*instrumentationImpl, error) {
6769
if !x.SelfObservability.Enabled() {
6870
return nil, nil
6971
}
7072

71-
selfObservability := &selfObservability{
73+
inst := &instrumentationImpl{
7274
attrs: []attribute.KeyValue{
73-
semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, nextExporterID())),
75+
semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, counter.NextExporterID())),
7476
semconv.OTelComponentTypeKey.String(otelComponentType),
7577
},
7678
}
@@ -81,24 +83,23 @@ func newSelfObservability() (*selfObservability, error) {
8183
metric.WithSchemaURL(semconv.SchemaURL),
8284
)
8385

84-
var err error
85-
if selfObservability.inflightMetric, err = otelconv.NewSDKExporterLogInflight(m); err != nil {
86-
return nil, err
87-
}
88-
if selfObservability.exportedMetric, err = otelconv.NewSDKExporterLogExported(m); err != nil {
89-
return nil, err
90-
}
91-
if selfObservability.operationDurationMetric, err = otelconv.NewSDKExporterOperationDuration(m); err != nil {
92-
return nil, err
93-
}
86+
var err, e error
87+
inst.inflightMetric, e = otelconv.NewSDKExporterLogInflight(m)
88+
err = errors.Join(err, e)
89+
90+
inst.exportedMetric, e = otelconv.NewSDKExporterLogExported(m)
91+
err = errors.Join(err, e)
92+
93+
inst.operationDurationMetric, e = otelconv.NewSDKExporterOperationDuration(m)
94+
err = errors.Join(err, e)
9495

95-
return selfObservability, nil
96+
return inst, err
9697
}
9798

9899
// Export exports log records to writer.
99100
func (e *Exporter) Export(ctx context.Context, records []log.Record) error {
100101
var err error
101-
if e.selfObservability != nil && x.SelfObservability.Enabled() {
102+
if e.inst != nil && x.SelfObservability.Enabled() {
102103
err = e.exportWithSelfObservability(ctx, records)
103104
} else {
104105
err = e.exportWithoutSelfObservability(ctx, records)
@@ -108,7 +109,7 @@ func (e *Exporter) Export(ctx context.Context, records []log.Record) error {
108109

109110
const bufferSize = 4
110111

111-
var selfObservabilityBuffer = sync.Pool{
112+
var attrPool = sync.Pool{
112113
New: func() any {
113114
buf := make([]attribute.KeyValue, 0, bufferSize)
114115
return &buf
@@ -120,31 +121,34 @@ func (e *Exporter) exportWithSelfObservability(ctx context.Context, records []lo
120121
count := int64(len(records))
121122
start := time.Now()
122123

123-
e.selfObservability.inflightMetric.Add(ctx, count, e.selfObservability.attrs...)
124+
e.inst.inflightMetric.Add(ctx, count, e.inst.attrs...)
124125

126+
bufPtrAny := attrPool.Get()
127+
bufPtr, ok := bufPtrAny.(*[]attribute.KeyValue)
128+
if !ok || bufPtr == nil {
129+
bufPtr = &[]attribute.KeyValue{}
130+
}
125131
defer func() {
126-
bufPtrAny := selfObservabilityBuffer.Get()
127-
bufPtr, ok := bufPtrAny.(*[]attribute.KeyValue)
128-
if !ok || bufPtr == nil {
129-
bufPtr = &[]attribute.KeyValue{}
130-
}
132+
*bufPtr = (*bufPtr)[:0]
133+
attrPool.Put(bufPtr)
134+
}()
131135

136+
defer func() {
132137
addAttrs := (*bufPtr)[:0]
133-
addAttrs = append(addAttrs, e.selfObservability.attrs...)
138+
addAttrs = append(addAttrs, e.inst.attrs...)
134139

135140
if err != nil {
136141
addAttrs = append(addAttrs, semconv.ErrorType(err))
137142
}
138-
e.selfObservability.exportedMetric.Add(ctx, count, addAttrs...)
139-
e.selfObservability.inflightMetric.Add(ctx, -count, e.selfObservability.attrs...)
140-
e.selfObservability.operationDurationMetric.Record(ctx, time.Since(start).Seconds(), addAttrs...)
143+
e.inst.exportedMetric.Add(ctx, count, addAttrs...)
144+
e.inst.inflightMetric.Add(ctx, -count, e.inst.attrs...)
145+
e.inst.operationDurationMetric.Record(ctx, time.Since(start).Seconds(), addAttrs...)
141146

142-
*bufPtr = addAttrs[:0]
143-
selfObservabilityBuffer.Put(bufPtr)
147+
*bufPtr = addAttrs
144148
}()
145149

146150
err = e.exportWithoutSelfObservability(ctx, records)
147-
return
151+
return err
148152
}
149153

150154
// exportWithoutSelfObservability exports logs without self-observability metrics.
@@ -178,11 +182,3 @@ func (e *Exporter) Shutdown(context.Context) error {
178182
func (*Exporter) ForceFlush(context.Context) error {
179183
return nil
180184
}
181-
182-
var exporterIDCounter atomic.Int64
183-
184-
// nextExporterID returns a new unique ID for an exporter.
185-
// the starting value is 0, and it increments by 1 for each call.
186-
func nextExporterID() int64 {
187-
return exporterIDCounter.Add(1) - 1
188-
}

exporters/stdout/stdoutlog/exporter_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"go.opentelemetry.io/otel"
1818
"go.opentelemetry.io/otel/attribute"
19+
"go.opentelemetry.io/otel/exporters/stdout/stdoutlog/internal/counter"
1920
"go.opentelemetry.io/otel/log"
2021
"go.opentelemetry.io/otel/sdk/instrumentation"
2122
sdklog "go.opentelemetry.io/otel/sdk/log"
@@ -24,7 +25,7 @@ import (
2425
"go.opentelemetry.io/otel/sdk/metric/metricdata"
2526
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
2627
"go.opentelemetry.io/otel/sdk/resource"
27-
"go.opentelemetry.io/otel/semconv/v1.36.0/otelconv"
28+
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
2829
"go.opentelemetry.io/otel/trace"
2930
)
3031

@@ -932,7 +933,7 @@ func TestNewSelfObservability(t *testing.T) {
932933

933934
if ranOnce {
934935
// Reset the global exporter ID counter for deterministic tests
935-
exporterIDCounter.Store(0) // First call to nextExporterID() will return 0
936+
counter.SetExporterID(0) // First call to NextExporterID() will return 0
936937
}
937938

938939
prev := otel.GetMeterProvider()

exporters/stdout/stdoutlog/go.mod

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,14 @@ go 1.24.0
66
retract v0.12.0
77

88
require (
9-
github.com/stretchr/testify v1.10.0
10-
go.opentelemetry.io/otel v1.37.0
11-
go.opentelemetry.io/otel/log v0.13.0
12-
go.opentelemetry.io/otel/metric v1.37.0
13-
go.opentelemetry.io/otel/sdk v1.37.0
14-
go.opentelemetry.io/otel/sdk/log v0.13.0
15-
go.opentelemetry.io/otel/sdk/log/logtest v0.13.0
16-
go.opentelemetry.io/otel/sdk/metric v1.37.0
17-
go.opentelemetry.io/otel/trace v1.37.0
189
github.com/stretchr/testify v1.11.1
1910
go.opentelemetry.io/otel v1.38.0
2011
go.opentelemetry.io/otel/log v0.14.0
12+
go.opentelemetry.io/otel/metric v1.38.0
2113
go.opentelemetry.io/otel/sdk v1.38.0
2214
go.opentelemetry.io/otel/sdk/log v0.14.0
2315
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0
16+
go.opentelemetry.io/otel/sdk/metric v1.38.0
2417
go.opentelemetry.io/otel/trace v1.38.0
2518
)
2619

@@ -31,7 +24,6 @@ require (
3124
github.com/google/uuid v1.6.0 // indirect
3225
github.com/pmezard/go-difflib v1.0.0 // indirect
3326
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
34-
go.opentelemetry.io/otel/metric v1.38.0 // indirect
3527
golang.org/x/sys v0.36.0 // indirect
3628
gopkg.in/yaml.v3 v3.0.1 // indirect
3729
)

exporters/stdout/stdoutlog/internal/counter/counter.go

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

exporters/stdout/stdoutlog/internal/counter/counter_test.go

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package internal provides internal functionality for the stdoutlog
5+
// package.
6+
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdoutlog/internal"
7+
8+
//go:generate gotmpl --body=../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/stdout/stdoutlog/internal/counter\" }" --out=counter/counter.go
9+
//go:generate gotmpl --body=../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go

0 commit comments

Comments
 (0)