use crate::core::{ExecutionContext, NodeId};
use async_trait::async_trait;
use std::time::Duration;
use tracing;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[async_trait]
pub trait GraphObserver: Send + Sync {
async fn on_execution_start(&self, graph_id: &str, context: &ExecutionContext);
async fn on_execution_end(&self, graph_id: &str, success: bool, duration: Duration);
async fn on_node_start(&self, node_id: &NodeId, context: &ExecutionContext);
async fn on_node_end(&self, node_id: &NodeId, success: bool, duration: Duration);
async fn on_state_change(&self, key: &str, old_value: Option<&str>, new_value: &str);
}
pub struct LoggingObserver;
#[async_trait]
impl GraphObserver for LoggingObserver {
async fn on_execution_start(&self, graph_id: &str, _context: &ExecutionContext) {
#[cfg(feature = "observability")]
tracing::info!("Graph execution started: {}", graph_id);
#[cfg(not(feature = "observability"))]
{
let _ = (graph_id, _context);
tracing::debug!("Graph execution started: {}", graph_id);
}
}
async fn on_execution_end(&self, graph_id: &str, success: bool, duration: Duration) {
#[cfg(feature = "observability")]
tracing::info!(
"Graph execution ended: {} (success: {}, duration: {:?})",
graph_id,
success,
duration
);
#[cfg(not(feature = "observability"))]
tracing::debug!(
"Graph execution ended: {} (success: {}, duration: {:?})",
graph_id,
success,
duration
);
}
async fn on_node_start(&self, node_id: &NodeId, _context: &ExecutionContext) {
#[cfg(feature = "observability")]
tracing::debug!("Node execution started: {}", node_id.as_str());
#[cfg(not(feature = "observability"))]
{
let _ = _context;
tracing::debug!("Node execution started: {}", node_id.as_str());
}
}
async fn on_node_end(&self, node_id: &NodeId, success: bool, duration: Duration) {
#[cfg(feature = "observability")]
tracing::debug!(
"Node execution ended: {} (success: {}, duration: {:?})",
node_id.as_str(),
success,
duration
);
#[cfg(not(feature = "observability"))]
tracing::debug!(
"Node execution ended: {} (success: {}, duration: {:?})",
node_id.as_str(),
success,
duration
);
}
async fn on_state_change(&self, key: &str, old_value: Option<&str>, new_value: &str) {
#[cfg(feature = "observability")]
tracing::trace!(
"State change: {} = {} (was: {:?})",
key,
new_value,
old_value
);
#[cfg(not(feature = "observability"))]
tracing::debug!(
"State change: {} = {} (was: {:?})",
key,
new_value,
old_value
);
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ExecutionMetrics {
pub nodes_executed: usize,
pub total_duration: Duration,
pub node_metrics: Vec<NodeMetrics>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NodeMetrics {
pub node_id: String,
pub execution_count: usize,
pub total_duration: Duration,
pub average_duration: Duration,
pub success_rate: f32,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ObservabilityConfig {
pub enable_logging: bool,
pub enable_metrics: bool,
pub enable_tracing: bool,
pub log_level: String,
}