use crate::graph::NodeMeta;
use async_trait::async_trait;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SpanStatus {
Success,
Error,
Cancelled,
Retrying,
}
#[derive(Debug, Clone)]
pub struct SpanContext<'a> {
pub thread_id: &'a str,
pub node_name: &'a str,
pub checkpoint_id: Option<&'a str>,
pub attempt: u8,
pub duration_ms: u64,
pub cost_eur: Option<f64>,
pub status: SpanStatus,
pub dag_id: Option<&'a str>,
pub error: Option<&'a str>,
pub meta: &'a NodeMeta,
}
#[async_trait]
pub trait SpanEmitter: Send + Sync {
async fn emit(&self, ctx: &SpanContext<'_>);
}
pub struct NoopEmitter;
#[async_trait]
impl SpanEmitter for NoopEmitter {
async fn emit(&self, _ctx: &SpanContext<'_>) {}
}
pub struct TracingEmitter;
#[async_trait]
impl SpanEmitter for TracingEmitter {
async fn emit(&self, ctx: &SpanContext<'_>) {
match ctx.status {
SpanStatus::Success => {
tracing::info!(
thread_id = ctx.thread_id,
node = ctx.node_name,
duration_ms = ctx.duration_ms,
cost_eur = ?ctx.cost_eur,
tokens_in = ?ctx.meta.tokens_in,
tokens_out = ?ctx.meta.tokens_out,
model = ?ctx.meta.model,
attempt = ctx.attempt,
"Node completed successfully"
);
}
SpanStatus::Retrying => {
tracing::warn!(
thread_id = ctx.thread_id,
node = ctx.node_name,
attempt = ctx.attempt,
error = ?ctx.error,
"Node retrying"
);
}
SpanStatus::Error => {
tracing::error!(
thread_id = ctx.thread_id,
node = ctx.node_name,
duration_ms = ctx.duration_ms,
attempt = ctx.attempt,
error = ?ctx.error,
"Node failed"
);
}
SpanStatus::Cancelled => {
tracing::warn!(
thread_id = ctx.thread_id,
node = ctx.node_name,
duration_ms = ctx.duration_ms,
"Node cancelled"
);
}
}
}
}