Skip to main content

Mountain/IPC/DevLog/
WriteToFile.rs

1
2//! Append a single formatted line to the session's
3//! `Mountain.dev.log`. The file sink is lazy: opens on first
4//! call, no-ops if `Record=0` or the directory cannot be
5//! created. Flushes per line so `tail -f` shows live output.
6
7use std::{
8	fs::{File, OpenOptions, create_dir_all},
9	io::{BufWriter, Write as IoWrite},
10	path::PathBuf,
11	sync::{Mutex, OnceLock},
12};
13
14use crate::IPC::DevLog::{AppDataPrefix, IsEnabled, IsShort, SessionTimestamp};
15
16static LOG_FILE:OnceLock<Mutex<Option<BufWriter<File>>>> = OnceLock::new();
17
18pub fn Fn(Line:&str) {
19	let Sink = InitFileSink();
20
21	if let Ok(mut Guard) = Sink.lock() {
22		if let Some(Writer) = Guard.as_mut() {
23			let _ = Writer.write_all(Line.as_bytes());
24
25			if !Line.ends_with('\n') {
26				let _ = Writer.write_all(b"\n");
27			}
28
29			let _ = Writer.flush();
30		}
31	}
32}
33
34pub(super) fn InitFileSink() -> &'static Mutex<Option<BufWriter<File>>> {
35	LOG_FILE.get_or_init(|| {
36		if !FileSinkEnabled() {
37			return Mutex::new(None);
38		}
39		let Dir = ResolveLogDirectory();
40		if create_dir_all(&Dir).is_err() {
41			eprintln!("[DEV:LOG] Failed to create log directory {}", Dir.display());
42			return Mutex::new(None);
43		}
44		let Path = Dir.join("Mountain.dev.log");
45		match OpenOptions::new().create(true).append(true).open(&Path) {
46			Ok(File) => {
47				let mut Writer = BufWriter::with_capacity(64 * 1024, File);
48				let Header = format!(
49					"# Land dev log - started {}, pid {}, short={}, ipc-enabled={}\n",
50					SessionTimestamp::Fn(),
51					std::process::id(),
52					IsShort::Fn(),
53					IsEnabled::Fn("ipc"),
54				);
55				let _ = Writer.write_all(Header.as_bytes());
56				let _ = Writer.flush();
57				eprintln!("[DEV:LOG] File sink → {}", Path.display());
58				Mutex::new(Some(Writer))
59			},
60			Err(Error) => {
61				eprintln!("[DEV:LOG] Failed to open {}: {}", Path.display(), Error);
62				Mutex::new(None)
63			},
64		}
65	})
66}
67
68fn FileSinkEnabled() -> bool {
69	static ENABLED:OnceLock<bool> = OnceLock::new();
70
71	*ENABLED.get_or_init(|| {
72		match std::env::var("Record") {
73			Ok(Value) => matches!(Value.as_str(), "1" | "true" | "yes" | "on"),
74			Err(_) => cfg!(debug_assertions) && std::env::var("Trace").is_ok(),
75		}
76	})
77}
78
79fn ResolveLogDirectory() -> PathBuf {
80	let Stamp = SessionTimestamp::Fn();
81
82	let Base = match AppDataPrefix::Fn() {
83		Some(Prefix) => PathBuf::from(Prefix).join("logs"),
84
85		None => std::env::temp_dir().join("land-editor-logs"),
86	};
87
88	Base.join(Stamp)
89}