Skip to main content

Mountain/IPC/DevLog/
WriteToFile.rs

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