1use ghidra_ipc::owner_only::restrict_to_owner;
6use std::io;
7use std::path::Path;
8use tracing_appender::non_blocking::WorkerGuard;
9
10pub fn init_file_logging(log_path: &Path) -> io::Result<WorkerGuard> {
13 let dir = log_path
14 .parent()
15 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "log path has no parent"))?;
16 std::fs::create_dir_all(dir)?;
17 restrict_to_owner(dir)?;
18
19 let file_name = log_path
20 .file_name()
21 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "log path has no file name"))?;
22 let appender = tracing_appender::rolling::Builder::new()
23 .rotation(tracing_appender::rolling::Rotation::DAILY)
24 .filename_prefix(file_name.to_string_lossy().as_ref())
25 .max_log_files(5)
26 .build(dir)
27 .map_err(|e| io::Error::other(format!("log appender: {e}")))?;
28 let (nb, guard) = tracing_appender::non_blocking(appender);
29
30 tracing_subscriber::fmt()
32 .with_writer(nb)
33 .with_ansi(false)
34 .init();
35 let _ = restrict_to_owner(dir);
38 Ok(guard)
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn init_creates_owner_only_log_dir_and_never_panics() {
47 let base = std::env::temp_dir().join(format!("ghidra-mcp-log-{}", std::process::id()));
48 let log = base.join("logs").join("worker-test.log");
49 let guard = init_file_logging(&log).expect("logging inits");
50 tracing::info!("hello file log");
51 drop(guard);
52 assert!(log.parent().unwrap().exists());
53 #[cfg(unix)]
54 {
55 use std::os::unix::fs::PermissionsExt;
56 let mode = std::fs::metadata(base.join("logs"))
57 .unwrap()
58 .permissions()
59 .mode();
60 assert_eq!(mode & 0o777, 0o700, "log dir must be owner-only");
61 }
62 std::fs::remove_dir_all(&base).ok();
63 }
64}