Skip to main content

par_term/tab/
session_logging.rs

1//! Tab session logging methods.
2//!
3//! Provides methods for toggling session logging on/off and querying its state.
4
5use crate::config::{Config, SessionLogFormat};
6use crate::session_logger::SessionLogger;
7use crate::tab::Tab;
8use std::sync::Arc;
9
10impl Tab {
11    /// Toggle session logging on/off.
12    ///
13    /// Returns `Ok(true)` if logging is now active, `Ok(false)` if stopped.
14    /// If logging wasn't active and no logger exists, creates a new one.
15    pub fn toggle_session_logging(&mut self, config: &Config) -> anyhow::Result<bool> {
16        let mut logger_guard = self.session_logger.lock();
17
18        if let Some(ref mut logger) = *logger_guard {
19            // Logger exists - toggle based on current state
20            if logger.is_active() {
21                logger.stop()?;
22                log::info!("Session logging stopped via hotkey");
23                Ok(false)
24            } else {
25                logger.start()?;
26                log::info!("Session logging started via hotkey");
27                Ok(true)
28            }
29        } else {
30            // No logger exists - create one and start it
31            let logs_dir = config.logs_dir();
32            if let Err(e) = std::fs::create_dir_all(&logs_dir) {
33                log::warn!("Failed to create logs directory: {}", e);
34                return Err(anyhow::anyhow!("Failed to create logs directory: {}", e));
35            }
36            // SEC-010: Enforce restrictive directory permissions so that session logs
37            // (which may capture terminal output including passwords) are not
38            // world-listable even if the user's umask is permissive.
39            #[cfg(unix)]
40            {
41                use std::os::unix::fs::PermissionsExt;
42                let _ = std::fs::set_permissions(&logs_dir, std::fs::Permissions::from_mode(0o700));
43            }
44
45            // Get terminal dimensions
46            let dimensions = if let Ok(term) = self.terminal.try_read() {
47                term.dimensions()
48            } else {
49                (80, 24) // fallback
50            };
51
52            let session_title = Some(format!(
53                "{} - {}",
54                self.title,
55                chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
56            ));
57
58            let mut logger = SessionLogger::new(
59                config.session_log.session_log_format,
60                &logs_dir,
61                dimensions,
62                session_title,
63            )?;
64
65            logger.set_redact_passwords(config.session_log.session_log_redact_passwords);
66
67            // v3 logs get the graphics-aware exporter: it ferries the recording
68            // to the core lib's export through the terminal at stop() time.
69            if config.session_log.session_log_format == SessionLogFormat::AsciicastV3 {
70                let tm = Arc::clone(&self.terminal);
71                logger.set_v3_exporter(Box::new(move |session| {
72                    tm.try_read()
73                        .ok()
74                        .map(|tm| tm.export_session_asciicast_v3(session))
75                }));
76            }
77
78            logger.start()?;
79
80            // SEC-002: Emit a prominent one-time warning when session logging is enabled.
81            // Session logs capture raw terminal I/O and may include passwords, API keys,
82            // tokens, and other credentials even with heuristic redaction active.
83            eprintln!(
84                "\n[par-term SESSION LOGGING ENABLED]\n\
85                 Session output is now being recorded to: {}\n\
86                 WARNING: Session logs may capture sensitive data (passwords, API keys, tokens)\n\
87                 despite heuristic redaction. Password-prompt detection is enabled: {}\n\
88                 Do NOT share session log files unless you have reviewed their contents.\n\
89                 Disable session logging when working with sensitive credentials.\n",
90                logs_dir.display(),
91                config.session_log.session_log_redact_passwords,
92            );
93
94            // Set up output callback to record PTY output
95            let logger_clone = Arc::clone(&self.session_logger);
96            if let Ok(term) = self.terminal.try_read() {
97                term.set_output_callback(move |data: &[u8]| {
98                    if let Some(ref mut logger) = *logger_clone.lock() {
99                        logger.record_output(data);
100                    }
101                });
102            }
103
104            *logger_guard = Some(logger);
105            log::info!("Session logging created and started via hotkey");
106            Ok(true)
107        }
108    }
109
110    /// Check if session logging is currently active.
111    pub fn is_session_logging_active(&self) -> bool {
112        if let Some(ref logger) = *self.session_logger.lock() {
113            logger.is_active()
114        } else {
115            false
116        }
117    }
118}