Skip to main content

celers_broker_sql/
tracing.rs

1//! W3C Trace Context for distributed tracing
2//!
3//! Compatible with OpenTelemetry, Jaeger, Zipkin, and other distributed tracing systems.
4//! See: <https://www.w3.org/TR/trace-context/>
5
6use celers_core::{CelersError, Result};
7use serde::{Deserialize, Serialize};
8
9/// W3C Trace Context for distributed tracing
10///
11/// Compatible with OpenTelemetry, Jaeger, Zipkin, and other distributed tracing systems.
12/// See: <https://www.w3.org/TR/trace-context/>
13///
14/// # Example
15/// ```
16/// use celers_broker_sql::{MysqlBroker, TraceContext};
17/// use celers_core::{Broker, SerializedTask};
18///
19/// # async fn example() -> celers_core::Result<()> {
20/// # let broker = MysqlBroker::new("mysql://localhost/test").await?;
21/// // Create a trace context
22/// let trace_ctx = TraceContext::new(
23///     "4bf92f3577b34da6a3ce929d0e0e4736",
24///     "00f067aa0ba902b7"
25/// );
26///
27/// // Enqueue task with trace context
28/// let task = SerializedTask::new("my_task".to_string(), vec![]);
29/// broker.enqueue_with_trace_context(task, trace_ctx).await?;
30///
31/// // Extract trace context when processing
32/// if let Some(msg) = broker.dequeue().await? {
33///     if let Some(ctx) = broker.extract_trace_context(&msg.task.metadata.id).await? {
34///         println!("Processing task with trace_id: {}", ctx.trace_id);
35///     }
36/// }
37/// # Ok(())
38/// # }
39/// ```
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41pub struct TraceContext {
42    /// W3C Trace ID (32 hex characters, 16 bytes)
43    pub trace_id: String,
44    /// W3C Span ID (16 hex characters, 8 bytes)
45    pub span_id: String,
46    /// Trace flags (8-bit field, typically "01" for sampled)
47    #[serde(default = "default_trace_flags")]
48    pub trace_flags: String,
49    /// Optional trace state for vendor-specific data
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub trace_state: Option<String>,
52}
53
54pub(crate) fn default_trace_flags() -> String {
55    "01".to_string()
56}
57
58impl TraceContext {
59    /// Create a new trace context with trace_id and span_id
60    ///
61    /// # Arguments
62    /// * `trace_id` - 32 hex character trace ID (W3C format)
63    /// * `span_id` - 16 hex character span ID (W3C format)
64    ///
65    /// # Example
66    /// ```
67    /// use celers_broker_sql::TraceContext;
68    ///
69    /// let ctx = TraceContext::new(
70    ///     "4bf92f3577b34da6a3ce929d0e0e4736",
71    ///     "00f067aa0ba902b7"
72    /// );
73    /// assert_eq!(ctx.trace_flags, "01"); // Sampled by default
74    /// ```
75    pub fn new(trace_id: impl Into<String>, span_id: impl Into<String>) -> Self {
76        Self {
77            trace_id: trace_id.into(),
78            span_id: span_id.into(),
79            trace_flags: default_trace_flags(),
80            trace_state: None,
81        }
82    }
83
84    /// Create trace context from W3C traceparent header value
85    ///
86    /// Format: "00-{trace_id}-{span_id}-{flags}"
87    ///
88    /// # Example
89    /// ```
90    /// use celers_broker_sql::TraceContext;
91    ///
92    /// let ctx = TraceContext::from_traceparent(
93    ///     "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
94    /// ).unwrap();
95    /// assert_eq!(ctx.trace_id, "4bf92f3577b34da6a3ce929d0e0e4736");
96    /// assert_eq!(ctx.span_id, "00f067aa0ba902b7");
97    /// ```
98    pub fn from_traceparent(traceparent: &str) -> Result<Self> {
99        let parts: Vec<&str> = traceparent.split('-').collect();
100        if parts.len() != 4 || parts[0] != "00" {
101            return Err(CelersError::Other(format!(
102                "Invalid traceparent format: {}",
103                traceparent
104            )));
105        }
106
107        Ok(Self {
108            trace_id: parts[1].to_string(),
109            span_id: parts[2].to_string(),
110            trace_flags: parts[3].to_string(),
111            trace_state: None,
112        })
113    }
114
115    /// Convert to W3C traceparent header value
116    ///
117    /// # Example
118    /// ```
119    /// use celers_broker_sql::TraceContext;
120    ///
121    /// let ctx = TraceContext::new(
122    ///     "4bf92f3577b34da6a3ce929d0e0e4736",
123    ///     "00f067aa0ba902b7"
124    /// );
125    /// assert_eq!(
126    ///     ctx.to_traceparent(),
127    ///     "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
128    /// );
129    /// ```
130    pub fn to_traceparent(&self) -> String {
131        format!("00-{}-{}-{}", self.trace_id, self.span_id, self.trace_flags)
132    }
133
134    /// Check if trace is sampled (should be recorded)
135    pub fn is_sampled(&self) -> bool {
136        self.trace_flags == "01"
137    }
138
139    /// Generate a new child span ID for this trace
140    ///
141    /// # Example
142    /// ```
143    /// use celers_broker_sql::TraceContext;
144    ///
145    /// let parent_ctx = TraceContext::new(
146    ///     "4bf92f3577b34da6a3ce929d0e0e4736",
147    ///     "00f067aa0ba902b7"
148    /// );
149    /// let child_ctx = parent_ctx.create_child_span();
150    ///
151    /// // Same trace ID, different span ID
152    /// assert_eq!(child_ctx.trace_id, parent_ctx.trace_id);
153    /// assert_ne!(child_ctx.span_id, parent_ctx.span_id);
154    /// ```
155    pub fn create_child_span(&self) -> Self {
156        let span_id = format!(
157            "{:016x}",
158            uuid::Uuid::new_v4().as_u128() & 0xFFFFFFFFFFFFFFFF
159        );
160        Self {
161            trace_id: self.trace_id.clone(),
162            span_id,
163            trace_flags: self.trace_flags.clone(),
164            trace_state: self.trace_state.clone(),
165        }
166    }
167}