//! # Debug Tracing and Task Naming //! //! This example demonstrates how to add debug metadata and tracing information to tasks //! for better observability in complex DAG workflows. //! //! ## What You'll Learn //! - How to add task names and IDs for debugging //! - How to implement correlation IDs for distributed tracing //! - How to track task execution timing and lifecycle events //! - How to build traceable task wrappers //! //! ## Debugging Patterns //! //! **1. Named Tasks** - Add human-readable names to tasks //! ```rust //! struct TaskMetadata { //! id: String, //! name: String, //! start_time: Option, //! } //! ``` //! //! **2. Correlation IDs** - Track requests across task boundaries //! ```rust //! struct TraceContext { //! correlation_id: String, //! parent_task: Option, //! } //! ``` //! //! **3. Execution Metrics** - Measure task performance //! ```rust //! struct TaskTrace { //! name: String, //! duration_ms: u64, //! status: &'static str, //! } //! ``` //! //! ## Workflow Diagram //! ```text //! TRACE: order-12345 //! ┌──────────────────┐ //! │ ValidateOrder │ [order-12345] Start: 0ms //! │ ID: task-001 │──────► Status: Success //! └──────────────────┘ Duration: 10ms //! │ //! ├──────────────────┬──────────────────┐ //! ▼ ▼ ▼ //! ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ //! │ CheckInventory │ │ ValidatePayment │ │ CalculateShipping│ //! │ ID: task-002 │ │ ID: task-003 │ │ ID: task-004 │ //! └──────────────────┘ └──────────────────┘ └──────────────────┘ //! [order-12345] [order-12345] [order-12345] //! Duration: 45ms Duration: 120ms Duration: 15ms //! │ │ │ //! └──────────────────┴──────────────────┘ //! ▼ //! ┌──────────────────┐ //! │ FulfillOrder │ [order-12345] //! │ ID: task-005 │ Duration: 30ms //! └──────────────────┘ //! ``` //! //! ## Key Concepts //! - **Observability**: Make task execution visible for debugging //! - **Correlation IDs**: Track a request through multiple tasks //! - **Structured logging**: Consistent task metadata across logs //! - **Performance metrics**: Measure task execution time //! - **Task lifecycle**: Track start, completion, and errors //! //! ## Use Cases //! - Debugging complex workflows in production //! - Performance analysis and bottleneck identification //! - Distributed tracing in microservices //! - SLA monitoring and alerting //! - Audit logging for compliance //! //! ## Running This Example //! ```bash //! cargo run --example debug_tracing //! ``` //! //! ## Expected Output //! ```text //! === Debug Tracing Example === //! //! Starting order processing for order-12345 //! //! [order-12345] [task-001] ValidateOrder: Starting //! [order-12345] [task-001] ValidateOrder: Completed in 10ms - Success //! //! [order-12345] [task-002] CheckInventory: Starting //! [order-12345] [task-003] ValidatePayment: Starting //! [order-12345] [task-004] CalculateShipping: Starting //! [order-12345] [task-002] CheckInventory: Completed in 45ms - Success //! [order-12345] [task-004] CalculateShipping: Completed in 15ms - Success //! [order-12345] [task-003] ValidatePayment: Completed in 120ms - Success //! //! [order-12345] [task-005] FulfillOrder: Starting //! [order-12345] [task-005] FulfillOrder: Completed in 30ms - Success //! //! === Execution Trace Summary === //! Correlation ID: order-12345 //! Total Tasks: 5 //! Total Duration: 220ms //! Success: 5, Failed: 0 //! //! Task Breakdown: //! ValidateOrder: 10ms //! CheckInventory: 45ms //! ValidatePayment: 120ms (slowest) //! CalculateShipping: 15ms //! FulfillOrder: 30ms //! ``` use dagx::{task, DagRunner}; use std::time::Instant; use tokio::time::{sleep, Duration}; // Stateful task that tracks execution metadata for debugging struct TracedTask { task_id: String, task_name: String, correlation_id: String, work_duration_ms: u64, start_time: Option, } impl TracedTask { fn new( task_id: impl Into, task_name: impl Into, correlation_id: impl Into, work_duration_ms: u64, ) -> Self { Self { task_id: task_id.into(), task_name: task_name.into(), correlation_id: correlation_id.into(), work_duration_ms, start_time: None, } } } #[task] impl TracedTask { async fn run(&mut self) -> Result { // Record start let start = Instant::now(); self.start_time = Some(start); println!( "[{}] [{}] {}: Starting", self.correlation_id, self.task_id, self.task_name ); // Simulate work sleep(Duration::from_millis(self.work_duration_ms)).await; // Record completion let duration_ms = start.elapsed().as_millis() as u64; println!( "[{}] [{}] {}: Completed in {}ms - Success", self.correlation_id, self.task_id, self.task_name, duration_ms ); Ok(TaskTrace { task_id: self.task_id.clone(), task_name: self.task_name.clone(), correlation_id: self.correlation_id.clone(), duration_ms, status: "Success", }) } } // Task that can fail, demonstrating error tracing struct TracedTaskWithError { task_id: String, task_name: String, correlation_id: String, should_fail: bool, } impl TracedTaskWithError { fn new( task_id: impl Into, task_name: impl Into, correlation_id: impl Into, should_fail: bool, ) -> Self { Self { task_id: task_id.into(), task_name: task_name.into(), correlation_id: correlation_id.into(), should_fail, } } } #[task] impl TracedTaskWithError { async fn run(&mut self) -> Result { let start = Instant::now(); println!( "[{}] [{}] {}: Starting", self.correlation_id, self.task_id, self.task_name ); sleep(Duration::from_millis(50)).await; let duration_ms = start.elapsed().as_millis() as u64; if self.should_fail { println!( "[{}] [{}] {}: Failed in {}ms - Validation Error", self.correlation_id, self.task_id, self.task_name, duration_ms ); Err(TaskTrace { task_id: self.task_id.clone(), task_name: self.task_name.clone(), correlation_id: self.correlation_id.clone(), duration_ms, status: "Failed", }) } else { println!( "[{}] [{}] {}: Completed in {}ms - Success", self.correlation_id, self.task_id, self.task_name, duration_ms ); Ok(TaskTrace { task_id: self.task_id.clone(), task_name: self.task_name.clone(), correlation_id: self.correlation_id.clone(), duration_ms, status: "Success", }) } } } // Task trace data structure #[derive(Clone, Debug)] #[allow(dead_code)] struct TaskTrace { task_id: String, task_name: String, correlation_id: String, duration_ms: u64, status: &'static str, } // Task that aggregates traces from multiple upstream tasks struct TraceAggregator { correlation_id: String, } impl TraceAggregator { fn new(correlation_id: impl Into) -> Self { Self { correlation_id: correlation_id.into(), } } } #[task] impl TraceAggregator { async fn run( &mut self, t1: &Result, t2: &Result, t3: &Result, ) -> TraceSummary { let traces = vec![t1, t2, t3]; let mut summary = TraceSummary { correlation_id: self.correlation_id.clone(), task_count: traces.len(), total_duration_ms: 0, success_count: 0, failed_count: 0, task_details: Vec::new(), }; for trace_result in traces { match trace_result { Ok(trace) => { summary.success_count += 1; summary.total_duration_ms += trace.duration_ms; summary.task_details.push(trace.clone()); } Err(e) => { summary.failed_count += 1; eprintln!("[{}] Task failed: {}", self.correlation_id, e); } } } summary } } // Summary of traced execution #[derive(Clone, Debug)] struct TraceSummary { correlation_id: String, task_count: usize, total_duration_ms: u64, success_count: usize, failed_count: usize, task_details: Vec, } impl TraceSummary { fn print(&self) { println!("\n=== Execution Trace Summary ==="); println!("Correlation ID: {}", self.correlation_id); println!("Total Tasks: {}", self.task_count); println!("Total Duration: {}ms", self.total_duration_ms); println!( "Success: {}, Failed: {}", self.success_count, self.failed_count ); if !self.task_details.is_empty() { println!("\nTask Breakdown:"); let max_duration = self .task_details .iter() .map(|t| t.duration_ms) .max() .unwrap_or(0); for trace in &self.task_details { let slowest_tag = if trace.duration_ms == max_duration { " (slowest)" } else { "" }; println!( " {}: {}ms{}", trace.task_name, trace.duration_ms, slowest_tag ); } } } } #[tokio::main] async fn main() { println!("=== Debug Tracing Example ===\n"); // Example 1: Successful order processing with full tracing println!("Example 1: Successful order processing\n"); { let correlation_id = "order-12345"; println!("Starting order processing for {}\n", correlation_id); let dag = DagRunner::new(); // Root task: Validate order let _validate = dag.add_task(TracedTask::new( "task-001", "ValidateOrder", correlation_id, 10, )); // Parallel validation tasks (independent, no dependencies) let inventory = dag.add_task(TracedTask::new( "task-002", "CheckInventory", correlation_id, 45, )); let payment = dag.add_task(TracedTask::new( "task-003", "ValidatePayment", correlation_id, 120, )); let shipping = dag.add_task(TracedTask::new( "task-004", "CalculateShipping", correlation_id, 15, )); // Aggregate traces let summary = dag .add_task(TraceAggregator::new(correlation_id)) .depends_on((inventory, payment, shipping)); // Run the DAG dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }) .await .unwrap(); // Print summary dag.get(summary).unwrap().print(); } println!("\n"); // Example 2: Order with failed payment println!("Example 2: Order with failed payment\n"); { let correlation_id = "order-67890"; println!("Starting order processing for {}\n", correlation_id); let dag = DagRunner::new(); let _validate = dag.add_task(TracedTaskWithError::new( "task-101", "ValidateOrder", correlation_id, false, )); let _inventory = dag.add_task(TracedTaskWithError::new( "task-102", "CheckInventory", correlation_id, false, )); // This task will fail let payment = dag.add_task(TracedTaskWithError::new( "task-103", "ValidatePayment", correlation_id, true, // will fail )); dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }) .await .unwrap(); // Check results match dag.get(payment).unwrap().as_ref() { Ok(_) => println!("\nPayment validation succeeded"), Err(trace) => { println!("\n[{}] Payment validation failed:", correlation_id); println!(" Task ID: {}", trace.task_id); println!(" Duration: {}ms", trace.duration_ms); println!(" Status: {}", trace.status); } } } println!("\n=== Debug Tracing Example Complete ==="); }