agentforge_observability/
datadog.rs1use agentforge_core::Trace;
2use async_trait::async_trait;
3use reqwest::Client;
4use serde_json::json;
5
6use crate::{ExporterError, TraceExporter};
7
8pub struct DatadogExporter {
16 api_key: String,
17 traces_url: String,
18 service: String,
19 env: String,
20 client: Client,
21}
22
23impl DatadogExporter {
24 pub fn new(api_key: String, site: String, service: String, env: String) -> Self {
25 let traces_url = format!("https://trace.agent.{site}/api/v0.2/traces");
26 Self {
27 api_key,
28 traces_url,
29 service,
30 env,
31 client: Client::new(),
32 }
33 }
34
35 pub fn from_env() -> Self {
36 Self::new(
37 std::env::var("DD_API_KEY").unwrap_or_default(),
38 std::env::var("DD_SITE").unwrap_or_else(|_| "datadoghq.com".to_string()),
39 std::env::var("DD_SERVICE").unwrap_or_else(|_| "agentforge".to_string()),
40 std::env::var("DD_ENV").unwrap_or_else(|_| "production".to_string()),
41 )
42 }
43}
44
45#[async_trait]
46impl TraceExporter for DatadogExporter {
47 async fn export(&self, trace: &Trace) -> Result<(), ExporterError> {
48 if self.api_key.is_empty() {
49 return Err(ExporterError::Config("DD_API_KEY is not set".to_string()));
50 }
51
52 let trace_id = trace.id.as_u128() as u64;
54 let span_id = trace.run_id.as_u128() as u64;
55
56 let span = json!({
57 "trace_id": trace_id,
58 "span_id": span_id,
59 "name": "agentforge.eval",
60 "resource": format!("scenario/{}", trace.scenario_id),
61 "service": self.service,
62 "type": "custom",
63 "start": trace.created_at.timestamp_nanos_opt().unwrap_or(0),
64 "duration": (trace.latency_ms * 1_000_000) as i64, "meta": {
66 "env": self.env,
67 "run_id": trace.run_id.to_string(),
68 "scenario_id": trace.scenario_id.to_string(),
69 "status": trace.status.to_string(),
70 "failure_cluster": trace.failure_cluster.to_string(),
71 "aggregate_score": trace.aggregate_score.map(|s| s.to_string()).unwrap_or_default(),
72 },
73 "metrics": {
74 "llm_calls": trace.llm_calls,
75 "input_tokens": trace.input_tokens,
76 "output_tokens": trace.output_tokens,
77 "tool_invocations": trace.tool_invocations,
78 }
79 });
80
81 let payload = json!([[span]]);
83
84 self.client
85 .put(&self.traces_url)
86 .header("DD-API-KEY", &self.api_key)
87 .header("Content-Type", "application/json")
88 .json(&payload)
89 .send()
90 .await?
91 .error_for_status()
92 .map_err(ExporterError::Http)?;
93
94 tracing::debug!(trace_id = %trace.id, "Exported trace to Datadog");
95 Ok(())
96 }
97}