1mod r#macro; pub mod r#trait;
3pub mod structs;
4pub mod webwriter;
5mod size_limit;
6mod signals;
7
8use lazy_init::Lazy;
9use r#macro::logging_parts;
10use lazy_static::lazy_static;
11
12use size_limit::CircularFileBuffer;
13use r#trait::WriteImmut;
14use structs::{PoisonErrorWrapper, ErrorWrapper};
15use url::Url;
16use webwriter::WebWriter;
17
18use std::collections::HashMap;
19use smallvec::{SmallVec, smallvec};
20
21use log::{LevelFilter, Metadata, Record};
22
23use std::fmt::{Display, Formatter};
24use std::sync::{RwLock, Arc};
25use chrono::{DateTime, Utc};
26
27
28use std::io::{stderr, stdout, ErrorKind, Write};
29use std::fs::OpenOptions;
30use std::path::Path;
31
32
33pub (crate) use std::io::{Error as IOError, Result as IOResult};
34
35#[doc(hidden)]
36pub mod __internal_redirects {
37 pub use log::{trace, debug, info, warn, error};
38}
39pub use log::Level;
40pub use arcs_logging_rs_proc_macro::with_target;
41use const_format::concatcp;
42
43macro_rules! color_text_fmt {
44 (bold $color_sequence:literal, $formatting_lit:literal, $text:expr) => {
45 format_args!(
46 "{}{}{}",
47 concatcp!("\x1b[1m", "\x1b[", $color_sequence, "m"),
48 format_args!($formatting_lit, $text),
49 "\x1b[0m",
50 )
51 };
52 ($color_sequence:literal, $formatting_lit:literal, $text:expr) => {
53 format_args!(
54 "{}{}{}",
55 concatcp!("\x1b[", $color_sequence, "m"),
56 format_args!($formatting_lit, $text),
57 "\x1b[0m",
58 )
59 };
60 (option bold $color_sequence:literal, $formatting_lit:literal, $text:expr) => {
61 {
62 let module_path = $text;
63 module_path.and(
64 Some(format_args!(
65 "{}{}{}",
66 concatcp!("\x1b[1m", "\x1b[", $color_sequence, "m"),
67 format!($formatting_lit, $text.unwrap()),
68 "\x1b[0m",
69 ))
70 )
71 }
72 };
73 (option $color_sequence:literal, $formatting_lit:literal, $text:expr) => {
74 {
75 let module_path = $text;
76 module_path.and(
77 Some(format_args!(
78 "{}{}{}",
79 concatcp!("\x1b[", $color_sequence, "m"),
80 format!($formatting_lit, $text.unwrap()),
81 "\x1b[0m",
82 ))
83 )
84 }
85 };
86}
87
88
89pub type LogLocationTargetMap<'a> = HashMap<Level, SmallVec<[LogLocationTarget<'a>; 6]>>;
90
91#[derive(Debug)]
92pub enum LogLocationTarget<'a> {
93 StdOut,
94 StdErr,
95 File(&'a Path, Option<u64>),
96 WebWriter(&'a str),
97}
98
99#[derive(Debug, Clone)]
100enum WritableLogLocationTarget {
101 StdOut,
102 StdErr,
103 File(Arc<RwLock<CircularFileBuffer>>),
104 WebWriter(Arc<WebWriter>),
105}
106
107impl WriteImmut for WritableLogLocationTarget {
108 fn write(&self, buf: &[u8]) -> IOResult<usize> {
109
110 match self {
111 WritableLogLocationTarget::StdOut => Write::write(&mut stdout(), buf),
112 WritableLogLocationTarget::StdErr => Write::write(&mut stderr(), buf),
113 WritableLogLocationTarget::File(f) => f.write().map_or_else(
114 |err| {
115 Err(IOError::new(
116 ErrorKind::Other,
117 PoisonErrorWrapper::from(err),
118 ))
119 },
120 |mut file| file.write(buf),
121 ),
122 WritableLogLocationTarget::WebWriter(w) => w.add_line(buf),
123 }
124 }
125 fn write_vectored(&self, bufs: &[std::io::IoSlice<'_>]) -> IOResult<usize> {
126 self.write(
127 bufs.iter()
128 .find(|buf| !buf.is_empty())
129 .map_or(&[][..], |buf| &**buf),
130 )
131 }
132 fn is_write_vectored(&self) -> bool {
133 false
134 }
135
136 fn flush(&self) -> IOResult<()> {
137 match self {
138 WritableLogLocationTarget::StdOut => stdout().flush(),
139 WritableLogLocationTarget::StdErr => stdout().flush(),
140 WritableLogLocationTarget::File(f) => f.write().map_or_else(
141 |err| {
142 Err(IOError::new(
143 ErrorKind::Other,
144 PoisonErrorWrapper::from(err),
145 ))
146 },
147 |mut file| {
148 file.flush()
149 },
150 ),
151 WritableLogLocationTarget::WebWriter(w) => w.flush(),
152 }
153 }
154}
155
156impl Write for WritableLogLocationTarget {
157 fn write(&mut self, buf: &[u8]) -> IOResult<usize> {
158 WriteImmut::write(self, buf)
159 }
160 fn flush(&mut self) -> IOResult<()> {
161 WriteImmut::flush(self)
162 }
163}
164
165#[derive(Debug, Default)]
166pub struct WritableLogLocationTargetMap(HashMap<Level, SmallVec<[WritableLogLocationTarget; 6]>>);
167
168impl Display for WritableLogLocationTargetMap {
169 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
170 f.write_fmt(format_args!("{:#?}", self))
171 }
172}
173
174struct FileLogger {
175 targets: RwLock<WritableLogLocationTargetMap>,
176 name: Lazy<&'static str>,
177 file_prefix: Lazy<(String, String)>,
178}
179
180impl FileLogger {
181 fn clear_targets(&self) {
182 use std::borrow::BorrowMut;
183
184 if let Ok(mut targets) = self.targets.write() {
185 let prev_map: WritableLogLocationTargetMap = std::mem::replace(
186 targets.borrow_mut(),
187 WritableLogLocationTargetMap(HashMap::new()),
188 );
189
190 drop(prev_map);
191 }
192 }
193}
194
195struct LevelStringStruct {
196 error: String,
197 warn: String,
198 info: String,
199 debug: String,
200 trace: String,
201}
202
203impl LevelStringStruct {
204 fn get_level(&self, level: Level) -> &str {
205 use Level::*;
206
207 match level {
208 Error => &self.error,
209 Warn => &self.warn,
210 Info => &self.info,
211 Debug => &self.debug,
212 Trace => &self.trace,
213 }
214 }
215}
216
217impl Default for LevelStringStruct {
218 fn default() -> Self {
219 Self {
220 error: color_text_fmt!(bold "31", "{:<5}", "ERROR").to_string(),
221 warn: color_text_fmt!(bold "33", "{:<5}", "WARN ").to_string(),
222 info: color_text_fmt!(bold "36", "{:<5}", "INFO ").to_string(),
223 debug: color_text_fmt!(bold "32", "{:<5}", "DEBUG").to_string(),
224 trace: color_text_fmt!(bold "35", "{:<5}", "TRACE").to_string(),
225 }
226 }
227}
228
229lazy_static! {
230 static ref LEVEL_STRINGS: LevelStringStruct = LevelStringStruct::default();
231}
232
233impl log::Log for FileLogger {
234 fn enabled(&self, metadata: &Metadata) -> bool {
235 Some(&metadata.target()) == self.name.get()
236 }
237
238 fn log(&self, record: &Record) {
239 if !self.enabled(record.metadata()) { return; }
240 let target_map = match self.targets.read() {
241 Ok(guard) => guard,
242 Err(e) => {
243 eprintln!("Logging target poisoned! {}", e);
244 return;
245 }
246 };
247
248 let utc: DateTime<Utc> = Utc::now();
249 let (level, args) = (record.level(), record.args());
250
251 for target in target_map.0.get(&record.level()).into_iter().flatten() {
252 let stripped = with_path_prefix_stripped(record.file(), self.file_prefix.get());
253 let full: Option<String> = stripped.map(|(prefix, body)| [prefix, body].into_iter().collect());
254
255 let log_result = logging_parts!(
256 target; <==
257 "{} " - Some(color_text_fmt!("38;5;147", "{}", utc.format("%b %d"))),
258 "{}" - Some(color_text_fmt!("38;5;86", "{}", utc.format("%H:%M:%S"))),
259 "{} | " - Some(color_text_fmt!("38;5;23", "{}", utc.format("%.3f"))),
260
261 "{} " - color_text_fmt!(option "38;5;47", "{}", record.module_path()),
262
263 "{}" - color_text_fmt!(option "38;5;159", "{}", full.as_ref())
264 => ":{}" - color_text_fmt!(option "38;5;159", "{:<3}", record.line())
265 => * "; ",
266 "{} - " - Some(LEVEL_STRINGS.get_level(level)),
267 "{}" - Some(args),
268 );
269
270 if let Err(error) = log_result {
271 eprintln!("Failed to log to x! Error: {:?}", error);
272 }
273 }
274 }
275
276 fn flush(&self) {
277 let target_map = match self.targets.read() {
278 Ok(guard) => guard,
279 Err(e) => {
280 eprintln!("Logging target poisoned! {}", e);
281 return;
282 }
283 };
284
285 for target in target_map.0.values().flatten() {
286 if let Err(error) = target.flush() {
287 eprintln!("Failed to flush to x! Error: {:?}", error);
288 }
289 }
290 }
291}
292
293lazy_static! {
294 static ref LOGGER: FileLogger = FileLogger {
295 targets: RwLock::default(),
296 name: Lazy::new(),
297 file_prefix: Lazy::new(),
298 };
299}
300
301pub fn with_path_prefix_stripped<'a>(path: Option<&'a str>, prefix: Option<&'a (String, String)>) -> Option<(&'a str, &'a str)> {
302 if let (Some(path), Some((prefix, replace))) = (path, prefix) {
303 path.strip_prefix(prefix).map_or_else(
304 || Some(("", path)),
305 |stripped| Some((replace, stripped)),
306 )
307 } else {
308 path.map(|p| ("", p))
309 }
310}
311
312pub fn set_up_logging(input: &LogLocationTargetMap<'_>, name: &'static str) -> IOResult<impl FnOnce()> {
313 LOGGER.name.get_or_create(|| name);
314 if let Ok(value) = std::env::var("LOGGING_PREFIX_REPLACE") {
315 if let Some((prefix, replace)) = value.split_once("->") {
316 LOGGER.file_prefix.get_or_create(|| (prefix.to_string(), replace.to_string()));
317 }
318 }
319
320 let mut target_hashmap = LOGGER
321 .targets
322 .write()
323 .map_err(|error| IOError::new(ErrorKind::Other, PoisonErrorWrapper::from(error)))?;
324
325 *target_hashmap = generate_writable_log_location_target_map(input, Utc::now());
326
327 let log_handler = signals::setup_signal_handler()?;
328
329 log::set_logger(&*LOGGER)
330 .map(|()| log::set_max_level(LevelFilter::Trace))
331 .map_err(|error| IOError::new(ErrorKind::Other, ErrorWrapper::from(error)))?;
332
333 Ok(log_handler)
334}
335
336pub fn generate_writable_log_location_target_map(
337 from: &LogLocationTargetMap,
338 time_startup: DateTime<Utc>,
339) -> WritableLogLocationTargetMap {
340 let mut file_map = HashMap::new();
341 let mut webwriter_map = HashMap::new();
342 WritableLogLocationTargetMap(
343 from.iter()
344 .map(|(level, targets)| {
345 let mut writable_targets = vec![];
346
347 for target in targets {
348 let writable_target = match target {
349 LogLocationTarget::File(path, size_limit) if !file_map.contains_key(path) => 'result: {
350 let file = match OpenOptions::new().write(true).read(true).create(true).open(path) {
351 Ok(file) => file,
352 Err(e) => {
353 eprintln!("Failed to open file! Error: {}", e);
354 break 'result Err(IOError::new(ErrorKind::Other, e));
355 }
356 };
357
358 let mut circular_file = match CircularFileBuffer::new(file, size_limit.unwrap_or(u64::MAX)) {
359 Ok(circular_file) => circular_file,
360 Err(e) => {
361 eprintln!("Failed to create circular file buffer! Error: {:?}", e);
362 break 'result Err(IOError::new(ErrorKind::Other, e));
363 }
364 };
365 let write_result = writeln!(
366 circular_file,
367 "\n\n{:-^50}",
368 format!(
369 "Logging started at {}",
370 time_startup.format("%b %d %H:%M:%S%.3f"),
371 ),
372 );
373 if let Err(e) = write_result {
374 eprintln!("Failed to write to circular file buffer! Error: {}", e);
375 break 'result Err(IOError::new(ErrorKind::Other, e));
376 }
377 let new_file = Arc::new(RwLock::new(circular_file));
378 let new_file = file_map.entry(path).or_insert(new_file.clone()).clone();
379 Ok(WritableLogLocationTarget::File(new_file))
380 }
381 LogLocationTarget::File(path, _) => match file_map.get(path) {
382 Some(file) => Ok(WritableLogLocationTarget::File(file.clone())),
383 None => Err(std::io::Error::new(
384 ErrorKind::Other,
385 format!("File not found in file_map! {:?}", path),
386 )),
387 },
388 LogLocationTarget::WebWriter(url) => 'result: {
389 let url = match Url::parse(url) {
390 Ok(url) => url,
391 Err(e) => {
392 eprintln!("Failed to parse url! Error: {}", e);
393 break 'result Err(IOError::new(ErrorKind::Other, e));
394 }
395 };
396 let web_writer = webwriter_map
397 .entry(url.clone())
398 .or_insert_with(|| Arc::new(WebWriter::new(url.clone())))
399 .clone();
400
401 Ok(WritableLogLocationTarget::WebWriter(web_writer))
402 }
403 LogLocationTarget::StdOut => Ok(WritableLogLocationTarget::StdOut),
404 LogLocationTarget::StdErr => Ok(WritableLogLocationTarget::StdErr),
405 };
406
407
408 match writable_target {
409 Ok(writable_target) => writable_targets.push(writable_target),
410 Err(error) => eprintln!("{}", error),
411 }
412 }
413
414 (*level, smallvec::SmallVec::from_vec(writable_targets))
415 })
416 .collect(),
417 )
418}
419
420
421lazy_static! {
422 pub static ref ERR_FILE: &'static Path = Path::new("./err.log");
423 pub static ref ERR_WARN_FILE: &'static Path = Path::new("./err_warn.log");
424 pub static ref INFO_DEBUG_FILE: &'static Path = Path::new("./info_debug.log");
425 pub static ref ALL_LOG_FILE: &'static Path = Path::new("./all.log");
426
427 pub static ref LOGGING_URL: Option<String> = {
428 if let Ok(url) = std::env::var("LOGGING_TARGET_URL") {
429 if let Ok(url) = Url::parse(&url) {
430 Some(url.to_string())
431 } else { None }
432 } else { None }
433 };
434
435 pub static ref DEFAULT_LOGGING_TARGETS: LogLocationTargetMap<'static> = {
436 use Level::*;
437 use LogLocationTarget::*;
438
439
440 let mut target_map = vec![
441 (Trace, smallvec![
442 StdOut,
443 File(&ALL_LOG_FILE, None),
444 ]),
445 (Debug, smallvec![
446 File(&INFO_DEBUG_FILE, None),
448 File(&ALL_LOG_FILE, None),
449 ]),
450 (Info, smallvec![
451 StdOut,
452 File(&INFO_DEBUG_FILE, None),
453 File(&ALL_LOG_FILE, None),
454 ]),
455 (Warn, smallvec![
456 StdErr,
457 File(&ERR_WARN_FILE, None),
458 File(&ALL_LOG_FILE, None),
459 ]),
460 (Error, smallvec![
461 StdErr,
462 File(&ERR_FILE, None),
463 File(&ERR_WARN_FILE, None),
464 File(&ALL_LOG_FILE, None),
465 ]),
466
467 ];
468
469 if let Some(url) = LOGGING_URL.as_ref() {
470 target_map[0].1.push(WebWriter(url));
471 target_map[1].1.push(WebWriter(url));
472 target_map[2].1.push(WebWriter(url));
473 target_map[3].1.push(WebWriter(url));
474 target_map[4].1.push(WebWriter(url));
475 }
476
477 target_map.into_iter().collect()
478 };
479}
480
481pub fn default_logging_targets_with_size_limit(limit: u64) -> LogLocationTargetMap<'static> {
482 use Level::*;
483 use LogLocationTarget::*;
484
485
486 let mut target_map = vec![
487 (Trace, smallvec![
488 StdOut,
489 File(&ALL_LOG_FILE, Some(limit)),
490 ]),
491 (Debug, smallvec![
492 File(&INFO_DEBUG_FILE, Some(limit)),
494 File(&ALL_LOG_FILE, Some(limit)),
495 ]),
496 (Info, smallvec![
497 StdOut,
498 File(&INFO_DEBUG_FILE, Some(limit)),
499 File(&ALL_LOG_FILE, Some(limit)),
500 ]),
501 (Warn, smallvec![
502 StdErr,
503 File(&ERR_WARN_FILE, Some(limit)),
504 File(&ALL_LOG_FILE, Some(limit)),
505 ]),
506 (Error, smallvec![
507 StdErr,
508 File(&ERR_FILE, Some(limit)),
509 File(&ERR_WARN_FILE, Some(limit)),
510 File(&ALL_LOG_FILE, Some(limit)),
511 ]),
512
513 ];
514
515 if let Some(url) = LOGGING_URL.as_ref() {
516 target_map[0].1.push(WebWriter(url));
517 target_map[1].1.push(WebWriter(url));
518 target_map[2].1.push(WebWriter(url));
519 target_map[3].1.push(WebWriter(url));
520 target_map[4].1.push(WebWriter(url));
521 }
522
523 target_map.into_iter().collect()
524}