Skip to main content

feagi_observability/
cli.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! CLI argument parsing for per-crate debug flags
5//!
6//! Supports flags like `--debug-feagi-api`, `--debug-feagi-burst-engine`, etc.
7//! to enable/disable observability per crate.
8
9use std::collections::HashMap;
10use std::env;
11
12use crate::KNOWN_CRATES;
13
14/// Parse debug flags from command-line arguments
15///
16/// # Example
17/// ```rust
18/// let flags = CrateDebugFlags::from_args(std::env::args().collect());
19/// if flags.is_enabled("feagi-api") {
20///     // Enable debug logging for feagi-api crate
21/// }
22/// ```
23#[derive(Debug, Clone, Default)]
24pub struct CrateDebugFlags {
25    pub enabled_crates: HashMap<String, bool>,
26}
27
28impl CrateDebugFlags {
29    /// Parse debug flags from command-line arguments
30    ///
31    /// Looks for arguments matching `--debug-{crate-name}` pattern.
32    /// Also supports `--debug-all` to enable all crates.
33    pub fn from_args<I>(args: I) -> Self
34    where
35        I: IntoIterator<Item = String>,
36    {
37        let mut enabled_crates = HashMap::new();
38        let mut debug_all = false;
39
40        for arg in args {
41            if arg == "--debug-all" {
42                debug_all = true;
43                continue;
44            }
45
46            if let Some(crate_name) = arg.strip_prefix("--debug-") {
47                enabled_crates.insert(crate_name.to_string(), true);
48            }
49        }
50
51        if debug_all {
52            // Enable all known crates
53            for crate_name in KNOWN_CRATES {
54                enabled_crates.insert(crate_name.to_string(), true);
55            }
56        }
57
58        CrateDebugFlags { enabled_crates }
59    }
60
61    /// Check if debug is enabled for a specific crate
62    pub fn is_enabled(&self, crate_name: &str) -> bool {
63        self.enabled_crates.contains_key(crate_name)
64    }
65
66    /// Get all enabled crates
67    pub fn enabled_crates(&self) -> Vec<&String> {
68        self.enabled_crates.keys().collect()
69    }
70
71    /// Check if debug is enabled for any crate
72    pub fn any_enabled(&self) -> bool {
73        !self.enabled_crates.is_empty()
74    }
75
76    /// Get log level filter for a crate
77    ///
78    /// Returns `tracing::Level::DEBUG` if enabled, `tracing::Level::INFO` otherwise.
79    pub fn log_level(&self, crate_name: &str) -> tracing::Level {
80        if self.is_enabled(crate_name) {
81            tracing::Level::DEBUG
82        } else {
83            tracing::Level::INFO
84        }
85    }
86
87    /// Create a tracing filter from debug flags
88    ///
89    /// Returns a filter string that can be used with `EnvFilter`.
90    /// Format: "feagi-api=debug,feagi-burst-engine=debug" or "info" if none enabled.
91    pub fn to_filter_string(&self) -> String {
92        if self.enabled_crates.is_empty() {
93            return "info".to_string();
94        }
95
96        let mut filters = Vec::new();
97        for crate_name in self.enabled_crates.keys() {
98            filters.push(format!("{}=debug", crate_name));
99        }
100        // Set default level for other crates
101        filters.push("info".to_string());
102        filters.join(",")
103    }
104}
105
106/// Helper function to parse debug flags from environment
107///
108/// Checks both command-line arguments and `FEAGI_DEBUG` environment variable.
109/// Environment variable format: comma-separated crate names, e.g., "feagi-api,feagi-burst-engine"
110pub fn parse_debug_flags() -> CrateDebugFlags {
111    let mut flags = CrateDebugFlags::from_args(env::args());
112
113    // Also check environment variable
114    if let Ok(env_var) = env::var("FEAGI_DEBUG") {
115        if env_var == "all" {
116            // Enable all crates
117            for crate_name in KNOWN_CRATES {
118                flags.enabled_crates.insert(crate_name.to_string(), true);
119            }
120        } else {
121            // Parse comma-separated crate names
122            for crate_name in env_var.split(',') {
123                let crate_name = crate_name.trim();
124                if !crate_name.is_empty() {
125                    flags.enabled_crates.insert(crate_name.to_string(), true);
126                }
127            }
128        }
129    }
130
131    flags
132}
133
134/// Generate help text for debug flags
135pub fn debug_flags_help() -> String {
136    format!(
137        r#"Debug Flags:
138  --debug-all                    Enable debug logging for all crates
139  --debug-{{crate-name}}          Enable debug logging for specific crate
140
141Available crates:
142  {}
143
144Environment Variable:
145  FEAGI_DEBUG={{crate-name}}[,{{crate-name}}]  Enable debug for crates (comma-separated)
146  FEAGI_DEBUG=all                               Enable debug for all crates
147
148Examples:
149  --debug-feagi-api
150  --debug-feagi-api --debug-feagi-burst-engine
151  FEAGI_DEBUG=feagi-api,feagi-burst-engine
152"#,
153        KNOWN_CRATES.join(", ")
154    )
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_single_crate_flag() {
163        let flags = CrateDebugFlags::from_args(vec!["--debug-feagi-api".to_string()]);
164        assert!(flags.is_enabled("feagi-api"));
165        assert!(!flags.is_enabled("feagi-burst-engine"));
166    }
167
168    #[test]
169    fn test_multiple_crate_flags() {
170        let flags = CrateDebugFlags::from_args(vec![
171            "--debug-feagi-api".to_string(),
172            "--debug-feagi-burst-engine".to_string(),
173        ]);
174        assert!(flags.is_enabled("feagi-api"));
175        assert!(flags.is_enabled("feagi-burst-engine"));
176        assert!(!flags.is_enabled("feagi-bdu"));
177    }
178
179    #[test]
180    fn test_debug_all() {
181        let flags = CrateDebugFlags::from_args(vec!["--debug-all".to_string()]);
182        for crate_name in KNOWN_CRATES {
183            assert!(
184                flags.is_enabled(crate_name),
185                "{} should be enabled",
186                crate_name
187            );
188        }
189    }
190
191    #[test]
192    fn test_filter_string() {
193        let flags = CrateDebugFlags::from_args(vec!["--debug-feagi-api".to_string()]);
194        let filter = flags.to_filter_string();
195        assert!(filter.contains("feagi-api=debug"));
196    }
197
198    #[test]
199    fn test_log_level() {
200        let flags = CrateDebugFlags::from_args(vec!["--debug-feagi-api".to_string()]);
201        assert_eq!(flags.log_level("feagi-api"), tracing::Level::DEBUG);
202        assert_eq!(flags.log_level("feagi-burst-engine"), tracing::Level::INFO);
203    }
204}