Skip to main content

cc_toolgate/
logging.rs

1//! Decision logging to `~/.local/share/cc-toolgate/decisions.log`.
2//!
3//! Initializes a file logger on first call and writes one line per evaluated
4//! command with the decision, truncated command text, and reason.
5
6use crate::eval::RuleMatch;
7use log::info;
8use simplelog::{Config, LevelFilter, WriteLogger};
9use std::sync::Once;
10
11/// Ensures the logger is initialized exactly once per process.
12static INIT: Once = Once::new();
13
14/// Initialize the file logger. Best-effort: failures are silently ignored.
15pub fn init() {
16    INIT.call_once(|| {
17        let Some(home) = std::env::var_os("HOME") else {
18            return;
19        };
20        let log_dir = std::path::Path::new(&home).join(".local/share/cc-toolgate");
21        let _ = std::fs::create_dir_all(&log_dir);
22
23        let log_path = log_dir.join("decisions.log");
24        let Ok(file) = std::fs::OpenOptions::new()
25            .create(true)
26            .append(true)
27            .open(log_path)
28        else {
29            return;
30        };
31
32        let _ = WriteLogger::init(LevelFilter::Info, Config::default(), file);
33    });
34}
35
36/// Log a decision record.
37/// Format: `{decision}\t{command_truncated}\t{reason_oneline}`
38/// Timestamp is provided by simplelog.
39pub fn log_decision(command: &str, result: &RuleMatch) {
40    let reason_oneline = result.reason.replace('\n', "; ");
41    let cmd_truncated: String = command.chars().take(200).collect();
42
43    info!(
44        "{decision}\t{cmd}\t{reason}",
45        decision = result.decision.as_str(),
46        cmd = cmd_truncated,
47        reason = reason_oneline,
48    );
49}