vtcode_config/
debug.rs

1//! Debug and tracing configuration
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Trace level for structured logging
7#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
9#[serde(rename_all = "lowercase")]
10pub enum TraceLevel {
11    Error,
12    Warn,
13    Info,
14    Debug,
15    Trace,
16}
17
18impl TraceLevel {
19    pub fn as_str(self) -> &'static str {
20        match self {
21            Self::Error => "error",
22            Self::Warn => "warn",
23            Self::Info => "info",
24            Self::Debug => "debug",
25            Self::Trace => "trace",
26        }
27    }
28
29    pub fn parse(value: &str) -> Option<Self> {
30        match value.trim().to_lowercase().as_str() {
31            "error" => Some(Self::Error),
32            "warn" => Some(Self::Warn),
33            "info" => Some(Self::Info),
34            "debug" => Some(Self::Debug),
35            "trace" => Some(Self::Trace),
36            _ => None,
37        }
38    }
39}
40
41impl Default for TraceLevel {
42    fn default() -> Self {
43        Self::Info
44    }
45}
46
47impl std::fmt::Display for TraceLevel {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.write_str(self.as_str())
50    }
51}
52
53impl<'de> serde::Deserialize<'de> for TraceLevel {
54    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55    where
56        D: serde::Deserializer<'de>,
57    {
58        let raw = String::deserialize(deserializer)?;
59        if let Some(parsed) = Self::parse(&raw) {
60            Ok(parsed)
61        } else {
62            tracing::warn!(
63                input = raw,
64                "Invalid trace level; falling back to default (info)"
65            );
66            Ok(Self::default())
67        }
68    }
69}
70
71/// Debug and tracing configuration
72#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
73#[derive(Debug, Clone, Deserialize, Serialize)]
74pub struct DebugConfig {
75    /// Enable structured logging for development and troubleshooting
76    #[serde(default)]
77    pub enable_tracing: bool,
78
79    /// Trace level (error, warn, info, debug, trace)
80    #[serde(default)]
81    pub trace_level: TraceLevel,
82
83    /// List of tracing targets to enable
84    /// Examples: "vtcode_core::agent", "vtcode_core::tools", "vtcode::*"
85    #[serde(default)]
86    pub trace_targets: Vec<String>,
87
88    /// Directory for debug logs
89    #[serde(default)]
90    pub debug_log_dir: Option<String>,
91
92    /// Maximum size of debug logs before rotating (in MB)
93    #[serde(default = "default_max_debug_log_size_mb")]
94    pub max_debug_log_size_mb: u64,
95
96    /// Maximum age of debug logs to keep (in days)
97    #[serde(default = "default_max_debug_log_age_days")]
98    pub max_debug_log_age_days: u32,
99}
100
101fn default_max_debug_log_size_mb() -> u64 {
102    50
103}
104
105fn default_max_debug_log_age_days() -> u32 {
106    7
107}
108
109impl Default for DebugConfig {
110    fn default() -> Self {
111        Self {
112            enable_tracing: false,
113            trace_level: TraceLevel::Info,
114            trace_targets: Vec::new(),
115            debug_log_dir: None,
116            max_debug_log_size_mb: 50,
117            max_debug_log_age_days: 7,
118        }
119    }
120}
121
122impl DebugConfig {
123    /// Get the debug log directory, expanding ~ to home directory
124    pub fn debug_log_path(&self) -> PathBuf {
125        self.debug_log_dir
126            .as_ref()
127            .and_then(|dir| {
128                if dir.starts_with("~") {
129                    dirs::home_dir()
130                        .map(|home| home.join(dir.trim_start_matches('~').trim_start_matches('/')))
131                } else {
132                    Some(PathBuf::from(dir))
133                }
134            })
135            .unwrap_or_else(|| {
136                dirs::home_dir()
137                    .map(|home| home.join(".vtcode/debug"))
138                    .unwrap_or_else(|| PathBuf::from(".vtcode/debug"))
139            })
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_trace_level_parsing() {
149        assert_eq!(TraceLevel::parse("error"), Some(TraceLevel::Error));
150        assert_eq!(TraceLevel::parse("WARN"), Some(TraceLevel::Warn));
151        assert_eq!(TraceLevel::parse("info"), Some(TraceLevel::Info));
152        assert_eq!(TraceLevel::parse("DEBUG"), Some(TraceLevel::Debug));
153        assert_eq!(TraceLevel::parse("trace"), Some(TraceLevel::Trace));
154        assert_eq!(TraceLevel::parse("invalid"), None);
155    }
156
157    #[test]
158    fn test_debug_config_default() {
159        let cfg = DebugConfig::default();
160        assert!(!cfg.enable_tracing);
161        assert_eq!(cfg.trace_level, TraceLevel::Info);
162        assert!(cfg.trace_targets.is_empty());
163        assert_eq!(cfg.max_debug_log_size_mb, 50);
164        assert_eq!(cfg.max_debug_log_age_days, 7);
165    }
166}