use anyhow::Result;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use tracing::{info, warn};
use uuid::Uuid;
pub struct TracingSetup;
impl TracingSetup {
pub fn init() -> Result<()> {
warn!("Full OpenTelemetry integration not implemented - using structured logging");
info!("Tracing initialized with span context support");
Ok(())
}
pub fn shutdown() -> Result<()> {
info!("Tracing shutdown complete");
Ok(())
}
}
pub struct ExecutorTracer;
impl ExecutorTracer {
pub fn span_nats_pull(stream_name: &str, consumer_name: &str) -> ExecutorSpan {
let span_id = Uuid::new_v4();
let started_at = Instant::now();
tracing::info!(
operation = "nats_pull",
span_id = %span_id,
stream = stream_name,
consumer = consumer_name,
"Starting NATS pull operation"
);
ExecutorSpan::new("nats_pull", span_id, started_at)
}
pub fn span_intent_admission(intent_id: &str, capability: &str) -> ExecutorSpan {
let span_id = Uuid::new_v4();
let started_at = Instant::now();
tracing::info!(
operation = "intent_admission",
span_id = %span_id,
intent_id = intent_id,
capability = capability,
"Starting intent admission pipeline"
);
ExecutorSpan::new("intent_admission", span_id, started_at)
}
pub fn span_jail_setup(intent_id: &str, work_dir: &str) -> ExecutorSpan {
let span_id = Uuid::new_v4();
let started_at = Instant::now();
tracing::info!(
operation = "jail_setup",
span_id = %span_id,
intent_id = intent_id,
work_dir = work_dir,
"Starting jail setup"
);
ExecutorSpan::new("jail_setup", span_id, started_at)
}
pub fn span_intent_run(intent_id: &str, capability: &str, runner: &str) -> ExecutorSpan {
let span_id = Uuid::new_v4();
let started_at = Instant::now();
tracing::info!(
operation = "intent_run",
span_id = %span_id,
intent_id = intent_id,
capability = capability,
runner = runner,
"Starting intent execution"
);
ExecutorSpan::new("intent_run", span_id, started_at)
}
pub fn span_publish_result(intent_id: &str, result_status: &str) -> ExecutorSpan {
let span_id = Uuid::new_v4();
let started_at = Instant::now();
tracing::info!(
operation = "publish_result",
span_id = %span_id,
intent_id = intent_id,
result_status = result_status,
"Publishing intent result"
);
ExecutorSpan::new("publish_result", span_id, started_at)
}
}
pub struct ExecutorSpan {
operation: String,
span_id: Uuid,
started_at: Instant,
trace_id: Option<String>,
}
pub type AdmissionSpan = ExecutorSpan;
impl ExecutorSpan {
fn new(operation: &str, span_id: Uuid, started_at: Instant) -> Self {
Self {
operation: operation.to_string(),
span_id,
started_at,
trace_id: None,
}
}
pub fn with_trace_id(mut self, trace_id: String) -> Self {
self.trace_id = Some(trace_id);
self
}
pub fn record_event(&self, event: &str, attributes: &[(&str, &str)]) {
let span_id_str = self.span_id.to_string();
let mut fields = Vec::with_capacity(attributes.len() + 3);
fields.push(("operation", self.operation.as_str()));
fields.push(("span_id", &span_id_str));
fields.push(("event", event));
fields.extend_from_slice(attributes);
tracing::info!(
operation = self.operation.as_str(),
span_id = %self.span_id,
trace_id = self.trace_id.as_deref().unwrap_or(""),
event = event,
attributes = ?attributes,
"Span event recorded"
);
}
pub fn record_error(&self, error: &str) {
tracing::error!(
operation = self.operation.as_str(),
span_id = %self.span_id,
trace_id = self.trace_id.as_deref().unwrap_or(""),
error = error,
"Span error recorded"
);
}
pub fn finish_success(self) -> Duration {
let duration = self.started_at.elapsed();
tracing::info!(
operation = self.operation.as_str(),
span_id = %self.span_id,
trace_id = self.trace_id.as_deref().unwrap_or(""),
duration_ms = duration.as_millis(),
status = "success",
"Span completed successfully"
);
duration
}
pub fn finish_error(self, error: &str) -> Duration {
let duration = self.started_at.elapsed();
tracing::error!(
operation = self.operation.as_str(),
span_id = %self.span_id,
trace_id = self.trace_id.as_deref().unwrap_or(""),
duration_ms = duration.as_millis(),
status = "error",
error = error,
"Span completed with error"
);
duration
}
pub fn span_id(&self) -> Uuid {
self.span_id
}
pub fn duration(&self) -> Duration {
self.started_at.elapsed()
}
}
pub fn generate_trace_id() -> String {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let random = Uuid::new_v4().as_u128();
format!("{:016x}{:032x}", timestamp, random)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracing_setup() {
let result = TracingSetup::init();
assert!(result.is_ok());
let result = TracingSetup::shutdown();
assert!(result.is_ok());
}
#[test]
fn test_span_creation() {
let span = ExecutorTracer::span_nats_pull("test-stream", "test-consumer");
assert_eq!(span.operation, "nats_pull");
assert!(span.duration().as_millis() < 10); }
#[test]
fn test_span_events() {
let span = ExecutorTracer::span_intent_admission("test-intent", "fs.read");
span.record_event("policy_evaluated", &[("result", "allow")]);
span.record_error("validation failed");
let duration = span.finish_error("processing failed");
assert!(duration.as_millis() < 50); }
#[test]
fn test_trace_id_generation() {
let trace_id = generate_trace_id();
assert_eq!(trace_id.len(), 48);
let trace_id2 = generate_trace_id();
assert_ne!(trace_id, trace_id2);
}
#[test]
fn test_span_with_trace_id() {
let trace_id = generate_trace_id();
let span = ExecutorTracer::span_intent_run("test", "fs.read", "builtin")
.with_trace_id(trace_id.clone());
assert_eq!(span.trace_id, Some(trace_id));
let _ = span.finish_success();
}
#[test]
fn test_span_jail_setup() {
let span = ExecutorTracer::span_jail_setup("intent-123", "/tmp/work");
assert_eq!(span.operation, "jail_setup");
let span_id = span.span_id();
assert!(!span_id.is_nil());
let duration = span.finish_success();
assert!(duration.as_millis() < 50);
}
#[test]
fn test_span_publish_result() {
let span = ExecutorTracer::span_publish_result("intent-456", "ok");
assert_eq!(span.operation, "publish_result");
let duration = span.finish_error("publish failed");
assert!(duration.as_millis() < 50);
}
#[test]
fn test_span_finish_success() {
let span = ExecutorTracer::span_nats_pull("stream", "consumer");
std::thread::sleep(std::time::Duration::from_millis(1));
let duration = span.finish_success();
assert!(duration.as_millis() >= 1);
}
#[test]
fn test_span_duration() {
let span = ExecutorTracer::span_intent_admission("test", "http.fetch");
std::thread::sleep(std::time::Duration::from_millis(1));
let duration = span.duration();
assert!(duration.as_millis() >= 1);
let _ = span.finish_success();
}
}