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