Skip to main content

agentzero_core/common/
mod.rs

1pub mod local_providers;
2pub mod paths;
3pub mod privacy_helpers;
4pub mod url_policy;
5pub mod util;
6
7use std::collections::HashMap;
8use tracing_subscriber::layer::SubscriberExt;
9use tracing_subscriber::util::SubscriberInitExt;
10use tracing_subscriber::EnvFilter;
11
12/// Log output format.
13#[derive(Debug, Clone, Default, PartialEq)]
14pub enum LogFormat {
15    /// Human-readable text output (default).
16    #[default]
17    Text,
18    /// Structured JSON — one JSON object per line.
19    Json,
20}
21
22/// Options for initialising the tracing subscriber.
23#[derive(Debug, Clone, Default)]
24pub struct TracingOptions {
25    /// Output format (text or json).
26    pub format: LogFormat,
27    /// Base log level (overridden by `RUST_LOG` env var).
28    pub level: Option<String>,
29    /// Per-module log level overrides.
30    pub modules: HashMap<String, String>,
31}
32
33/// Initialise tracing from CLI verbosity flag (backward-compatible).
34///
35/// Also checks `AGENTZERO__LOGGING__FORMAT` env var (values: "text", "json")
36/// and `AGENTZERO__LOGGING__LEVEL` env var so container deployments can set
37/// format via environment without a config file.
38pub fn init_tracing(verbosity: u8) {
39    let format = match std::env::var("AGENTZERO__LOGGING__FORMAT")
40        .unwrap_or_default()
41        .to_lowercase()
42        .as_str()
43    {
44        "json" => LogFormat::Json,
45        _ => LogFormat::Text,
46    };
47
48    let level = std::env::var("AGENTZERO__LOGGING__LEVEL")
49        .ok()
50        .unwrap_or_else(|| verbosity_to_level(verbosity).to_string());
51
52    init_tracing_with_options(&TracingOptions {
53        format,
54        level: Some(level),
55        ..Default::default()
56    });
57}
58
59/// Initialise tracing with full options (format, level, per-module overrides).
60pub fn init_tracing_with_options(opts: &TracingOptions) {
61    let base_level = opts.level.as_deref().unwrap_or("error");
62    let filter = build_env_filter(base_level, &opts.modules);
63
64    let registry = tracing_subscriber::registry().with(filter);
65
66    match opts.format {
67        LogFormat::Json => {
68            registry
69                .with(tracing_subscriber::fmt::layer().json().with_target(true))
70                .try_init()
71                .ok();
72        }
73        LogFormat::Text => {
74            registry
75                .with(tracing_subscriber::fmt::layer().with_target(false))
76                .try_init()
77                .ok();
78        }
79    }
80}
81
82fn build_env_filter(base_level: &str, modules: &HashMap<String, String>) -> EnvFilter {
83    // RUST_LOG takes precedence when set.
84    if let Ok(rust_log) = std::env::var("RUST_LOG") {
85        return EnvFilter::new(rust_log);
86    }
87
88    let mut directives = base_level.to_string();
89    for (module, level) in modules {
90        directives.push_str(&format!(",{module}={level}"));
91    }
92    EnvFilter::new(directives)
93}
94
95fn verbosity_to_level(verbosity: u8) -> &'static str {
96    match verbosity {
97        0 | 1 => "error",
98        2 => "info",
99        3 => "debug",
100        _ => "trace",
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn verbosity_level_one_maps_to_error() {
110        assert_eq!(verbosity_to_level(1), "error");
111    }
112
113    #[test]
114    fn verbosity_level_two_maps_to_info() {
115        assert_eq!(verbosity_to_level(2), "info");
116    }
117
118    #[test]
119    fn verbosity_level_three_maps_to_debug() {
120        assert_eq!(verbosity_to_level(3), "debug");
121    }
122
123    #[test]
124    fn verbosity_level_four_or_more_maps_to_trace() {
125        assert_eq!(verbosity_to_level(4), "trace");
126        assert_eq!(verbosity_to_level(8), "trace");
127    }
128
129    #[test]
130    fn log_format_default_is_text() {
131        assert_eq!(LogFormat::default(), LogFormat::Text);
132    }
133
134    #[test]
135    fn tracing_options_default_uses_text_format() {
136        let opts = TracingOptions::default();
137        assert_eq!(opts.format, LogFormat::Text);
138        assert!(opts.level.is_none());
139        assert!(opts.modules.is_empty());
140    }
141
142    #[test]
143    fn build_env_filter_with_module_overrides() {
144        let mut modules = HashMap::new();
145        modules.insert("agentzero_gateway".to_string(), "debug".to_string());
146        // Should not panic — just verifies the directive string is valid.
147        let _ = build_env_filter("info", &modules);
148    }
149}