diff --git a/crates/brightstaff/src/handlers/llm.rs b/crates/brightstaff/src/handlers/llm.rs index ba674c3e..71bb95ad 100644 --- a/crates/brightstaff/src/handlers/llm.rs +++ b/crates/brightstaff/src/handlers/llm.rs @@ -42,12 +42,9 @@ pub async fn llm_chat( .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| { - // No traceparent - this is a root span, generate a new trace ID use uuid::Uuid; let trace_id = Uuid::new_v4().to_string().replace("-", ""); - let span_id = Uuid::new_v4().to_string().replace("-", "")[..16].to_string(); - // Format: version-trace_id-parent_span_id-trace_flags - format!("00-{}-{}-01", trace_id, span_id) + format!("00-{}-0000000000000000-01", trace_id) }); let mut request_headers = request_headers; @@ -273,7 +270,6 @@ async fn build_llm_span( let mut span_builder = SpanBuilder::new(&operation_name) .with_trace_id(&trace_id) - .with_parent_span_id(&parent_span_id) .with_kind(SpanKind::Client) .with_start_time(start_time) .with_attribute(http::METHOD, "POST") @@ -283,6 +279,11 @@ async fn build_llm_span( .with_attribute(llm::MODEL_NAME, resolved_model.to_string()) .with_attribute(llm::IS_STREAMING, is_streaming.to_string()); + // Only set parent span ID if it exists (not a root span) + if let Some(parent) = parent_span_id { + span_builder = span_builder.with_parent_span_id(&parent); + } + // Add optional attributes if let Some(temp) = temperature { span_builder = span_builder.with_attribute(llm::TEMPERATURE, temp.to_string()); diff --git a/crates/brightstaff/src/handlers/router_chat.rs b/crates/brightstaff/src/handlers/router_chat.rs index 03c660de..09b09975 100644 --- a/crates/brightstaff/src/handlers/router_chat.rs +++ b/crates/brightstaff/src/handlers/router_chat.rs @@ -219,7 +219,6 @@ async fn record_routing_span( // Build the routing span directly using constants let mut span_builder = SpanBuilder::new(&routing_operation_name) .with_trace_id(&trace_id) - .with_parent_span_id(&parent_span_id) .with_kind(SpanKind::Client) .with_start_time(start_system_time) .with_end_time(std::time::SystemTime::now()) @@ -227,6 +226,11 @@ async fn record_routing_span( .with_attribute(http::TARGET, routing_api_path.to_string()) .with_attribute(routing::ROUTE_DETERMINATION_MS, start_time.elapsed().as_millis().to_string()); + // Only set parent span ID if it exists (not a root span) + if let Some(parent) = parent_span_id { + span_builder = span_builder.with_parent_span_id(&parent); + } + // Add all custom attributes for (key, value) in attrs { span_builder = span_builder.with_attribute(key, value); diff --git a/crates/common/src/traces/collector.rs b/crates/common/src/traces/collector.rs index c2188339..0fc407b2 100644 --- a/crates/common/src/traces/collector.rs +++ b/crates/common/src/traces/collector.rs @@ -9,15 +9,26 @@ use tracing::{debug, error, warn}; /// Parse W3C traceparent header into trace_id and parent_span_id /// Format: "00-{trace_id}-{parent_span_id}-01" /// -/// Returns (trace_id, parent_span_id) as strings -pub fn parse_traceparent(traceparent: &str) -> (String, String) { +/// Returns (trace_id, Option) +/// - parent_span_id is None if it's all zeros (0000000000000000), indicating a root span +pub fn parse_traceparent(traceparent: &str) -> (String, Option) { let parts: Vec<&str> = traceparent.split('-').collect(); if parts.len() == 4 { - (parts[1].to_string(), parts[2].to_string()) + let trace_id = parts[1].to_string(); + let parent_span_id = parts[2].to_string(); + + // If parent_span_id is all zeros, this is a root span with no parent + let parent = if parent_span_id == "0000000000000000" { + None + } else { + Some(parent_span_id) + }; + + (trace_id, parent) } else { warn!("Invalid traceparent format: {}", traceparent); - // Generate empty IDs if parsing fails - (String::new(), String::new()) + // Return empty trace ID and None for parent if parsing fails + (String::new(), None) } }