1#![deny(unused_crate_dependencies)]
45
46pub(crate) mod dd_proto {
47 include!(concat!(env!("OUT_DIR"), "/dd_trace.rs"));
48}
49
50mod exporter;
51
52mod propagator {
53 use opentelemetry::{
54 propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
55 trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState},
56 Context,
57 };
58
59 use crate::exporter::u128_to_u64s;
60
61 const DATADOG_TRACE_ID_HEADER: &str = "x-datadog-trace-id";
62 const DATADOG_PARENT_ID_HEADER: &str = "x-datadog-parent-id";
63 const DATADOG_SAMPLING_PRIORITY_HEADER: &str = "x-datadog-sampling-priority";
64
65 const TRACE_FLAG_DEFERRED: TraceFlags = TraceFlags::new(0x02);
66
67 lazy_static::lazy_static! {
68 static ref DATADOG_HEADER_FIELDS: [String; 3] = [
69 DATADOG_TRACE_ID_HEADER.to_string(),
70 DATADOG_PARENT_ID_HEADER.to_string(),
71 DATADOG_SAMPLING_PRIORITY_HEADER.to_string(),
72 ];
73 }
74
75 enum SamplingPriority {
76 UserReject = -1,
77 AutoReject = 0,
78 AutoKeep = 1,
79 UserKeep = 2,
80 }
81
82 #[derive(Debug)]
83 enum ExtractError {
84 TraceId,
85 SpanId,
86 SamplingPriority,
87 }
88
89 #[derive(Clone, Debug, Default)]
105 #[allow(clippy::module_name_repetitions)]
106 pub struct DatadogPropagator {
107 _private: (),
108 }
109
110 impl DatadogPropagator {
111 #[must_use]
113 pub fn new() -> Self {
114 DatadogPropagator::default()
115 }
116
117 fn extract_trace_id(trace_id: &str) -> Result<TraceId, ExtractError> {
118 trace_id
119 .parse::<u64>()
120 .map(|id| TraceId::from(u128::from(id).to_be_bytes()))
121 .map_err(|_| ExtractError::TraceId)
122 }
123
124 fn extract_span_id(span_id: &str) -> Result<SpanId, ExtractError> {
125 span_id
126 .parse::<u64>()
127 .map(|id| SpanId::from(id.to_be_bytes()))
128 .map_err(|_| ExtractError::SpanId)
129 }
130
131 fn extract_sampling_priority(
132 sampling_priority: &str,
133 ) -> Result<SamplingPriority, ExtractError> {
134 let i = sampling_priority
135 .parse::<i32>()
136 .map_err(|_| ExtractError::SamplingPriority)?;
137
138 match i {
139 -1 => Ok(SamplingPriority::UserReject),
140 0 => Ok(SamplingPriority::AutoReject),
141 1 => Ok(SamplingPriority::AutoKeep),
142 2 => Ok(SamplingPriority::UserKeep),
143 _ => Err(ExtractError::SamplingPriority),
144 }
145 }
146
147 fn extract_span_context(extractor: &dyn Extractor) -> Result<SpanContext, ExtractError> {
148 let trace_id =
149 Self::extract_trace_id(extractor.get(DATADOG_TRACE_ID_HEADER).unwrap_or(""))?;
150 let span_id =
153 Self::extract_span_id(extractor.get(DATADOG_PARENT_ID_HEADER).unwrap_or(""))
154 .unwrap_or(SpanId::INVALID);
155 let sampling_priority = Self::extract_sampling_priority(
156 extractor
157 .get(DATADOG_SAMPLING_PRIORITY_HEADER)
158 .unwrap_or(""),
159 );
160 let sampled = match sampling_priority {
161 Ok(SamplingPriority::UserReject | SamplingPriority::AutoReject) => {
162 TraceFlags::default()
163 }
164 Ok(SamplingPriority::UserKeep | SamplingPriority::AutoKeep) => TraceFlags::SAMPLED,
165 Err(_) => TRACE_FLAG_DEFERRED,
167 };
168
169 let trace_state = TraceState::default();
170
171 Ok(SpanContext::new(
172 trace_id,
173 span_id,
174 sampled,
175 true,
176 trace_state,
177 ))
178 }
179 }
180
181 impl TextMapPropagator for DatadogPropagator {
182 fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
183 let span = cx.span();
184 let span_context = span.span_context();
185 if span_context.is_valid() {
186 let [t0, _] = u128_to_u64s(u128::from_be_bytes(span_context.trace_id().to_bytes()));
187 injector.set(DATADOG_TRACE_ID_HEADER, t0.to_string());
188 injector.set(
189 DATADOG_PARENT_ID_HEADER,
190 u64::from_be_bytes(span_context.span_id().to_bytes()).to_string(),
191 );
192
193 if span_context.trace_flags() & TRACE_FLAG_DEFERRED != TRACE_FLAG_DEFERRED {
194 let sampling_priority = if span_context.is_sampled() {
195 SamplingPriority::AutoKeep
196 } else {
197 SamplingPriority::AutoReject
198 };
199
200 injector.set(
201 DATADOG_SAMPLING_PRIORITY_HEADER,
202 (sampling_priority as i32).to_string(),
203 );
204 }
205 }
206 }
207
208 fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
209 let extracted = Self::extract_span_context(extractor)
210 .unwrap_or_else(|_| SpanContext::empty_context());
211
212 cx.with_remote_span_context(extracted)
213 }
214
215 fn fields(&self) -> FieldIter<'_> {
216 FieldIter::new(DATADOG_HEADER_FIELDS.as_ref())
217 }
218 }
219
220 #[cfg(test)]
221 mod tests {
222 use super::*;
223 use opentelemetry::testing::trace::TestSpan;
224 use opentelemetry::trace::TraceState;
225 use std::collections::HashMap;
226
227 #[rustfmt::skip]
228 fn extract_test_data() -> Vec<(Vec<(&'static str, &'static str)>, SpanContext)> {
229 vec![
230 (vec![], SpanContext::empty_context()),
231 (vec![(DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::empty_context()),
232 (vec![(DATADOG_TRACE_ID_HEADER, "garbage")], SpanContext::empty_context()),
233 (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "garbage")], SpanContext::new(TraceId::from_u128(1234), SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
234 (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TRACE_FLAG_DEFERRED, true, TraceState::default())),
235 (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::default(), true, TraceState::default())),
236 (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "1")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, TraceState::default())),
237 ]
238 }
239
240 #[rustfmt::skip]
241 fn inject_test_data() -> Vec<(Vec<(&'static str, &'static str)>, SpanContext)> {
242 vec![
243 (vec![], SpanContext::empty_context()),
244 (vec![], SpanContext::new(TraceId::INVALID, SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
245 (vec![], SpanContext::new(TraceId::from_hex("1234").unwrap(), SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
246 (vec![], SpanContext::new(TraceId::from_hex("1234").unwrap(), SpanId::INVALID, TraceFlags::SAMPLED, true, TraceState::default())),
247 (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TRACE_FLAG_DEFERRED, true, TraceState::default())),
248 (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::default(), true, TraceState::default())),
249 (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "1")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, TraceState::default())),
250 ]
251 }
252
253 #[test]
254 fn test_extract() {
255 for (header_list, expected) in extract_test_data() {
256 let map: HashMap<String, String> = header_list
257 .into_iter()
258 .map(|(k, v)| (k.to_string(), v.to_string()))
259 .collect();
260
261 let propagator = DatadogPropagator::default();
262 let context = propagator.extract(&map);
263 assert_eq!(context.span().span_context(), &expected);
264 }
265 }
266
267 #[test]
268 fn test_extract_empty() {
269 let map: HashMap<String, String> = HashMap::new();
270 let propagator = DatadogPropagator::default();
271 let context = propagator.extract(&map);
272 assert_eq!(context.span().span_context(), &SpanContext::empty_context());
273 }
274
275 #[test]
276 fn test_inject() {
277 let propagator = DatadogPropagator::default();
278 for (header_values, span_context) in inject_test_data() {
279 let mut injector: HashMap<String, String> = HashMap::new();
280 propagator.inject_context(
281 &Context::current_with_span(TestSpan(span_context)),
282 &mut injector,
283 );
284
285 if !header_values.is_empty() {
286 for (k, v) in header_values {
287 let injected_value: Option<&String> = injector.get(k);
288 assert_eq!(injected_value, Some(&v.to_string()));
289 injector.remove(k);
290 }
291 }
292 assert!(injector.is_empty());
293 }
294 }
295 }
296}
297
298pub use exporter::{
299 new_pipeline, DatadogExporter, DatadogPipelineBuilder, Error, SpanProcessExt,
300 WASMWorkerSpanProcessor,
301};
302pub use propagator::DatadogPropagator;