Skip to content

Commit 58591c0

Browse files
committed
Encapsulate SDK Tracer observability
1 parent 07a91dd commit 58591c0

File tree

10 files changed

+672
-181
lines changed

10 files changed

+672
-181
lines changed

.codespellignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ nam
88
valu
99
thirdparty
1010
addOpt
11+
observ

sdk/trace/internal/observ/doc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package observ provides observability instrumentation for the OTel trace SDK
5+
// package.
6+
package observ
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package observ_test
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
"go.opentelemetry.io/otel"
12+
"go.opentelemetry.io/otel/attribute"
13+
mapi "go.opentelemetry.io/otel/metric"
14+
"go.opentelemetry.io/otel/sdk"
15+
"go.opentelemetry.io/otel/sdk/instrumentation"
16+
"go.opentelemetry.io/otel/sdk/metric"
17+
"go.opentelemetry.io/otel/sdk/metric/metricdata"
18+
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
19+
"go.opentelemetry.io/otel/sdk/trace/internal/observ"
20+
)
21+
22+
func setup(t *testing.T) func() metricdata.ScopeMetrics {
23+
t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true")
24+
25+
orig := otel.GetMeterProvider()
26+
t.Cleanup(func() { otel.SetMeterProvider(orig) })
27+
28+
reader := metric.NewManualReader()
29+
mp := metric.NewMeterProvider(metric.WithReader(reader))
30+
otel.SetMeterProvider(mp)
31+
32+
return func() metricdata.ScopeMetrics {
33+
var got metricdata.ResourceMetrics
34+
require.NoError(t, reader.Collect(context.Background(), &got))
35+
if len(got.ScopeMetrics) != 1 {
36+
return metricdata.ScopeMetrics{}
37+
}
38+
return got.ScopeMetrics[0]
39+
}
40+
}
41+
42+
func scopeMetrics(metrics ...metricdata.Metrics) metricdata.ScopeMetrics {
43+
return metricdata.ScopeMetrics{
44+
Scope: instrumentation.Scope{
45+
Name: observ.ScopeName,
46+
Version: sdk.Version(),
47+
SchemaURL: observ.SchemaURL,
48+
},
49+
Metrics: metrics,
50+
}
51+
}
52+
53+
func check(t *testing.T, got metricdata.ScopeMetrics, want ...metricdata.Metrics) {
54+
o := []metricdatatest.Option{
55+
metricdatatest.IgnoreTimestamp(),
56+
metricdatatest.IgnoreExemplars(),
57+
}
58+
metricdatatest.AssertEqual(t, scopeMetrics(want...), got, o...)
59+
}
60+
61+
func dPt(set attribute.Set, value int64) metricdata.DataPoint[int64] {
62+
return metricdata.DataPoint[int64]{Attributes: set, Value: value}
63+
}
64+
65+
type errMeterProvider struct {
66+
mapi.MeterProvider
67+
68+
err error
69+
}
70+
71+
func (m *errMeterProvider) Meter(string, ...mapi.MeterOption) mapi.Meter {
72+
return &errMeter{err: m.err}
73+
}
74+
75+
type errMeter struct {
76+
mapi.Meter
77+
78+
err error
79+
}
80+
81+
func (m *errMeter) Int64UpDownCounter(string, ...mapi.Int64UpDownCounterOption) (mapi.Int64UpDownCounter, error) {
82+
return nil, m.err
83+
}
84+
85+
func (m *errMeter) Int64Counter(string, ...mapi.Int64CounterOption) (mapi.Int64Counter, error) {
86+
return nil, m.err
87+
}
88+
89+
func (m *errMeter) Int64ObservableUpDownCounter(
90+
string,
91+
...mapi.Int64ObservableUpDownCounterOption,
92+
) (mapi.Int64ObservableUpDownCounter, error) {
93+
return nil, m.err
94+
}
95+
96+
func (m *errMeter) RegisterCallback(mapi.Callback, ...mapi.Observable) (mapi.Registration, error) {
97+
return nil, m.err
98+
}

