1use std::fs::{self, File, OpenOptions};
17use std::io::Write;
18use std::path::PathBuf;
19use std::sync::{Mutex, Once};
20use std::time::SystemTime;
21
22use log::{LevelFilter, Log, Metadata, Record};
23
24static INIT: Once = Once::new();
25
26pub fn init() {
34 INIT.call_once(|| {
35 if let Some(path) = log_file_path() {
36 if let Some(parent) = path.parent() {
37 let _ = fs::create_dir_all(parent);
38 }
39
40 let file = OpenOptions::new().create(true).append(true).open(&path);
41
42 match file {
43 Ok(f) => {
44 let logger = FileLogger(Mutex::new(f));
45 let leaked = Box::leak(Box::new(logger));
48 let _ = log::set_logger(leaked);
49 log::set_max_level(LevelFilter::Debug);
50 log::info!("elemaudio-rs logger initialized: {}", path.display());
51 }
52 Err(e) => {
53 eprintln!(
54 "elemaudio-rs: failed to open log file {}: {e}",
55 path.display()
56 );
57 }
58 }
59 }
60 });
61}
62
63fn log_file_path() -> Option<PathBuf> {
64 #[cfg(target_os = "macos")]
65 {
66 dirs::home_dir().map(|h| h.join("Library/Logs/elemaudio-rs-plugin.log"))
67 }
68 #[cfg(target_os = "linux")]
69 {
70 dirs::data_dir()
71 .or_else(dirs::home_dir)
72 .map(|d| d.join("elemaudio-rs-plugin.log"))
73 }
74 #[cfg(target_os = "windows")]
75 {
76 dirs::data_dir().map(|d| d.join("elemaudio-rs-plugin.log"))
77 }
78 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
79 {
80 dirs::home_dir().map(|h| h.join("elemaudio-rs-plugin.log"))
81 }
82}
83
84struct FileLogger(Mutex<File>);
85
86impl Log for FileLogger {
87 fn enabled(&self, _metadata: &Metadata) -> bool {
88 true
89 }
90
91 fn log(&self, record: &Record) {
92 if !self.enabled(record.metadata()) {
93 return;
94 }
95 if let Ok(mut file) = self.0.lock() {
96 let timestamp = format_timestamp();
97 let _ = writeln!(
98 file,
99 "[{timestamp}] [{level}] {msg}",
100 level = record.level(),
101 msg = record.args(),
102 );
103 }
104 }
105
106 fn flush(&self) {
107 if let Ok(mut file) = self.0.lock() {
108 let _ = file.flush();
109 }
110 }
111}
112
113fn format_timestamp() -> String {
114 match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
115 Ok(d) => {
116 let secs = d.as_secs();
117 let millis = d.subsec_millis();
118 format!("{secs}.{millis:03}")
119 }
120 Err(_) => "0.000".to_string(),
121 }
122}