1use crate::backend::Backend;
30use crate::{LogMsg, Logger};
31use crossbeam_channel::{bounded, Receiver, Sender};
32use log::{Level, Log, Metadata, Record};
33use std::sync::atomic::{AtomicBool, Ordering};
34use std::sync::Mutex;
35use time::macros::format_description;
36use time::OffsetDateTime;
37use time_tz::OffsetDateTimeExt;
38
39const BUF_SIZE: usize = 128; enum Command {
42 Flush,
43 Log(LogMsg),
44 Terminate,
45}
46
47fn log<T: Backend>(
48 backend: Option<&mut T>,
49 target: &str,
50 msg: &str,
51 level: Level,
52) -> Result<(), T::Error> {
53 if let Some(back) = backend {
54 back.write(target, msg, level)
55 } else {
56 Ok(())
57 }
58}
59
60fn exec_commad(cmd: Command, logger: &mut Logger) -> bool {
61 match cmd {
62 Command::Terminate => true,
63 Command::Flush => {
64 if let Some(file) = &mut logger.file {
65 if let Err(e) = file.flush() {
66 let _ = log(
67 logger.std.as_mut(),
68 "bp3d-logger",
69 &format!("Could not flush file backend: {}", e),
70 Level::Error,
71 );
72 }
73 }
74 false
75 }
76 Command::Log(LogMsg { target, msg, level }) => {
77 if let Err(e) = log(logger.file.as_mut(), &target, &msg, level) {
78 let _ = log(
79 logger.std.as_mut(),
80 "bp3d-logger",
81 &format!("Could not write to file backend: {}", e),
82 Level::Error,
83 );
84 }
85 let _ = log(logger.std.as_mut(), &target, &msg, level);
86 false
87 }
88 }
89}
90
91pub struct LoggerImpl {
92 thread: Mutex<Option<std::thread::JoinHandle<()>>>,
93 send_ch: Sender<Command>,
94 recv_ch: Receiver<Command>,
95 log_buffer_send_ch: Sender<LogMsg>,
96 log_buffer_recv_ch: Receiver<LogMsg>,
97 enable_log_buffer: AtomicBool,
98 enabled: AtomicBool,
99}
100
101impl LoggerImpl {
102 pub fn new() -> LoggerImpl {
103 let (send_ch, recv_ch) = bounded(BUF_SIZE);
104 let (log_buffer_send_ch, log_buffer_recv_ch) = bounded(BUF_SIZE);
105 LoggerImpl {
106 thread: Mutex::new(None),
107 send_ch,
108 recv_ch,
109 log_buffer_send_ch,
110 log_buffer_recv_ch,
111 enable_log_buffer: AtomicBool::new(false),
112 enabled: AtomicBool::new(false),
113 }
114 }
115
116 pub fn enable(&self, flag: bool) {
117 self.enabled.store(flag, Ordering::Release);
118 }
119
120 pub fn enable_log_buffer(&self, flag: bool) {
121 self.enable_log_buffer.store(flag, Ordering::Release);
122 }
123
124 pub fn clear_log_buffer(&self) {
125 while self.log_buffer_recv_ch.try_recv().is_ok() {} }
127
128 pub fn get_log_buffer(&self) -> Receiver<LogMsg> {
129 self.log_buffer_recv_ch.clone()
130 }
131
132 pub fn terminate(&self) {
133 let mut thread = self.thread.lock().unwrap();
135 if let Some(handle) = thread.take() {
136 unsafe {
139 self.send_ch.send(Command::Flush).unwrap_unchecked();
140 self.send_ch.send(Command::Terminate).unwrap_unchecked();
141 }
142 handle.join().unwrap();
144 }
145 }
146
147 pub fn start_new_thread(&self, logger: Logger) {
148 let mut flag = false;
149 {
150 let mut thread = self.thread.lock().unwrap();
152 if let Some(handle) = thread.take() {
153 unsafe {
156 self.send_ch.send(Command::Terminate).unwrap_unchecked();
157 }
158 if handle.join().is_err() {
159 flag = true;
160 }
161 }
162 let recv_ch = self.recv_ch.clone();
163 *thread = Some(std::thread::spawn(move || {
164 let mut logger = logger;
165 while let Ok(v) = recv_ch.recv() {
166 let flag = exec_commad(v, &mut logger);
167 if flag {
168 break;
170 }
171 }
172 }));
173 }
174 if flag {
175 unsafe {
177 self.send_ch
180 .send(Command::Log(LogMsg {
181 level: Level::Error,
182 msg: "The logging thread has panicked!".into(),
183 target: "bp3d-logger".into(),
184 }))
185 .unwrap_unchecked();
186 }
187 }
188 }
189
190 pub fn low_level_log(&self, msg: LogMsg) {
191 if self.enable_log_buffer.load(Ordering::Acquire) {
192 unsafe {
193 self.send_ch
196 .send(Command::Log(msg.clone()))
197 .unwrap_unchecked();
198 self.log_buffer_send_ch.send(msg).unwrap_unchecked();
199 }
200 } else {
201 unsafe {
202 self.send_ch.send(Command::Log(msg)).unwrap_unchecked();
205 }
206 }
207 }
208
209 pub fn is_enabled(&self) -> bool {
210 self.enabled.load(Ordering::Acquire)
211 }
212}
213
214fn extract_target_module<'a>(record: &'a Record) -> (&'a str, Option<&'a str>) {
215 let base_string = record.module_path().unwrap_or_else(|| record.target());
216 let target = base_string
217 .find("::")
218 .map(|v| &base_string[..v])
219 .unwrap_or(base_string);
220 let module = base_string.find("::").map(|v| &base_string[(v + 2)..]);
221 (target, module)
222}
223
224impl Log for LoggerImpl {
225 fn enabled(&self, _: &Metadata) -> bool {
226 self.is_enabled()
227 }
228
229 fn log(&self, record: &Record) {
230 if !self.enabled(record.metadata()) {
232 return;
233 }
234 let (target, module) = extract_target_module(record);
235 let system_tz =
237 time_tz::system::get_timezone().unwrap_or(time_tz::timezones::db::us::CENTRAL);
238 let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]");
239 let formatted = OffsetDateTime::now_utc()
241 .to_timezone(system_tz)
242 .format(format)
243 .unwrap_or_else(|_| "<error>".into());
244 let msg = LogMsg {
245 msg: format!(
246 "({}) {}: {}",
247 formatted,
248 module.unwrap_or("main"),
249 record.args()
250 ),
251 target: target.into(),
252 level: record.level(),
253 };
254 self.low_level_log(msg);
255 }
256
257 fn flush(&self) {
258 if !self.is_enabled() {
259 return;
260 }
261 unsafe {
262 self.send_ch.send(Command::Flush).unwrap_unchecked();
265 while !self.send_ch.is_empty() {}
266 }
267 }
268}