1#![feature(ptr_metadata, panic_payload_as_str)]
6use std::mem::replace;
26use std::num::NonZeroU32;
27use std::panic::Location;
28use std::path::Path;
29use std::sync::Mutex;
30use std::thread::{self, Thread, ThreadId};
31use std::{fmt, panic, ptr};
32
33pub use log;
35use log::{Level, LevelFilter, Log, set_logger, set_max_level};
36use sys_abstract::{MaybeBacktrace, SystemImpl};
37use time::{Date, OffsetDateTime};
38
39mod sys_abstract;
40
41#[cfg(target_arch = "wasm32")]
42mod sys_web;
43#[cfg(target_arch = "wasm32")]
44use sys_web::System;
45
46#[cfg(not(target_arch = "wasm32"))]
47mod sys_native;
48#[cfg(not(target_arch = "wasm32"))]
49use sys_native::System;
50
51pub struct Settings {
53 pub title: &'static str,
55 pub filters: &'static [(&'static str, LevelFilter)],
58 pub file_out: Option<&'static Path>,
60 pub console_out: bool,
62 pub panic_hook: bool,
64}
65
66impl Settings {
67 pub fn init(self) {
72 let Self {
73 title,
74 filters,
75 file_out,
76 console_out,
77 panic_hook,
78 } = self;
79 let max_level = filters
80 .iter()
81 .map(|&(_, level)| level)
82 .max()
83 .unwrap_or(LevelFilter::Off);
84 if panic_hook {
85 panic::set_hook(Box::new(panic_handler));
88 }
89 let date = now().date();
90 let logger = Logger {
91 title,
92 filters,
93 file_out: file_out.map(System::file_new),
94 console_out: console_out.then(System::console_new),
95 prev_day: Mutex::new(date.to_julian_day()),
96 };
97 let message = Header { title, date };
98 if let Some(out) = &logger.file_out {
99 System::file_p_header(out, &message);
100 }
101 if let Some(out) = &logger.console_out {
102 System::console_p_header(out, &message);
103 }
104 set_logger(upcast_log(Box::leak(Box::new(logger)))).expect("Failed to apply logger");
105 set_max_level(max_level);
106 }
107}
108
109fn as_dyn_ref(logger: *const Logger) -> *const dyn Log {
111 logger as *const dyn Log
113}
114fn upcast_log(logger: &'static Logger) -> &'static dyn Log {
115 unsafe { &*as_dyn_ref(logger) }
117}
118fn downcast_log(log: &'static dyn Log) -> Option<&'static Logger> {
119 let (logger_ptr, logger_meta) = (&raw const *log).to_raw_parts();
121 let (_, fake_logger_meta) = as_dyn_ref(ptr::null::<Logger>()).to_raw_parts();
122 (logger_meta == fake_logger_meta).then(|| {
123 unsafe { &*logger_ptr.cast::<Logger>() }
125 })
126}
127
128struct Logger {
130 title: &'static str,
131 filters: &'static [(&'static str, LevelFilter)],
132 file_out: Option<<System as SystemImpl>::File>,
133 console_out: Option<<System as SystemImpl>::Console>,
134 prev_day: Mutex<i32>,
135}
136
137impl Log for Logger {
138 fn enabled(&self, meta: &log::Metadata) -> bool {
139 for (name, level) in self.filters {
140 if meta.target().starts_with(name) {
141 return *level >= meta.level();
142 }
143 }
144 false
145 }
146 fn log(&self, record: &log::Record) {
147 if self.enabled(record.metadata()) {
148 let now = now();
149 let date = now.date();
150 let day = date.to_julian_day();
151 let date = match self.prev_day.lock() {
152 Ok(mut lock) => (replace(&mut *lock, day) != day).then_some(date),
153 Err(_) => None,
154 };
155 let thread = thread::current();
156 let message = Record {
157 date,
158 module: record.module_path().unwrap_or("?"),
159 line: NonZeroU32::new(record.line().unwrap_or(0)),
160 thread: ThreadName::new(&thread),
161 args: *record.args(),
162 hmsms: now.time().as_hms_milli(),
163 level: record.level(),
164 };
165 if let Some(out) = &self.file_out {
166 System::file_p_record(out, &message);
167 }
168 if let Some(out) = &self.console_out {
169 System::console_p_record(out, &message);
170 }
171 }
172 }
173 fn flush(&self) {
174 self.file_out.as_ref().map(System::file_flush);
175 self.console_out.as_ref().map(System::console_flush);
176 }
177}
178
179fn now() -> OffsetDateTime {
180 OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc())
181}
182
183#[derive(Debug)]
184enum ThreadName<'data> {
185 Name(&'data str),
186 Id(ThreadId),
187}
188impl<'data> ThreadName<'data> {
189 fn new(thread: &'data Thread) -> Self {
190 if let Some(name) = thread.name() {
191 Self::Name(name)
192 } else {
193 Self::Id(thread.id())
194 }
195 }
196}
197impl fmt::Display for ThreadName<'_> {
198 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199 match self {
200 ThreadName::Name(name) => write!(f, "Thread {name:?}"),
201 ThreadName::Id(id) => write!(f, "{id:?}"),
202 }
203 }
204}
205
206struct Header {
210 title: &'static str,
211 date: Date,
212}
213
214struct Record<'data> {
221 date: Option<Date>,
222 module: &'data str,
223 line: Option<NonZeroU32>,
224 thread: ThreadName<'data>,
225 args: fmt::Arguments<'data>,
226 hmsms: (u8, u8, u8, u16),
227 level: Level,
228}
229
230#[derive(Debug)]
236struct Panic<'data> {
237 thread: ThreadName<'data>,
238 message: Option<&'data str>,
240 location: Option<Location<'data>>,
242 title: &'data str,
244 path: Option<&'data Path>,
246 trace: MaybeBacktrace<System>,
248}
249
250impl Panic<'_> {
251 fn message_str(&self) -> &str { self.message.unwrap_or("[non-string message]") }
252 fn location_display(&self) -> &dyn fmt::Display {
253 self.location.as_ref().map_or(&"[citation needed]", |v| v)
254 }
255}
256
257fn panic_handler(info: &panic::PanicHookInfo) {
258 let logger = downcast_log(log::logger());
259 let thread = thread::current();
260 let mut message = Panic {
261 thread: ThreadName::new(&thread),
262 message: info.payload_as_str(),
263 location: info.location().copied(),
264 title: "[pre-init?]",
265 path: None,
266 trace: System::backtrace_new(),
267 };
268 if let Some(logger) = logger {
269 message.title = logger.title;
270 message.path = System::file_path(logger.file_out.as_ref());
271 if let Some(out) = &logger.file_out {
272 System::file_p_panic(out, &message);
273 }
274 if let Some(out) = &logger.console_out {
275 System::console_p_panic(out, &message);
276 }
277 } else {
281 System::fallback_p_panic(&message);
282 }
283}