1use std::fs::File;
2use std::path::PathBuf;
3
4use chrono::Utc;
5use simplelog::{CombinedLogger, ConfigBuilder, LevelFilter, SharedLogger, SimpleLogger, WriteLogger};
6
7use crate::config::Config;
8use crate::Result;
9
10fn parse_level(level: &str) -> LevelFilter {
11 match level.to_lowercase().as_str() {
12 "trace" => LevelFilter::Trace,
13 "debug" => LevelFilter::Debug,
14 "warn" => LevelFilter::Warn,
15 "error" => LevelFilter::Error,
16 _ => LevelFilter::Info,
17 }
18}
19
20pub fn init_logging(config: &Config, cli_verbose: bool) -> Result<()> {
21 let level = if cli_verbose {
22 LevelFilter::Debug
23 } else {
24 parse_level(&config.log_level)
25 };
26
27 let config_builder = ConfigBuilder::new()
28 .set_time_format_rfc3339()
29 .set_thread_level(LevelFilter::Off)
30 .set_location_level(LevelFilter::Error)
31 .build();
32
33 let mut loggers: Vec<Box<dyn SharedLogger>> = Vec::new();
34
35 let term_logger: Box<dyn SharedLogger> = SimpleLogger::new(level, config_builder.clone());
37 loggers.push(term_logger);
38
39 if config.log_to_file {
41 let log_dir = Config::data_dir()?.join("logs");
42 std::fs::create_dir_all(&log_dir)?;
43 let log_file = log_dir.join("epik.log");
44 let file = File::create(&log_file)?;
45 loggers.push(WriteLogger::new(level, config_builder.clone(), file));
46 }
47
48 if let Err(e) = CombinedLogger::init(loggers) {
49 eprintln!("Logger already initialized: {}", e);
51 }
52
53 log::set_max_level(level);
54 Ok(())
55}
56
57pub fn install_panic_hook(enabled: bool, data_dir: PathBuf) {
58 if !enabled {
59 return;
60 }
61
62 let report_dir = data_dir.join("crash_reports");
63 let _ = std::fs::create_dir_all(&report_dir);
64
65 std::panic::set_hook(Box::new(move |info| {
66 let timestamp = Utc::now().format("%Y%m%d-%H%M%S");
67 let file_path = report_dir.join(format!("crash-{}.log", timestamp));
68 let mut report = String::new();
69
70 report.push_str("Epik Launcher Crash Report\n");
71 report.push_str(&format!("Timestamp: {}\n", Utc::now().to_rfc3339()));
72 report.push_str(&format!("Thread: {:?}\n", std::thread::current().name()));
73
74 if let Some(location) = info.location() {
75 report.push_str(&format!(
76 "Location: file {} line {} col {}\n",
77 location.file(),
78 location.line(),
79 location.column()
80 ));
81 }
82
83 if let Some(s) = info.payload().downcast_ref::<&str>() {
84 report.push_str(&format!("Payload: {}\n", s));
85 } else if let Some(s) = info.payload().downcast_ref::<String>() {
86 report.push_str(&format!("Payload: {}\n", s));
87 } else {
88 report.push_str("Payload: <unknown>\n");
89 }
90
91 if let Ok(mut file) = File::create(&file_path) {
92 use std::io::Write;
93 let _ = file.write_all(report.as_bytes());
94 }
95
96 eprintln!("A crash report was written to {:?}", file_path);
97 }));
98}