use std::path::PathBuf;
use serde::{Deserialize, Serialize};
fn default_trace_dir() -> PathBuf {
PathBuf::from(".local/traces")
}
fn default_include_args() -> bool {
false
}
fn default_service_name() -> String {
"zeph-agent".into()
}
fn default_sample_rate() -> f64 {
1.0
}
fn default_system_metrics_interval_secs() -> u64 {
5
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TelemetryBackend {
#[default]
Local,
Otlp,
Pyroscope,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TelemetryConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub backend: TelemetryBackend,
#[serde(default = "default_trace_dir")]
pub trace_dir: PathBuf,
#[serde(default = "default_include_args")]
pub include_args: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub otlp_endpoint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub otlp_headers_vault_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pyroscope_endpoint: Option<String>,
#[serde(default = "default_service_name")]
pub service_name: String,
#[serde(default = "default_sample_rate")]
pub sample_rate: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub otel_filter: Option<String>,
#[serde(default = "default_system_metrics_interval_secs")]
pub system_metrics_interval_secs: u64,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
enabled: false,
backend: TelemetryBackend::default(),
trace_dir: default_trace_dir(),
include_args: default_include_args(),
otlp_endpoint: None,
otlp_headers_vault_key: None,
pyroscope_endpoint: None,
service_name: default_service_name(),
sample_rate: default_sample_rate(),
otel_filter: None,
system_metrics_interval_secs: default_system_metrics_interval_secs(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn telemetry_config_defaults() {
let cfg = TelemetryConfig::default();
assert!(!cfg.enabled);
assert_eq!(cfg.backend, TelemetryBackend::Local);
assert_eq!(cfg.trace_dir, PathBuf::from(".local/traces"));
assert!(!cfg.include_args);
assert!(cfg.otlp_endpoint.is_none());
assert_eq!(cfg.service_name, "zeph-agent");
assert!((cfg.sample_rate - 1.0).abs() < f64::EPSILON);
}
#[test]
fn telemetry_config_serde_roundtrip() {
let toml = r#"
enabled = true
backend = "otlp"
trace_dir = "/tmp/traces"
include_args = false
otlp_endpoint = "http://otel:4317"
service_name = "my-agent"
sample_rate = 0.5
"#;
let cfg: TelemetryConfig = toml::from_str(toml).unwrap();
assert!(cfg.enabled);
assert_eq!(cfg.backend, TelemetryBackend::Otlp);
assert_eq!(cfg.trace_dir, PathBuf::from("/tmp/traces"));
assert!(!cfg.include_args);
assert_eq!(cfg.otlp_endpoint.as_deref(), Some("http://otel:4317"));
assert_eq!(cfg.service_name, "my-agent");
let serialized = toml::to_string(&cfg).unwrap();
let cfg2: TelemetryConfig = toml::from_str(&serialized).unwrap();
assert_eq!(cfg2.backend, TelemetryBackend::Otlp);
assert_eq!(cfg2.service_name, "my-agent");
}
#[test]
fn telemetry_config_old_toml_without_section_uses_defaults() {
let cfg: TelemetryConfig = toml::from_str("").unwrap();
assert!(!cfg.enabled);
assert_eq!(cfg.backend, TelemetryBackend::Local);
}
}