Skip to main content

vtcode_config/
debug.rs

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