debugger/common/
logging.rs

1//! Logging and tracing configuration
2//!
3//! Provides structured logging for both CLI and daemon modes.
4//! The daemon logs to a file since it runs in the background.
5
6use std::path::PathBuf;
7use tracing_subscriber::{
8    fmt::{self, format::FmtSpan},
9    layer::SubscriberExt,
10    util::SubscriberInitExt,
11    EnvFilter,
12};
13
14use super::paths;
15
16/// Initialize tracing for the CLI (stdout logging)
17///
18/// Logs are controlled by the `RUST_LOG` environment variable.
19/// Default level is INFO for this crate, WARN for dependencies.
20pub fn init_cli() {
21    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
22        EnvFilter::new("debugger=info,warn")
23    });
24
25    tracing_subscriber::registry()
26        .with(filter)
27        .with(
28            fmt::layer()
29                .with_target(true)
30                .with_thread_ids(false)
31                .with_file(false)
32                .with_line_number(false)
33                .compact(),
34        )
35        .init();
36}
37
38/// Initialize tracing for the daemon (file + stderr logging)
39///
40/// The daemon logs to both:
41/// 1. A log file at `~/.local/share/debugger-cli/logs/daemon.log`
42/// 2. stderr (inherited from spawning process for early errors)
43///
44/// Log level controlled by `RUST_LOG`, default is TRACE for daemon to capture DAP messages.
45pub fn init_daemon() -> Option<PathBuf> {
46    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
47        // Default to trace for daemon - we want to see DAP messages
48        EnvFilter::new("debugger=trace,info")
49    });
50
51    // Try to set up file logging
52    let log_path = if let Some(log_dir) = paths::log_dir() {
53        // Ensure log directory exists
54        if std::fs::create_dir_all(&log_dir).is_ok() {
55            let log_file = log_dir.join("daemon.log");
56
57            // Create or append to log file
58            match std::fs::OpenOptions::new()
59                .create(true)
60                .append(true)
61                .open(&log_file)
62            {
63                Ok(file) => {
64                    // File logging with full details
65                    let file_layer = fmt::layer()
66                        .with_writer(file)
67                        .with_ansi(false)
68                        .with_target(true)
69                        .with_thread_ids(true)
70                        .with_file(true)
71                        .with_line_number(true)
72                        .with_span_events(FmtSpan::ENTER | FmtSpan::EXIT);
73
74                    // Also log to stderr for early startup issues
75                    let stderr_layer = fmt::layer()
76                        .with_writer(std::io::stderr)
77                        .with_target(true)
78                        .with_thread_ids(false)
79                        .with_file(false)
80                        .compact();
81
82                    tracing_subscriber::registry()
83                        .with(filter)
84                        .with(file_layer)
85                        .with(stderr_layer)
86                        .init();
87
88                    return Some(log_file);
89                }
90                Err(e) => {
91                    eprintln!("Warning: Could not open log file: {}", e);
92                }
93            }
94        }
95        None
96    } else {
97        None
98    };
99
100    // Fallback: stderr only
101    tracing_subscriber::registry()
102        .with(filter)
103        .with(
104            fmt::layer()
105                .with_writer(std::io::stderr)
106                .with_target(true)
107                .with_thread_ids(true)
108                .with_file(true)
109                .with_line_number(true),
110        )
111        .init();
112
113    log_path
114}
115
116/// Get the path to the daemon log file
117pub fn daemon_log_path() -> Option<PathBuf> {
118    paths::log_dir().map(|d| d.join("daemon.log"))
119}
120
121/// Truncate the daemon log file (useful before debugging sessions)
122pub fn truncate_daemon_log() -> std::io::Result<()> {
123    if let Some(path) = daemon_log_path() {
124        if path.exists() {
125            std::fs::write(&path, "")?;
126        }
127    }
128    Ok(())
129}