use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceContext {
pub trace_id: String,
pub parent_span_id: Option<String>,
pub trace_flags: u8,
pub trace_state: String,
}
impl TraceContext {
pub fn new(operation: &str) -> Self {
Self {
trace_id: Self::generate_trace_id(),
parent_span_id: None,
trace_flags: 1, trace_state: format!("operation={}", operation),
}
}
pub fn from_traceparent(traceparent: &str) -> Option<Self> {
let parts: Vec<&str> = traceparent.split('-').collect();
if parts.len() != 4 {
return None;
}
Some(Self {
trace_id: parts[1].to_string(),
parent_span_id: Some(parts[2].to_string()),
trace_flags: u8::from_str_radix(parts[3], 16).ok()?,
trace_state: String::new(),
})
}
pub fn to_traceparent(&self, span_id: &str) -> String {
format!("00-{}-{}-{:02x}", self.trace_id, span_id, self.trace_flags)
}
fn generate_trace_id() -> String {
format!("{:032x}", fastrand::u128(..))
}
pub fn generate_span_id() -> String {
format!("{:016x}", fastrand::u64(..))
}
pub fn is_sampled(&self) -> bool {
self.trace_flags & 0x01 != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SpanKind {
Internal,
Server,
Client,
Producer,
Consumer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SpanStatus {
Unset,
Ok,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpanEvent {
pub name: String,
pub timestamp: SystemTime,
pub attributes: HashMap<String, AttributeValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AttributeValue {
String(String),
Int(i64),
Float(f64),
Bool(bool),
StringArray(Vec<String>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Span {
pub span_id: String,
pub trace_context: TraceContext,
pub name: String,
pub kind: SpanKind,
pub parent_span_id: Option<String>,
pub start_time: SystemTime,
pub end_time: Option<SystemTime>,
pub status: SpanStatus,
pub attributes: HashMap<String, AttributeValue>,
pub events: Vec<SpanEvent>,
pub duration_ms: Option<u64>,
}
impl Span {
pub fn new(name: String, trace_context: TraceContext, kind: SpanKind) -> Self {
Self {
span_id: TraceContext::generate_span_id(),
trace_context,
name,
kind,
parent_span_id: None,
start_time: SystemTime::now(),
end_time: None,
status: SpanStatus::Unset,
attributes: HashMap::new(),
events: Vec::new(),
duration_ms: None,
}
}
pub fn with_parent(mut self, parent_span_id: String) -> Self {
self.parent_span_id = Some(parent_span_id);
self
}
pub fn set_attribute(&mut self, key: String, value: AttributeValue) {
self.attributes.insert(key, value);
}
pub fn record_event(&mut self, name: String) {
self.events.push(SpanEvent {
name,
timestamp: SystemTime::now(),
attributes: HashMap::new(),
});
}
pub fn record_event_with_attributes(
&mut self,
name: String,
attributes: HashMap<String, AttributeValue>,
) {
self.events.push(SpanEvent {
name,
timestamp: SystemTime::now(),
attributes,
});
}
pub fn set_status(&mut self, status: SpanStatus) {
self.status = status;
}
pub fn finish(&mut self) {
self.end_time = Some(SystemTime::now());
if let Ok(duration) = self
.end_time
.expect("end_time should be set before calling finish")
.duration_since(self.start_time)
{
self.duration_ms = Some(duration.as_millis() as u64);
}
}
pub fn is_finished(&self) -> bool {
self.end_time.is_some()
}
}
pub struct SpanBuilder {
name: String,
trace_context: TraceContext,
kind: SpanKind,
parent_span_id: Option<String>,
attributes: HashMap<String, AttributeValue>,
}
impl SpanBuilder {
pub fn new(name: String, trace_context: TraceContext) -> Self {
Self {
name,
trace_context,
kind: SpanKind::Internal,
parent_span_id: None,
attributes: HashMap::new(),
}
}
pub fn with_kind(mut self, kind: SpanKind) -> Self {
self.kind = kind;
self
}
pub fn with_parent(mut self, parent_span_id: String) -> Self {
self.parent_span_id = Some(parent_span_id);
self
}
pub fn with_attribute(mut self, key: &str, value: AttributeValue) -> Self {
self.attributes.insert(key.to_string(), value);
self
}
pub fn build(self) -> Span {
let mut span = Span::new(self.name, self.trace_context, self.kind);
if let Some(parent) = self.parent_span_id {
span = span.with_parent(parent);
}
for (key, value) in self.attributes {
span.set_attribute(key, value);
}
span
}
}
pub struct TraceCorrelator {
active_spans: Arc<RwLock<HashMap<String, Span>>>,
completed_spans: Arc<RwLock<Vec<Span>>>,
max_completed_spans: usize,
}
impl TraceCorrelator {
pub fn new() -> Self {
Self {
active_spans: Arc::new(RwLock::new(HashMap::new())),
completed_spans: Arc::new(RwLock::new(Vec::new())),
max_completed_spans: 10000,
}
}
pub fn with_max_completed_spans(mut self, max: usize) -> Self {
self.max_completed_spans = max;
self
}
pub async fn start_span(&self, span: Span) -> String {
let span_id = span.span_id.clone();
let mut spans = self.active_spans.write().await;
spans.insert(span_id.clone(), span);
span_id
}
pub async fn get_span(&self, span_id: &str) -> Option<Span> {
let spans = self.active_spans.read().await;
spans.get(span_id).cloned()
}
pub async fn update_span<F>(&self, span_id: &str, update_fn: F)
where
F: FnOnce(&mut Span),
{
let mut spans = self.active_spans.write().await;
if let Some(span) = spans.get_mut(span_id) {
update_fn(span);
}
}
pub async fn finish_span(&self, span_id: &str) {
let mut active = self.active_spans.write().await;
if let Some(mut span) = active.remove(span_id) {
span.finish();
let mut completed = self.completed_spans.write().await;
completed.push(span);
if completed.len() > self.max_completed_spans {
let excess = completed.len() - self.max_completed_spans;
completed.drain(0..excess);
}
}
}
pub async fn get_trace_spans(&self, trace_id: &str) -> Vec<Span> {
let completed = self.completed_spans.read().await;
completed
.iter()
.filter(|span| span.trace_context.trace_id == trace_id)
.cloned()
.collect()
}
pub async fn get_span_hierarchy(&self, trace_id: &str) -> HashMap<String, Vec<String>> {
let spans = self.get_trace_spans(trace_id).await;
let mut hierarchy: HashMap<String, Vec<String>> = HashMap::new();
for span in &spans {
if let Some(parent_id) = &span.parent_span_id {
hierarchy
.entry(parent_id.clone())
.or_default()
.push(span.span_id.clone());
}
}
hierarchy
}
pub async fn clear_completed_spans(&self) {
let mut completed = self.completed_spans.write().await;
completed.clear();
}
pub async fn get_statistics(&self) -> CorrelationStatistics {
let active = self.active_spans.read().await;
let completed = self.completed_spans.read().await;
CorrelationStatistics {
active_spans: active.len(),
completed_spans: completed.len(),
total_traces: completed
.iter()
.map(|s| s.trace_context.trace_id.as_str())
.collect::<std::collections::HashSet<_>>()
.len(),
}
}
}
impl Default for TraceCorrelator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorrelationStatistics {
pub active_spans: usize,
pub completed_spans: usize,
pub total_traces: usize,
}
#[derive(Debug, Clone)]
pub struct SparqlTraceContext {
pub trace_context: TraceContext,
pub field_path: Vec<String>,
pub query: String,
pub query_type: String,
}
impl SparqlTraceContext {
pub fn new(
trace_context: TraceContext,
field_path: Vec<String>,
query: String,
query_type: String,
) -> Self {
Self {
trace_context,
field_path,
query,
query_type,
}
}
pub fn create_sparql_span(&self) -> Span {
let mut span = Span::new(
"sparql-execution".to_string(),
self.trace_context.clone(),
SpanKind::Internal,
);
span.set_attribute(
"graphql.field_path".to_string(),
AttributeValue::String(self.field_path.join(".")),
);
span.set_attribute(
"sparql.query".to_string(),
AttributeValue::String(self.query.clone()),
);
span.set_attribute(
"sparql.query_type".to_string(),
AttributeValue::String(self.query_type.clone()),
);
span
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trace_context_creation() {
let ctx = TraceContext::new("test-operation");
assert!(!ctx.trace_id.is_empty());
assert!(ctx.parent_span_id.is_none());
assert_eq!(ctx.trace_flags, 1);
assert!(ctx.trace_state.contains("operation=test-operation"));
}
#[test]
fn test_trace_context_from_traceparent() {
let traceparent = "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01";
let ctx = TraceContext::from_traceparent(traceparent).expect("should succeed");
assert_eq!(ctx.trace_id, "0af7651916cd43dd8448eb211c80319c");
assert_eq!(
ctx.parent_span_id.as_ref().expect("should succeed"),
"00f067aa0ba902b7"
);
assert_eq!(ctx.trace_flags, 1);
assert!(ctx.is_sampled());
}
#[test]
fn test_trace_context_to_traceparent() {
let ctx = TraceContext {
trace_id: "0af7651916cd43dd8448eb211c80319c".to_string(),
parent_span_id: None,
trace_flags: 1,
trace_state: String::new(),
};
let span_id = "00f067aa0ba902b7";
let traceparent = ctx.to_traceparent(span_id);
assert_eq!(
traceparent,
"00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
);
}
#[test]
fn test_span_creation() {
let ctx = TraceContext::new("test");
let span = Span::new("test-span".to_string(), ctx, SpanKind::Server);
assert_eq!(span.name, "test-span");
assert_eq!(span.kind, SpanKind::Server);
assert_eq!(span.status, SpanStatus::Unset);
assert!(!span.is_finished());
}
#[test]
fn test_span_with_parent() {
let ctx = TraceContext::new("test");
let parent_id = "parent123".to_string();
let span = Span::new("child-span".to_string(), ctx, SpanKind::Internal)
.with_parent(parent_id.clone());
assert_eq!(span.parent_span_id, Some(parent_id));
}
#[test]
fn test_span_attributes() {
let ctx = TraceContext::new("test");
let mut span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
span.set_attribute(
"key1".to_string(),
AttributeValue::String("value1".to_string()),
);
span.set_attribute("key2".to_string(), AttributeValue::Int(42));
span.set_attribute("key3".to_string(), AttributeValue::Bool(true));
assert_eq!(span.attributes.len(), 3);
match span.attributes.get("key2").expect("should succeed") {
AttributeValue::Int(v) => assert_eq!(*v, 42),
_ => panic!("Wrong attribute type"),
}
}
#[test]
fn test_span_events() {
let ctx = TraceContext::new("test");
let mut span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
span.record_event("event1".to_string());
span.record_event("event2".to_string());
assert_eq!(span.events.len(), 2);
assert_eq!(span.events[0].name, "event1");
assert_eq!(span.events[1].name, "event2");
}
#[test]
fn test_span_finish() {
let ctx = TraceContext::new("test");
let mut span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
assert!(!span.is_finished());
assert!(span.duration_ms.is_none());
span.finish();
assert!(span.is_finished());
assert!(span.duration_ms.is_some());
}
#[test]
fn test_span_builder() {
let ctx = TraceContext::new("test");
let span = SpanBuilder::new("test-span".to_string(), ctx)
.with_kind(SpanKind::Client)
.with_parent("parent123".to_string())
.with_attribute("key", AttributeValue::String("value".to_string()))
.build();
assert_eq!(span.kind, SpanKind::Client);
assert_eq!(span.parent_span_id, Some("parent123".to_string()));
assert_eq!(span.attributes.len(), 1);
}
#[tokio::test]
async fn test_trace_correlator_start_span() {
let correlator = TraceCorrelator::new();
let ctx = TraceContext::new("test");
let span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
let span_id = correlator.start_span(span).await;
let retrieved = correlator.get_span(&span_id).await;
assert!(retrieved.is_some());
assert_eq!(retrieved.expect("should succeed").name, "test-span");
}
#[tokio::test]
async fn test_trace_correlator_update_span() {
let correlator = TraceCorrelator::new();
let ctx = TraceContext::new("test");
let span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
let span_id = correlator.start_span(span).await;
correlator
.update_span(&span_id, |s| {
s.set_attribute("new_attr".to_string(), AttributeValue::Int(123));
})
.await;
let updated = correlator.get_span(&span_id).await.expect("should succeed");
assert_eq!(updated.attributes.len(), 1);
}
#[tokio::test]
async fn test_trace_correlator_finish_span() {
let correlator = TraceCorrelator::new();
let ctx = TraceContext::new("test");
let span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
let span_id = correlator.start_span(span).await;
correlator.finish_span(&span_id).await;
let stats = correlator.get_statistics().await;
assert_eq!(stats.active_spans, 0);
assert_eq!(stats.completed_spans, 1);
}
#[tokio::test]
async fn test_trace_correlator_get_trace_spans() {
let correlator = TraceCorrelator::new();
let ctx = TraceContext::new("test");
let trace_id = ctx.trace_id.clone();
let span1 = Span::new("span1".to_string(), ctx.clone(), SpanKind::Internal);
let span2 = Span::new("span2".to_string(), ctx, SpanKind::Internal);
correlator.start_span(span1).await;
correlator.start_span(span2).await;
let active_spans = correlator.active_spans.read().await;
let span_ids: Vec<_> = active_spans.keys().cloned().collect();
drop(active_spans);
for span_id in span_ids {
correlator.finish_span(&span_id).await;
}
let trace_spans = correlator.get_trace_spans(&trace_id).await;
assert_eq!(trace_spans.len(), 2);
}
#[tokio::test]
async fn test_trace_correlator_hierarchy() {
let correlator = TraceCorrelator::new();
let ctx = TraceContext::new("test");
let trace_id = ctx.trace_id.clone();
let parent_span = Span::new("parent".to_string(), ctx.clone(), SpanKind::Server);
let parent_id = parent_span.span_id.clone();
let child_span =
Span::new("child".to_string(), ctx, SpanKind::Internal).with_parent(parent_id.clone());
correlator.start_span(parent_span).await;
correlator.start_span(child_span).await;
let active_spans = correlator.active_spans.read().await;
let span_ids: Vec<_> = active_spans.keys().cloned().collect();
drop(active_spans);
for span_id in span_ids {
correlator.finish_span(&span_id).await;
}
let hierarchy = correlator.get_span_hierarchy(&trace_id).await;
assert!(hierarchy.contains_key(&parent_id));
}
#[tokio::test]
async fn test_trace_correlator_clear() {
let correlator = TraceCorrelator::new();
let ctx = TraceContext::new("test");
let span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
let span_id = correlator.start_span(span).await;
correlator.finish_span(&span_id).await;
let stats_before = correlator.get_statistics().await;
assert_eq!(stats_before.completed_spans, 1);
correlator.clear_completed_spans().await;
let stats_after = correlator.get_statistics().await;
assert_eq!(stats_after.completed_spans, 0);
}
#[tokio::test]
async fn test_trace_correlator_max_completed_spans() {
let correlator = TraceCorrelator::new().with_max_completed_spans(5);
for i in 0..10 {
let ctx = TraceContext::new("test");
let span = Span::new(format!("span-{}", i), ctx, SpanKind::Internal);
let span_id = correlator.start_span(span).await;
correlator.finish_span(&span_id).await;
}
let stats = correlator.get_statistics().await;
assert_eq!(stats.completed_spans, 5);
}
#[test]
fn test_sparql_trace_context() {
let ctx = TraceContext::new("graphql-query");
let sparql_ctx = SparqlTraceContext::new(
ctx,
vec!["user".to_string(), "posts".to_string()],
"SELECT * WHERE { ?s ?p ?o }".to_string(),
"SELECT".to_string(),
);
let span = sparql_ctx.create_sparql_span();
assert_eq!(span.name, "sparql-execution");
assert!(span.attributes.contains_key("graphql.field_path"));
assert!(span.attributes.contains_key("sparql.query"));
}
#[test]
fn test_span_status() {
let ctx = TraceContext::new("test");
let mut span = Span::new("test-span".to_string(), ctx, SpanKind::Internal);
assert_eq!(span.status, SpanStatus::Unset);
span.set_status(SpanStatus::Ok);
assert_eq!(span.status, SpanStatus::Ok);
span.set_status(SpanStatus::Error);
assert_eq!(span.status, SpanStatus::Error);
}
#[test]
fn test_attribute_value_types() {
let string_attr = AttributeValue::String("test".to_string());
let int_attr = AttributeValue::Int(42);
let float_attr = AttributeValue::Float(1.5);
let bool_attr = AttributeValue::Bool(true);
let array_attr = AttributeValue::StringArray(vec!["a".to_string(), "b".to_string()]);
match string_attr {
AttributeValue::String(s) => assert_eq!(s, "test"),
_ => panic!("Wrong type"),
}
match int_attr {
AttributeValue::Int(i) => assert_eq!(i, 42),
_ => panic!("Wrong type"),
}
match float_attr {
AttributeValue::Float(f) => assert!((f - 1.5).abs() < 0.001),
_ => panic!("Wrong type"),
}
match bool_attr {
AttributeValue::Bool(b) => assert!(b),
_ => panic!("Wrong type"),
}
match array_attr {
AttributeValue::StringArray(arr) => assert_eq!(arr.len(), 2),
_ => panic!("Wrong type"),
}
}
}