sdk/trace/internal/observ/tracer.go

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package observ
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
11+
"go.opentelemetry.io/otel"
12+
"go.opentelemetry.io/otel/attribute"
13+
"go.opentelemetry.io/otel/metric"
14+
"go.opentelemetry.io/otel/sdk"
15+
"go.opentelemetry.io/otel/sdk/trace/internal/x"
16+
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
17+
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
18+
"go.opentelemetry.io/otel/trace"
19+
)
20+
21+
const (
22+
// ScopeName is the name of the instrumentation scope.
23+
ScopeName = "go.opentelemetry.io/otel/sdk/trace/internal/observ"
24+
25+
// SchemaURL is the schema URL of the instrumentation.
26+
SchemaURL = semconv.SchemaURL
27+
)
28+
29+
var meterOpts = []metric.MeterOption{
30+
metric.WithInstrumentationVersion(sdk.Version()),
31+
metric.WithSchemaURL(SchemaURL),
32+
}
33+
34+
// Tracer is instrumentation for an OTel SDK Tracer.
35+
type Tracer struct {
36+
enabled bool
37+
38+
live metric.Int64UpDownCounter
39+
started metric.Int64Counter
40+
}
41+
42+
func NewTracer() (Tracer, error) {
43+
if !x.SelfObservability.Enabled() {
44+
return Tracer{}, nil
45+
}
46+
meter := otel.GetMeterProvider().Meter(ScopeName, meterOpts...)
47+
48+
var err error
49+
l, e := otelconv.NewSDKSpanLive(meter)
50+
if e != nil {
51+
e = fmt.Errorf("failed to create span live metric: %w", e)
52+
err = errors.Join(err, e)
53+
}
54+
55+
s, e := otelconv.NewSDKSpanStarted(meter)
56+
if e != nil {
57+
e = fmt.Errorf("failed to create span started metric: %w", e)
58+
err = errors.Join(err, e)
59+
}
60+
61+
return Tracer{enabled: true, live: l.Inst(), started: s.Inst()}, err
62+
}
63+
64+
func (t Tracer) Enabled() bool { return t.enabled }
65+
66+
func (t Tracer) SpanStarted(ctx context.Context, psc trace.SpanContext, span trace.Span) {
67+
key := spanStartedKey{
68+
parent: parentStateNoParent,
69+
sampling: samplingStateDrop,
70+
}
71+
72+
if psc.IsValid() {
73+
if psc.IsRemote() {
74+
key.parent = parentStateRemoteParent
75+
} else {
76+
key.parent = parentStateLocalParent
77+
}
78+
}
79+
80+
if span.IsRecording() {
81+
if span.SpanContext().IsSampled() {
82+
key.sampling = samplingStateRecordAndSample
83+
} else {
84+
key.sampling = samplingStateRecordOnly
85+
}
86+
}
87+
88+
opts := spanStartedOpts[key]
89+
t.started.Add(ctx, 1, opts...)
90+
}
91+
92+
func (t Tracer) SpanLive(ctx context.Context, span trace.Span) {
93+
t.spanLive(ctx, 1, span)
94+
}
95+
96+
func (t Tracer) SpanEnded(ctx context.Context, span trace.Span) {
97+
t.spanLive(ctx, -1, span)
98+
}
99+
100+
func (t Tracer) spanLive(ctx context.Context, value int64, span trace.Span) {
101+
key := spanLiveKey{sampled: span.SpanContext().IsSampled()}
102+
opts := spanLiveOpts[key]
103+
t.live.Add(ctx, value, opts...)
104+
}
105+
106+
type parentState int
107+
108+
const (
109+
parentStateNoParent parentState = iota
110+
parentStateLocalParent
111+
parentStateRemoteParent
112+
)
113+
114+
type samplingState int
115+
116+
const (
117+
samplingStateDrop samplingState = iota
118+
samplingStateRecordOnly
119+
samplingStateRecordAndSample
120+
)
121+
122+
type spanStartedKey struct {
123+
parent parentState
124+
sampling samplingState
125+
}
126+
127+
var spanStartedOpts = map[spanStartedKey][]metric.AddOption{
128+
{
129+
parentStateNoParent,
130+
samplingStateDrop,
131+
}: {
132+
metric.WithAttributeSet(attribute.NewSet(
133+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginNone),
134+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultDrop),
135+
)),
136+
},
137+
{
138+
parentStateLocalParent,
139+
samplingStateDrop,
140+
}: {
141+
metric.WithAttributeSet(attribute.NewSet(
142+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginLocal),
143+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultDrop),
144+
)),
145+
},
146+
{
147+
parentStateRemoteParent,
148+
samplingStateDrop,
149+
}: {
150+
metric.WithAttributeSet(attribute.NewSet(
151+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginRemote),
152+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultDrop),
153+
)),
154+
},
155+
156+
{
157+
parentStateNoParent,
158+
samplingStateRecordOnly,
159+
}: {
160+
metric.WithAttributeSet(attribute.NewSet(
161+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginNone),
162+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly),
163+
)),
164+
},
165+
{
166+
parentStateLocalParent,
167+
samplingStateRecordOnly,
168+
}: {
169+
metric.WithAttributeSet(attribute.NewSet(
170+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginLocal),
171+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly),
172+
)),
173+
},
174+
{
175+
parentStateRemoteParent,
176+
samplingStateRecordOnly,
177+
}: {
178+
metric.WithAttributeSet(attribute.NewSet(
179+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginRemote),
180+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly),
181+
)),
182+
},
183+
184+
{
185+
parentStateNoParent,
186+
samplingStateRecordAndSample,
187+
}: {
188+
metric.WithAttributeSet(attribute.NewSet(
189+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginNone),
190+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordAndSample),
191+
)),
192+
},
193+
{
194+
parentStateLocalParent,
195+
samplingStateRecordAndSample,
196+
}: {
197+
metric.WithAttributeSet(attribute.NewSet(
198+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginLocal),
199+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordAndSample),
200+
)),
201+
},
202+
{
203+
parentStateRemoteParent,
204+
samplingStateRecordAndSample,
205+
}: {
206+
metric.WithAttributeSet(attribute.NewSet(
207+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginRemote),
208+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordAndSample),
209+
)),
210+
},
211+
}
212+
213+
type spanLiveKey struct {
214+
sampled bool
215+
}
216+
217+
var spanLiveOpts = map[spanLiveKey][]metric.AddOption{
218+
{true}: {
219+
metric.WithAttributeSet(attribute.NewSet(
220+
otelconv.SDKSpanLive{}.AttrSpanSamplingResult(
221+
otelconv.SpanSamplingResultRecordAndSample,
222+
),
223+
)),
224+
},
225+
{false}: {
226+
metric.WithAttributeSet(attribute.NewSet(
227+
otelconv.SDKSpanLive{}.AttrSpanSamplingResult(
228+
otelconv.SpanSamplingResultRecordOnly,
229+
),
230+
)),
231+
},
232+
}

0 commit comments

Comments
 (0)