156 lines
4.6 KiB
Rust
156 lines
4.6 KiB
Rust
use std::fs::{File, OpenOptions};
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::time::SystemTime;
|
|
|
|
const LOG_NAME: &str = "meatshell-debug.log";
|
|
|
|
pub fn init() -> Option<PathBuf> {
|
|
let (file, path) = match open_log_file() {
|
|
Some(pair) => pair,
|
|
None => {
|
|
install_panic_hook(None);
|
|
init_stderr_tracing();
|
|
tracing::warn!("failed to create {LOG_NAME}; logging to stderr only");
|
|
return None;
|
|
}
|
|
};
|
|
|
|
install_panic_hook(Some(path.clone()));
|
|
|
|
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info,meatshell=debug,winit=error"));
|
|
let log_file = file;
|
|
if tracing_subscriber::fmt()
|
|
.with_env_filter(filter)
|
|
.with_ansi(false)
|
|
.with_thread_ids(true)
|
|
.with_writer(move || log_file.try_clone().expect("clone debug log file"))
|
|
.try_init()
|
|
.is_err()
|
|
{
|
|
return Some(path);
|
|
}
|
|
|
|
tracing::info!(
|
|
version = env!("CARGO_PKG_VERSION"),
|
|
os = std::env::consts::OS,
|
|
arch = std::env::consts::ARCH,
|
|
"debug logging initialized"
|
|
);
|
|
|
|
if log_paths_enabled() {
|
|
tracing::debug!(log = %path.display(), "debug log path");
|
|
if let Ok(cwd) = std::env::current_dir() {
|
|
tracing::debug!(cwd = %cwd.display(), "current working directory");
|
|
}
|
|
if let Ok(exe) = std::env::current_exe() {
|
|
tracing::debug!(exe = %exe.display(), "current executable");
|
|
}
|
|
}
|
|
|
|
Some(path)
|
|
}
|
|
|
|
fn init_stderr_tracing() {
|
|
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info,meatshell=debug,winit=error"));
|
|
let _ = tracing_subscriber::fmt()
|
|
.with_env_filter(filter)
|
|
.with_ansi(false)
|
|
.with_thread_ids(true)
|
|
.try_init();
|
|
}
|
|
|
|
fn open_log_file() -> Option<(File, PathBuf)> {
|
|
let mut dirs = Vec::new();
|
|
if let Ok(exe) = std::env::current_exe() {
|
|
if let Some(dir) = exe.parent() {
|
|
dirs.push(dir.to_path_buf());
|
|
}
|
|
}
|
|
if let Ok(cwd) = std::env::current_dir() {
|
|
if !dirs.iter().any(|dir| dir == &cwd) {
|
|
dirs.push(cwd);
|
|
}
|
|
}
|
|
|
|
for dir in dirs {
|
|
let path = dir.join(LOG_NAME);
|
|
if let Ok(file) = OpenOptions::new().create(true).append(true).open(&path) {
|
|
return Some((file, path));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn install_panic_hook(path: Option<PathBuf>) {
|
|
let previous = std::panic::take_hook();
|
|
std::panic::set_hook(Box::new(move |info| {
|
|
let payload = if let Some(message) = info.payload().downcast_ref::<&str>() {
|
|
*message
|
|
} else if let Some(message) = info.payload().downcast_ref::<String>() {
|
|
message.as_str()
|
|
} else {
|
|
"<non-string panic payload>"
|
|
};
|
|
let location = info
|
|
.location()
|
|
.map(|loc| format!("{}:{}:{}", short_file(loc.file()), loc.line(), loc.column()))
|
|
.unwrap_or_else(|| "<unknown>".to_string());
|
|
let backtrace = if env_flag("MEATSHELL_DEBUG_BACKTRACE") {
|
|
Some(std::backtrace::Backtrace::force_capture().to_string())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(path) = &path {
|
|
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
|
|
let _ = writeln!(
|
|
file,
|
|
"\n===== panic {:?} =====\nlocation: {}\nmessage: {}",
|
|
SystemTime::now(),
|
|
location,
|
|
payload
|
|
);
|
|
if let Some(backtrace) = &backtrace {
|
|
let _ = writeln!(file, "backtrace:\n{}", backtrace);
|
|
}
|
|
let _ = writeln!(file);
|
|
}
|
|
}
|
|
|
|
if let Some(backtrace) = &backtrace {
|
|
tracing::error!(
|
|
location = %location,
|
|
message = %payload,
|
|
backtrace = %backtrace,
|
|
"panic captured"
|
|
);
|
|
} else {
|
|
tracing::error!(
|
|
location = %location,
|
|
message = %payload,
|
|
"panic captured"
|
|
);
|
|
}
|
|
previous(info);
|
|
}));
|
|
}
|
|
|
|
pub(crate) fn log_paths_enabled() -> bool {
|
|
env_flag("MEATSHELL_DEBUG_PATHS")
|
|
}
|
|
|
|
fn env_flag(name: &str) -> bool {
|
|
matches!(
|
|
std::env::var(name).as_deref(),
|
|
Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("YES") | Ok("on") | Ok("ON")
|
|
)
|
|
}
|
|
|
|
fn short_file(path: &str) -> &str {
|
|
path.rsplit(['/', '\\']).next().unwrap_or(path)
|
|
}
|