Skip to main content

saluki_core/data_model/event/trace/
mod.rs

1//! Traces.
2
3use std::sync::Arc;
4
5use saluki_common::collections::FastHashMap;
6use stringtheory::MetaString;
7
8/// Typed value for attributes at every level of the trace model: span attributes,
9/// span event attributes, span link attributes, and trace-level attributes.
10///
11/// Covers all variants carried by the V1 APM `idx` wire format (`RawAnyValue`).
12#[derive(Clone, Debug, PartialEq)]
13pub enum AttributeValue {
14    /// String-valued attribute.
15    String(MetaString),
16    /// Boolean attribute.
17    Bool(bool),
18    /// Integer attribute.
19    Int(i64),
20    /// Floating-point attribute.
21    Float(f64),
22    /// Raw bytes attribute.
23    Bytes(Vec<u8>),
24    /// Array of attribute values (may be heterogeneous).
25    Array(Vec<AttributeValue>),
26    /// List of key-value pairs.
27    KeyValueList(Vec<(MetaString, AttributeValue)>),
28}
29
30impl AttributeValue {
31    /// Returns the inner string if this is a `String` variant.
32    pub fn as_string(&self) -> Option<&MetaString> {
33        if let AttributeValue::String(s) = self {
34            Some(s)
35        } else {
36            None
37        }
38    }
39
40    /// Returns the inner float if this is a `Float` variant.
41    ///
42    /// Returns `Some` only when the stored variant is `Float`. For numeric semantic tags
43    /// where the source may store an integer (for example, sampling priority, `_sample_rate`,
44    /// `_top_level`), use [`as_num`][AttributeValue::as_num] instead.
45    pub fn as_float(&self) -> Option<f64> {
46        if let AttributeValue::Float(f) = self {
47            Some(*f)
48        } else {
49            None
50        }
51    }
52
53    /// Returns the inner bool if this is a `Bool` variant.
54    pub fn as_bool(&self) -> Option<bool> {
55        if let AttributeValue::Bool(b) = self {
56            Some(*b)
57        } else {
58            None
59        }
60    }
61
62    /// Returns the inner integer if this is an `Int` variant.
63    pub fn as_int(&self) -> Option<i64> {
64        if let AttributeValue::Int(i) = self {
65            Some(*i)
66        } else {
67            None
68        }
69    }
70
71    /// Returns a numeric value as `f64` for either `Float` or `Int` variants.
72    ///
73    /// Use this when the caller only needs a number and doesn't care whether
74    /// the stored type is integral or floating-point (for example, sampling priority).
75    pub fn as_num(&self) -> Option<f64> {
76        match self {
77            AttributeValue::Float(f) => Some(*f),
78            AttributeValue::Int(i) => Some(*i as f64),
79            _ => None,
80        }
81    }
82
83    /// Returns the inner bytes if this is a `Bytes` variant.
84    pub fn as_bytes(&self) -> Option<&[u8]> {
85        if let AttributeValue::Bytes(b) = self {
86            Some(b)
87        } else {
88            None
89        }
90    }
91}
92
93/// Payload-level metadata promoted from the tracer payload or OTLP resource.
94///
95/// These fields are common to all chunks within a single tracer payload and describe
96/// the tracer and its environment rather than any individual trace or span.
97#[derive(Clone, Debug, PartialEq, Default)]
98pub struct PayloadFields {
99    /// Container ID associated with the tracer.
100    pub container_id: MetaString,
101    /// Tracer language name (for example, `"go"`, `"python"`).
102    pub language_name: MetaString,
103    /// Tracer language runtime version.
104    pub language_version: MetaString,
105    /// Tracer library version.
106    pub tracer_version: MetaString,
107    /// Tracer runtime ID.
108    pub runtime_id: MetaString,
109    /// Deployment environment (for example, `"production"`, `"staging"`).
110    pub env: MetaString,
111    /// Hostname of the tracer host.
112    pub hostname: MetaString,
113    /// Application version string.
114    pub app_version: MetaString,
115    /// Per-chunk weight from `Datadog-Client-Dropped-P0-Traces` header. Zero if absent.
116    pub client_dropped_p0s_weight: f64,
117}
118
119/// The protocol a trace chunk was ingested through.
120///
121/// Set once when the trace is constructed. Span attributes can be rewritten or removed downstream, so they cannot
122/// reliably identify the input protocol.
123#[derive(Clone, Copy, Debug, PartialEq)]
124pub enum TraceIngestInfo {
125    /// Received in a Datadog tracer payload.
126    Datadog,
127
128    /// Translated from OTLP.
129    Otlp {
130        /// The OTLP receiver's configured sampling rate, normalized to `(0, 1]`.
131        ///
132        /// This is the receiver's configuration, not an estimate of sampling performed upstream of it. The value is
133        /// not validated here; the producer normalizes it.
134        receiver_sampling_rate: f64,
135    },
136}
137
138/// A trace event.
139///
140/// A trace is a collection of spans that represent a distributed trace.
141#[derive(Clone, Debug, PartialEq)]
142pub struct Trace {
143    /// The spans that make up this trace.
144    spans: Vec<Span>,
145    /// Upper 8 bytes of the 128-bit trace ID (big-endian). Zero for 64-bit-only sources.
146    pub trace_id_high: u64,
147    /// Lower 8 bytes of the 128-bit trace ID (big-endian).
148    pub trace_id_low: u64,
149    /// Trace origin string (for example, `"lambda"`, `"rum"`).
150    pub origin: MetaString,
151    /// Payload-level metadata (promoted from the tracer payload or OTLP resource).
152    pub payload: PayloadFields,
153    /// Trace chunk-level or resource-level attributes.
154    pub attributes: Arc<FastHashMap<MetaString, AttributeValue>>,
155    /// Sampling priority set by the tracer or a sampler.
156    pub priority: Option<i32>,
157    /// Whether this trace was dropped during sampling.
158    pub dropped_trace: bool,
159    /// The mechanism by which the sampling decision was made.
160    pub sampling_mechanism: u32,
161    /// Identifier of the component that made the final sampling decision.
162    pub decision_maker: Option<MetaString>,
163    /// The protocol this trace was ingested through.
164    ingest_info: TraceIngestInfo,
165}
166
167impl Trace {
168    /// Creates a new `Trace` with the given spans, ingested through the protocol described by `ingest_info`.
169    ///
170    /// All other unified fields default to empty / zero. Callers should set them
171    /// directly after construction.
172    pub fn new(spans: Vec<Span>, ingest_info: TraceIngestInfo) -> Self {
173        Self {
174            spans,
175            trace_id_high: 0,
176            trace_id_low: 0,
177            origin: MetaString::empty(),
178            payload: PayloadFields::default(),
179            attributes: Arc::new(FastHashMap::default()),
180            priority: None,
181            dropped_trace: false,
182            sampling_mechanism: 0,
183            decision_maker: None,
184            ingest_info,
185        }
186    }
187
188    /// Returns the protocol this trace was ingested through.
189    ///
190    /// Fixed at construction, so later changes to spans or attributes cannot reclassify the trace.
191    pub fn ingest_info(&self) -> TraceIngestInfo {
192        self.ingest_info
193    }
194
195    /// Returns a reference to the spans in this trace.
196    pub fn spans(&self) -> &[Span] {
197        &self.spans
198    }
199
200    /// Returns a mutable reference to the spans in this trace.
201    pub fn spans_mut(&mut self) -> &mut [Span] {
202        &mut self.spans
203    }
204
205    /// Replaces the spans in this trace with the given spans.
206    pub fn set_spans(&mut self, spans: Vec<Span>) {
207        self.spans = spans;
208    }
209
210    /// Retains only the spans specified by the predicate.
211    ///
212    /// Returns the number of spans retained. If no spans match, the trace is left unchanged.
213    pub fn retain_spans<F>(&mut self, mut f: F) -> usize
214    where
215        F: FnMut(&Trace, &Span) -> bool,
216    {
217        if self.spans.is_empty() {
218            return 0;
219        }
220
221        let mut has_match = false;
222        for span in self.spans.iter() {
223            if f(self, span) {
224                has_match = true;
225                break;
226            }
227        }
228
229        if !has_match {
230            return 0;
231        }
232
233        let mut spans = std::mem::take(&mut self.spans);
234        spans.retain(|span| f(self, span));
235        spans.shrink_to_fit();
236        let _ = std::mem::replace(&mut self.spans, spans);
237
238        self.spans.len()
239    }
240
241    /// Remove spans only the spans specified by the predicate return true.
242    pub fn remove_spans<F>(&mut self, mut f: F)
243    where
244        F: FnMut(&Trace, &Span) -> bool,
245    {
246        if self.spans.is_empty() {
247            return;
248        }
249
250        let mut spans = std::mem::take(&mut self.spans);
251        spans.retain(|span| !f(self, span));
252        spans.shrink_to_fit();
253        let _ = std::mem::replace(&mut self.spans, spans);
254    }
255}
256
257/// A span event.
258#[derive(Clone, Debug, PartialEq, Default)]
259pub struct Span {
260    /// The name of the service associated with this span.
261    service: MetaString,
262    /// The operation name of this span.
263    name: MetaString,
264    /// The resource associated with this span.
265    resource: MetaString,
266    /// The unique identifier of this span.
267    span_id: u64,
268    /// The identifier of this span's parent, if any.
269    parent_id: u64,
270    /// The start timestamp of this span in nanoseconds since Unix epoch.
271    start: u64,
272    /// The duration of this span in nanoseconds.
273    duration: u64,
274    /// Error flag represented as 0 (no error) or 1 (error).
275    error: i32,
276    /// Span type classification (for example, web, db, lambda).
277    span_type: MetaString,
278    /// Links describing relationships to other spans.
279    span_links: Vec<SpanLink>,
280    /// Events associated with this span.
281    span_events: Vec<SpanEvent>,
282    /// Per-span environment override. Overrides `Trace.payload.env` when non-empty.
283    pub env: MetaString,
284    /// Per-span application version.
285    pub version: MetaString,
286    /// Instrumentation component name.
287    pub component: MetaString,
288    /// Span kind (OTel values): 0=unspecified, 1=internal, 2=server, 3=client, 4=producer, 5=consumer.
289    pub kind: u32,
290    /// Typed span-level attributes.
291    pub attributes: FastHashMap<MetaString, AttributeValue>,
292}
293
294impl Span {
295    /// Creates a new `Span` with all required fields.
296    #[allow(clippy::too_many_arguments)]
297    pub fn new(
298        service: impl Into<MetaString>, name: impl Into<MetaString>, resource: impl Into<MetaString>,
299        span_type: impl Into<MetaString>, span_id: u64, parent_id: u64, start: u64, duration: u64, error: i32,
300    ) -> Self {
301        Self {
302            service: service.into(),
303            name: name.into(),
304            resource: resource.into(),
305            span_type: span_type.into(),
306            span_id,
307            parent_id,
308            start,
309            duration,
310            error,
311            ..Self::default()
312        }
313    }
314
315    /// Sets the service name.
316    pub fn with_service(mut self, service: impl Into<MetaString>) -> Self {
317        self.service = service.into();
318        self
319    }
320
321    /// Sets the operation name.
322    pub fn with_name(mut self, name: impl Into<MetaString>) -> Self {
323        self.name = name.into();
324        self
325    }
326
327    /// Sets the resource name.
328    pub fn with_resource(mut self, resource: impl Into<MetaString>) -> Self {
329        self.resource = resource.into();
330        self
331    }
332
333    /// Sets the span identifier.
334    pub fn with_span_id(mut self, span_id: u64) -> Self {
335        self.span_id = span_id;
336        self
337    }
338
339    /// Sets the parent span identifier.
340    pub fn with_parent_id(mut self, parent_id: u64) -> Self {
341        self.parent_id = parent_id;
342        self
343    }
344
345    /// Sets the start timestamp.
346    pub fn with_start(mut self, start: u64) -> Self {
347        self.start = start;
348        self
349    }
350
351    /// Sets the span duration.
352    pub fn with_duration(mut self, duration: u64) -> Self {
353        self.duration = duration;
354        self
355    }
356
357    /// Sets the error flag.
358    pub fn with_error(mut self, error: i32) -> Self {
359        self.error = error;
360        self
361    }
362
363    /// Sets the span type (for example, web, db, lambda).
364    pub fn with_span_type(mut self, span_type: impl Into<MetaString>) -> Self {
365        self.span_type = span_type.into();
366        self
367    }
368
369    /// Replaces the span attributes map.
370    pub fn with_attributes(mut self, attributes: impl Into<Option<FastHashMap<MetaString, AttributeValue>>>) -> Self {
371        self.attributes = attributes.into().unwrap_or_default();
372        self
373    }
374
375    /// Replaces the span links collection.
376    pub fn with_span_links(mut self, span_links: impl Into<Option<Vec<SpanLink>>>) -> Self {
377        self.span_links = span_links.into().unwrap_or_default();
378        self
379    }
380
381    /// Replaces the span events collection.
382    pub fn with_span_events(mut self, span_events: impl Into<Option<Vec<SpanEvent>>>) -> Self {
383        self.span_events = span_events.into().unwrap_or_default();
384        self
385    }
386
387    /// Sets the per-span environment override.
388    pub fn with_env(mut self, env: impl Into<MetaString>) -> Self {
389        self.env = env.into();
390        self
391    }
392
393    /// Sets the per-span application version.
394    pub fn with_version(mut self, version: impl Into<MetaString>) -> Self {
395        self.version = version.into();
396        self
397    }
398
399    /// Sets the instrumentation component.
400    pub fn with_component(mut self, component: impl Into<MetaString>) -> Self {
401        self.component = component.into();
402        self
403    }
404
405    /// Sets the span kind.
406    pub fn with_kind(mut self, kind: u32) -> Self {
407        self.kind = kind;
408        self
409    }
410
411    /// Returns the service name.
412    pub fn service(&self) -> &str {
413        &self.service
414    }
415
416    /// Returns the operation name.
417    pub fn name(&self) -> &str {
418        &self.name
419    }
420
421    /// Returns the resource name.
422    pub fn resource(&self) -> &str {
423        &self.resource
424    }
425
426    /// Sets the resource name.
427    pub fn set_resource(&mut self, resource: impl Into<MetaString>) {
428        self.resource = resource.into();
429    }
430
431    /// Returns the span identifier.
432    pub fn span_id(&self) -> u64 {
433        self.span_id
434    }
435
436    /// Returns the parent span identifier.
437    pub fn parent_id(&self) -> u64 {
438        self.parent_id
439    }
440
441    /// Returns the start timestamp.
442    pub fn start(&self) -> u64 {
443        self.start
444    }
445
446    /// Returns the span duration.
447    pub fn duration(&self) -> u64 {
448        self.duration
449    }
450
451    /// Returns the error flag.
452    pub fn error(&self) -> i32 {
453        self.error
454    }
455
456    /// Returns the span type.
457    pub fn span_type(&self) -> &str {
458        &self.span_type
459    }
460
461    /// Returns the span links collection.
462    pub fn span_links(&self) -> &[SpanLink] {
463        &self.span_links
464    }
465
466    /// Returns the span events collection.
467    pub fn span_events(&self) -> &[SpanEvent] {
468        &self.span_events
469    }
470
471    /// Returns a mutable reference to the span events collection.
472    pub fn span_events_mut(&mut self) -> &mut Vec<SpanEvent> {
473        &mut self.span_events
474    }
475}
476
477/// A link between spans describing a causal relationship.
478#[derive(Clone, Debug, PartialEq, Default)]
479pub struct SpanLink {
480    /// Trace identifier for the linked span.
481    trace_id: u64,
482    /// High bits of the trace identifier when 128-bit IDs are used.
483    trace_id_high: u64,
484    /// Span identifier for the linked span.
485    span_id: u64,
486    /// Additional attributes attached to the link.
487    attributes: FastHashMap<MetaString, AttributeValue>,
488    /// W3C tracestate value.
489    tracestate: MetaString,
490    /// W3C trace flags where the high bit must be set when provided.
491    flags: u32,
492}
493
494impl SpanLink {
495    /// Creates a new span link for the provided identifiers.
496    pub fn new(trace_id: u64, span_id: u64) -> Self {
497        Self {
498            trace_id,
499            span_id,
500            ..Self::default()
501        }
502    }
503
504    /// Sets the trace identifier.
505    pub fn with_trace_id(mut self, trace_id: u64) -> Self {
506        self.trace_id = trace_id;
507        self
508    }
509
510    /// Sets the high bits of the trace identifier.
511    pub fn with_trace_id_high(mut self, trace_id_high: u64) -> Self {
512        self.trace_id_high = trace_id_high;
513        self
514    }
515
516    /// Sets the span identifier.
517    pub fn with_span_id(mut self, span_id: u64) -> Self {
518        self.span_id = span_id;
519        self
520    }
521
522    /// Replaces the attributes map.
523    pub fn with_attributes(mut self, attributes: impl Into<Option<FastHashMap<MetaString, AttributeValue>>>) -> Self {
524        self.attributes = attributes.into().unwrap_or_default();
525        self
526    }
527
528    /// Sets the W3C tracestate value.
529    pub fn with_tracestate(mut self, tracestate: impl Into<MetaString>) -> Self {
530        self.tracestate = tracestate.into();
531        self
532    }
533
534    /// Sets the W3C trace flags.
535    pub fn with_flags(mut self, flags: u32) -> Self {
536        self.flags = flags;
537        self
538    }
539
540    /// Returns the trace identifier.
541    pub fn trace_id(&self) -> u64 {
542        self.trace_id
543    }
544
545    /// Returns the high bits of the trace identifier.
546    pub fn trace_id_high(&self) -> u64 {
547        self.trace_id_high
548    }
549
550    /// Returns the span identifier.
551    pub fn span_id(&self) -> u64 {
552        self.span_id
553    }
554
555    /// Returns the attributes map.
556    pub fn attributes(&self) -> &FastHashMap<MetaString, AttributeValue> {
557        &self.attributes
558    }
559
560    /// Returns the W3C tracestate value.
561    pub fn tracestate(&self) -> &str {
562        &self.tracestate
563    }
564
565    /// Returns the W3C trace flags.
566    pub fn flags(&self) -> u32 {
567        self.flags
568    }
569}
570
571/// An event associated with a span.
572#[derive(Clone, Debug, PartialEq, Default)]
573pub struct SpanEvent {
574    /// Event timestamp in nanoseconds since Unix epoch.
575    time_unix_nano: u64,
576    /// Event name.
577    name: MetaString,
578    /// Arbitrary attributes describing the event.
579    attributes: FastHashMap<MetaString, AttributeValue>,
580}
581
582impl SpanEvent {
583    /// Creates a new span event with the given timestamp and name.
584    pub fn new(time_unix_nano: u64, name: impl Into<MetaString>) -> Self {
585        Self {
586            time_unix_nano,
587            name: name.into(),
588            ..Self::default()
589        }
590    }
591
592    /// Sets the event timestamp.
593    pub fn with_time_unix_nano(mut self, time_unix_nano: u64) -> Self {
594        self.time_unix_nano = time_unix_nano;
595        self
596    }
597
598    /// Sets the event name.
599    pub fn with_name(mut self, name: impl Into<MetaString>) -> Self {
600        self.name = name.into();
601        self
602    }
603
604    /// Replaces the attributes map.
605    pub fn with_attributes(mut self, attributes: impl Into<Option<FastHashMap<MetaString, AttributeValue>>>) -> Self {
606        self.attributes = attributes.into().unwrap_or_default();
607        self
608    }
609
610    /// Returns the event timestamp.
611    pub fn time_unix_nano(&self) -> u64 {
612        self.time_unix_nano
613    }
614
615    /// Returns the event name.
616    pub fn name(&self) -> &str {
617        &self.name
618    }
619
620    /// Returns the attributes map.
621    pub fn attributes(&self) -> &FastHashMap<MetaString, AttributeValue> {
622        &self.attributes
623    }
624
625    /// Returns a mutable reference to the attributes map.
626    pub fn attributes_mut(&mut self) -> &mut FastHashMap<MetaString, AttributeValue> {
627        &mut self.attributes
628    }
629}