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}