Skip to content

Commit 4d55b0c

Browse files
authored
[tracing/azotel] Enable span link and propagation support (#24081)
* enable span link and propagation support * remove azcore/tracing changes * update go mod to point to the main branch * get ut up to 100% * one more ut * better docs * export propagator option` * remove Propagator option * adding back a comment * new version and changelog * update version and changelog one more time
1 parent dc2f507 commit 4d55b0c

File tree

5 files changed

+226
-19
lines changed

5 files changed

+226
-19
lines changed

sdk/tracing/azotel/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
### Other Changes
1212

1313
* Updated dependencies.
14+
* Added OTel implementation for `Provider.NewPropagator()` to return a `propagation.TraceContext()` propagator for context propagation.
15+
* Added OTel implementation for `tracing.LinkFromContext()` to return a OTel Span Link with the passed in attributes attached to it.
16+
* Added OTel implementation for `Span.AddLink()` to add an OTel Span Link to the current span.
17+
* Added OTel implementation for `Span.SpanContext()` to return the OTel Span Context of the current span.
1418

1519
## 0.4.0 (2023-11-07)
1620

sdk/tracing/azotel/go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ require (
3232
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
3333
go.opentelemetry.io/otel/metric v1.33.0 // indirect
3434
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
35-
golang.org/x/crypto v0.31.0 // indirect
36-
golang.org/x/net v0.33.0 // indirect
37-
golang.org/x/sys v0.28.0 // indirect
35+
golang.org/x/crypto v0.32.0 // indirect
36+
golang.org/x/net v0.34.0 // indirect
37+
golang.org/x/sys v0.29.0 // indirect
3838
golang.org/x/text v0.21.0 // indirect
3939
google.golang.org/genproto/googleapis/api v0.0.0-20241219184827-bd154493cd20 // indirect
4040
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219184827-bd154493cd20 // indirect
4141
google.golang.org/grpc v1.69.2 // indirect
4242
google.golang.org/protobuf v1.36.0 // indirect
4343
gopkg.in/yaml.v3 v3.0.1 // indirect
4444
)
45+
46+
replace github.com/Azure/azure-sdk-for-go/sdk/azcore => github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1-0.20250211200623-5579d8255255

sdk/tracing/azotel/go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
2-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
1+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1-0.20250211200623-5579d8255255 h1:5EK1NvmFZWehKBOaZQenWgOcXAercz1j03Lc/rSc3Os=
2+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1-0.20250211200623-5579d8255255/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
33
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
44
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
55
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
@@ -75,13 +75,13 @@ go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qq
7575
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
7676
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
7777
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
78-
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
79-
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
80-
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
81-
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
78+
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
79+
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
80+
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
81+
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
8282
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
83-
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
84-
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
83+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
84+
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
8585
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
8686
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
8787
google.golang.org/genproto/googleapis/api v0.0.0-20241219184827-bd154493cd20 h1:VtcT0lutoLuZk634hxhu2lc6ryh5pmn1pPOTuWReKZI=

sdk/tracing/azotel/otel.go

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing"
1515
"go.opentelemetry.io/otel/attribute"
1616
"go.opentelemetry.io/otel/codes"
17+
"go.opentelemetry.io/otel/propagation"
1718
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
1819
"go.opentelemetry.io/otel/trace"
1920
)
@@ -33,19 +34,31 @@ func NewTracingProvider(tracerProvider trace.TracerProvider, opts *TracingProvid
3334
return tracing.NewTracer(func(ctx context.Context, spanName string, options *tracing.SpanOptions) (context.Context, tracing.Span) {
3435
kind := tracing.SpanKindInternal
3536
var attrs []attribute.KeyValue
37+
var links []trace.Link
3638
if options != nil {
3739
kind = options.Kind
3840
attrs = convertAttributes(options.Attributes)
41+
links = convertLinks(options.Links)
3942
}
40-
ctx, span := tracer.Start(ctx, spanName, trace.WithSpanKind(convertSpanKind(kind)), trace.WithAttributes(attrs...))
43+
ctx, span := tracer.Start(ctx, spanName, trace.WithSpanKind(convertSpanKind(kind)), trace.WithAttributes(attrs...), trace.WithLinks(links...))
4144
return ctx, convertSpan(span)
4245
}, &tracing.TracerOptions{
4346
SpanFromContext: func(ctx context.Context) tracing.Span {
4447
return convertSpan(trace.SpanFromContext(ctx))
4548
},
49+
LinkFromContext: func(ctx context.Context, attrs ...tracing.Attribute) tracing.Link {
50+
link := trace.LinkFromContext(ctx, convertAttributes(attrs)...)
51+
return tracing.Link{
52+
SpanContext: convertOTelSpanContext(link.SpanContext),
53+
Attributes: attrs,
54+
}
55+
},
4656
})
47-
48-
}, nil)
57+
}, &tracing.ProviderOptions{
58+
NewPropagatorFn: func() tracing.Propagator {
59+
return convertPropagator(propagation.TraceContext{})
60+
},
61+
})
4962
}
5063

5164
func convertSpan(traceSpan trace.Span) tracing.Span {
@@ -59,6 +72,12 @@ func convertSpan(traceSpan trace.Span) tracing.Span {
5972
AddEvent: func(name string, attrs ...tracing.Attribute) {
6073
traceSpan.AddEvent(name, trace.WithAttributes(convertAttributes(attrs)...))
6174
},
75+
AddLink: func(link tracing.Link) {
76+
traceSpan.AddLink(convertLink(link))
77+
},
78+
SpanContext: func() tracing.SpanContext {
79+
return convertOTelSpanContext(traceSpan.SpanContext())
80+
},
6281
SetStatus: func(code tracing.SpanStatus, desc string) {
6382
traceSpan.SetStatus(convertStatus(code), desc)
6483
},
@@ -87,6 +106,42 @@ func convertAttributes(attrs []tracing.Attribute) []attribute.KeyValue {
87106
return keyvals
88107
}
89108

109+
func convertLinks(links []tracing.Link) []trace.Link {
110+
var otelLinks []trace.Link
111+
for _, link := range links {
112+
otelLinks = append(otelLinks, convertLink(link))
113+
}
114+
return otelLinks
115+
}
116+
117+
func convertLink(link tracing.Link) trace.Link {
118+
return trace.Link{
119+
SpanContext: convertSpanContext(link.SpanContext),
120+
Attributes: convertAttributes(link.Attributes),
121+
}
122+
}
123+
124+
func convertSpanContext(spanContext tracing.SpanContext) trace.SpanContext {
125+
oTelTraceState, _ := trace.ParseTraceState(string(spanContext.TraceState()))
126+
return trace.NewSpanContext(trace.SpanContextConfig{
127+
TraceID: trace.TraceID(spanContext.TraceID()),
128+
SpanID: trace.SpanID(spanContext.SpanID()),
129+
TraceFlags: trace.TraceFlags(spanContext.TraceFlags()),
130+
TraceState: oTelTraceState,
131+
Remote: spanContext.IsRemote(),
132+
})
133+
}
134+
135+
func convertOTelSpanContext(spanContext trace.SpanContext) tracing.SpanContext {
136+
return tracing.NewSpanContext(tracing.SpanContextConfig{
137+
TraceID: tracing.TraceID(spanContext.TraceID()),
138+
SpanID: tracing.SpanID(spanContext.SpanID()),
139+
TraceFlags: tracing.TraceFlags(spanContext.TraceFlags()),
140+
TraceState: tracing.TraceState(spanContext.TraceState().String()),
141+
Remote: spanContext.IsRemote(),
142+
})
143+
}
144+
90145
func convertSpanKind(sk tracing.SpanKind) trace.SpanKind {
91146
switch sk {
92147
case tracing.SpanKindServer:
@@ -112,3 +167,17 @@ func convertStatus(ss tracing.SpanStatus) codes.Code {
112167
return codes.Unset
113168
}
114169
}
170+
171+
func convertPropagator(pr propagation.TextMapPropagator) tracing.Propagator {
172+
return tracing.NewPropagator(tracing.PropagatorImpl{
173+
Inject: func(ctx context.Context, carrier tracing.Carrier) {
174+
pr.Inject(ctx, carrier)
175+
},
176+
Extract: func(ctx context.Context, carrier tracing.Carrier) context.Context {
177+
return pr.Extract(ctx, carrier)
178+
},
179+
Fields: func() []string {
180+
return pr.Fields()
181+
},
182+
})
183+
}

sdk/tracing/azotel/otel_test.go

Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/stretchr/testify/require"
2020
"go.opentelemetry.io/otel/attribute"
2121
"go.opentelemetry.io/otel/codes"
22+
"go.opentelemetry.io/otel/propagation"
2223
tracesdk "go.opentelemetry.io/otel/sdk/trace"
2324
"go.opentelemetry.io/otel/trace"
2425
"go.opentelemetry.io/otel/trace/embedded"
@@ -42,6 +43,10 @@ func TestNewTracingProvider(t *testing.T) {
4243
emptySpan := client.Tracer().SpanFromContext(context.Background())
4344
emptySpan.AddEvent("noop_event")
4445

46+
// returns an empty link as there is no span yet
47+
emptyLink := client.Tracer().LinkFromContext(context.Background())
48+
require.Empty(t, emptyLink)
49+
4550
ctx, endSpan := azruntime.StartSpan(context.Background(), "test_span", client.Tracer(), nil)
4651

4752
req, err := azruntime.NewRequest(ctx, http.MethodGet, "https://www.microsoft.com/")
@@ -50,6 +55,9 @@ func TestNewTracingProvider(t *testing.T) {
5055
startedSpan := client.Tracer().SpanFromContext(req.Raw().Context())
5156
startedSpan.AddEvent("post_event")
5257

58+
startedSpanLink := client.Tracer().LinkFromContext(req.Raw().Context())
59+
require.NotEmpty(t, startedSpanLink)
60+
5361
_, err = client.Pipeline().Do(req)
5462
require.NoError(t, err)
5563

@@ -61,6 +69,34 @@ func TestNewTracingProvider(t *testing.T) {
6169
require.Len(t, exporter.spans, 2)
6270
}
6371

72+
func TestPropagator(t *testing.T) {
73+
provider := NewTracingProvider(tracesdk.NewTracerProvider(), nil)
74+
tracer := provider.NewTracer("test", "1.0")
75+
propagator := provider.NewPropagator()
76+
require.EqualValues(t, 2, len(propagator.Fields()))
77+
78+
mapCarrier := propagation.MapCarrier{}
79+
carrier := tracing.NewCarrier(tracing.CarrierImpl{
80+
Get: mapCarrier.Get,
81+
Set: mapCarrier.Set,
82+
Keys: mapCarrier.Keys,
83+
})
84+
85+
ctx, endSpan := azruntime.StartSpan(context.Background(), "test_span", tracer, nil)
86+
spanContext := tracer.SpanFromContext(ctx).SpanContext()
87+
require.NotNil(t, spanContext)
88+
require.False(t, spanContext.IsRemote())
89+
propagator.Inject(ctx, carrier)
90+
endSpan(nil)
91+
92+
extractedCtx := propagator.Extract(context.Background(), carrier)
93+
extractedSpanContext := tracer.SpanFromContext(extractedCtx).SpanContext()
94+
require.NotNil(t, extractedSpanContext)
95+
require.True(t, extractedSpanContext.IsRemote())
96+
require.EqualValues(t, spanContext.TraceID(), extractedSpanContext.TraceID())
97+
require.EqualValues(t, spanContext.SpanID(), extractedSpanContext.SpanID())
98+
}
99+
64100
func TestConvertSpan(t *testing.T) {
65101
ts := testSpan{t: t}
66102
span := convertSpan(&ts)
@@ -84,6 +120,9 @@ func TestConvertSpan(t *testing.T) {
84120
span.SetAttributes(attr)
85121
require.Len(t, ts.attributes, 1)
86122

123+
span.AddLink(tracing.Link{})
124+
require.Len(t, ts.links, 1)
125+
87126
const statusDesc = "everything is ok"
88127
span.SetStatus(tracing.SpanStatusOK, statusDesc)
89128
assert.EqualValues(t, tracing.SpanStatusOK, ts.statusCode)
@@ -169,6 +208,65 @@ func TestConvertAttributes(t *testing.T) {
169208
}
170209
}
171210

211+
func TestConvertLinks(t *testing.T) {
212+
attr := tracing.Attribute{
213+
Key: "key",
214+
Value: "value",
215+
}
216+
spanContext := tracing.NewSpanContext(tracing.SpanContextConfig{
217+
TraceID: tracing.TraceID{1, 2, 3, 4, 5, 6, 7, 8},
218+
SpanID: tracing.SpanID{1, 2, 3, 4, 5, 6, 7, 8},
219+
TraceFlags: tracing.TraceFlags(0x1),
220+
TraceState: "key1=val1,key2=val2",
221+
Remote: true,
222+
})
223+
224+
links := convertLinks([]tracing.Link{
225+
{
226+
SpanContext: spanContext,
227+
},
228+
{
229+
Attributes: []tracing.Attribute{attr},
230+
},
231+
{
232+
SpanContext: spanContext,
233+
Attributes: []tracing.Attribute{attr},
234+
},
235+
})
236+
require.Len(t, links, 3)
237+
require.NotNil(t, links[0].SpanContext)
238+
require.True(t, links[0].SpanContext.IsRemote())
239+
require.Len(t, links[0].Attributes, 0)
240+
241+
require.NotNil(t, links[1].SpanContext)
242+
require.False(t, links[1].SpanContext.IsRemote())
243+
require.Len(t, links[1].Attributes, 1)
244+
245+
require.NotNil(t, links[2].SpanContext)
246+
require.True(t, links[2].SpanContext.IsRemote())
247+
require.Len(t, links[2].Attributes, 1)
248+
}
249+
250+
func TestConvertSpanContext(t *testing.T) {
251+
traceID := tracing.TraceID{1, 2, 3, 4, 5, 6, 7, 8}
252+
spanID := tracing.SpanID{1, 2, 3, 4, 5, 6, 7, 8}
253+
traceFlags := tracing.TraceFlags(0x1)
254+
spanContext := tracing.NewSpanContext(tracing.SpanContextConfig{
255+
TraceID: traceID,
256+
SpanID: spanID,
257+
TraceFlags: traceFlags,
258+
TraceState: "key1=val1,key2=val2",
259+
Remote: true,
260+
})
261+
262+
otelSpanContext := convertSpanContext(spanContext)
263+
assert.EqualValues(t, traceID, otelSpanContext.TraceID())
264+
assert.EqualValues(t, spanID, otelSpanContext.SpanID())
265+
assert.EqualValues(t, traceFlags, otelSpanContext.TraceFlags())
266+
assert.EqualValues(t, "key1=val1,key2=val2", otelSpanContext.TraceState().String())
267+
assert.True(t, otelSpanContext.IsRemote())
268+
}
269+
172270
func TestConvertSpanKind(t *testing.T) {
173271
assert.EqualValues(t, trace.SpanKindClient, convertSpanKind(tracing.SpanKindClient))
174272
assert.EqualValues(t, trace.SpanKindConsumer, convertSpanKind(tracing.SpanKindConsumer))
@@ -185,6 +283,22 @@ func TestConvertStatus(t *testing.T) {
185283
assert.EqualValues(t, codes.Unset, convertStatus(tracing.SpanStatus(12345)))
186284
}
187285

286+
func TestConvertPropagator(t *testing.T) {
287+
carrier := tracing.NewCarrier(tracing.CarrierImpl{
288+
Get: func(key string) string { return "" },
289+
Set: func(key, value string) {},
290+
Keys: func() []string { return nil },
291+
})
292+
propagator := &testPropagator{}
293+
otelPropagator := convertPropagator(propagator)
294+
require.NotNil(t, otelPropagator)
295+
otelPropagator.Inject(context.Background(), carrier)
296+
otelPropagator.Extract(context.Background(), carrier)
297+
require.True(t, propagator.injectCalled)
298+
require.True(t, propagator.extractCalled)
299+
require.Len(t, propagator.Fields(), 1)
300+
}
301+
188302
type testExporter struct {
189303
spans []string
190304
}
@@ -207,12 +321,12 @@ type testSpan struct {
207321

208322
t *testing.T
209323
attributes []attribute.KeyValue
324+
links []trace.Link
210325
eventName string
211326
eventOptions []trace.EventOption
212327
endCalled bool
213328
statusCode codes.Code
214329
statusDesc string
215-
link trace.Link
216330
}
217331

218332
func (ts *testSpan) End(options ...trace.SpanEndOption) {
@@ -224,10 +338,6 @@ func (ts *testSpan) AddEvent(name string, options ...trace.EventOption) {
224338
ts.eventOptions = options
225339
}
226340

227-
func (ts *testSpan) AddLink(link trace.Link) {
228-
ts.link = link
229-
}
230-
231341
func (ts *testSpan) IsRecording() bool {
232342
ts.t.Fatal("IsRecording not required")
233343
return false
@@ -255,7 +365,29 @@ func (ts *testSpan) SetAttributes(kv ...attribute.KeyValue) {
255365
ts.attributes = kv
256366
}
257367

368+
func (ts *testSpan) AddLink(link trace.Link) {
369+
ts.links = append(ts.links, link)
370+
}
371+
258372
func (ts *testSpan) TracerProvider() trace.TracerProvider {
259373
ts.t.Fatal("TracerProvider not required")
260374
return nil
261375
}
376+
377+
type testPropagator struct {
378+
injectCalled bool
379+
extractCalled bool
380+
}
381+
382+
func (tp *testPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
383+
tp.injectCalled = true
384+
}
385+
386+
func (tp *testPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
387+
tp.extractCalled = true
388+
return ctx
389+
}
390+
391+
func (tp *testPropagator) Fields() []string {
392+
return []string{"testfield"}
393+
}

0 commit comments

Comments
 (0)