@@ -8,7 +8,10 @@ use azure_core::{
8
8
tracing:: { AsAny , AttributeValue , Span , SpanGuard , SpanStatus } ,
9
9
Result ,
10
10
} ;
11
- use opentelemetry:: trace:: TraceContextExt ;
11
+ use opentelemetry:: { propagation:: TextMapPropagator , trace:: TraceContextExt } ;
12
+ use opentelemetry_http:: HeaderInjector ;
13
+ use opentelemetry_sdk:: propagation:: TraceContextPropagator ;
14
+ use reqwest:: header:: HeaderMap ;
12
15
use std:: { error:: Error as StdError , sync:: Arc } ;
13
16
14
17
/// newtype for Azure Core SpanKind to enable conversion to OpenTelemetry SpanKind
@@ -76,7 +79,34 @@ impl Span for OpenTelemetrySpan {
76
79
self . context . span ( ) . set_status ( otel_status) ;
77
80
}
78
81
79
- fn propagate_headers ( & self , _request : & mut azure_core:: http:: Request ) { }
82
+ fn propagate_headers ( & self , request : & mut azure_core:: http:: Request ) {
83
+ // A TraceContextPropagator is used to inject trace context information into HTTP headers.
84
+ let trace_propagator = TraceContextPropagator :: new ( ) ;
85
+ // We need to map between a reqwest header map (which is what the OpenTelemetry SDK requires)
86
+ // and the Azure Core request headers.
87
+ //
88
+ // We start with an empty header map and inject the OpenTelemetry headers into it.
89
+ let mut header_map = HeaderMap :: new ( ) ;
90
+ trace_propagator. inject_context ( & self . context , & mut HeaderInjector ( & mut header_map) ) ;
91
+
92
+ // We then insert each of the headers from the OpenTelemetry header map into the
93
+ // Request's header map.
94
+ for ( key, value) in header_map. into_iter ( ) {
95
+ // Note: The OpenTelemetry HeaderInjector will always produce unique header names, so we don't need to
96
+ // handle the multiple headers case here.
97
+ if let Some ( key) = key {
98
+ // Convert HeaderName to &str for insertion.
99
+ let value_str = value. to_str ( ) . unwrap ( ) . to_string ( ) ;
100
+ request. insert_header (
101
+ azure_core:: http:: headers:: HeaderName :: from ( key. to_string ( ) ) ,
102
+ azure_core:: http:: headers:: HeaderValue :: from ( value_str) ,
103
+ ) ;
104
+ } else {
105
+ // If the key is invalid, we skip it
106
+ tracing:: warn!( "Invalid header key: {:?}" , key) ;
107
+ }
108
+ }
109
+ }
80
110
81
111
fn set_current (
82
112
& self ,
@@ -117,7 +147,7 @@ impl Drop for OpenTelemetrySpanGuard {
117
147
#[ cfg( test) ]
118
148
mod tests {
119
149
use crate :: telemetry:: OpenTelemetryTracerProvider ;
120
- use azure_core:: http:: Context as AzureContext ;
150
+ use azure_core:: http:: { Context as AzureContext , Url } ;
121
151
use azure_core:: tracing:: { Attribute , AttributeValue , SpanKind , SpanStatus , TracerProvider } ;
122
152
use opentelemetry:: trace:: TraceContextExt ;
123
153
use opentelemetry:: { Context , Key , KeyValue , Value } ;
@@ -158,6 +188,34 @@ mod tests {
158
188
}
159
189
}
160
190
191
+ // cspell: ignore traceparent tracestate
192
+ #[ test]
193
+ fn test_open_telemetry_span_propagate ( ) {
194
+ let ( otel_tracer_provider, otel_exporter) = create_exportable_tracer_provider ( ) ;
195
+
196
+ let tracer_provider = OpenTelemetryTracerProvider :: new ( otel_tracer_provider) ;
197
+ assert ! ( tracer_provider. is_ok( ) ) ;
198
+ let tracer =
199
+ tracer_provider
200
+ . unwrap ( )
201
+ . get_tracer ( Some ( "Microsoft.SpecialCase" ) , "test" , "0.1.0" ) ;
202
+ let span = tracer. start_span ( "test_span" , SpanKind :: Client , vec ! [ ] ) ;
203
+ let mut request = azure_core:: http:: Request :: new (
204
+ Url :: parse ( "http://example.com" ) . unwrap ( ) ,
205
+ azure_core:: http:: Method :: Get ,
206
+ ) ;
207
+ span. propagate_headers ( & mut request) ;
208
+ trace ! ( "Request headers after propagation: {:?}" , request. headers( ) ) ;
209
+ let traceparent = azure_core:: http:: headers:: HeaderName :: from ( "traceparent" ) ;
210
+ let tracestate = azure_core:: http:: headers:: HeaderName :: from ( "tracestate" ) ;
211
+ request. headers ( ) . get_as :: < String , _ > ( & traceparent) . unwrap ( ) ;
212
+ request. headers ( ) . get_as :: < String , _ > ( & tracestate) . unwrap ( ) ;
213
+ span. end ( ) ;
214
+
215
+ let finished_spans = otel_exporter. get_finished_spans ( ) . unwrap ( ) ;
216
+ assert_eq ! ( finished_spans. len( ) , 1 ) ;
217
+ }
218
+
161
219
#[ test]
162
220
fn test_open_telemetry_span_hierarchy ( ) {
163
221
let ( otel_tracer_provider, otel_exporter) = create_exportable_tracer_provider ( ) ;
0 commit comments