1use std::fs::File;
2use std::io;
3
4use log::*;
5use simplelog::{
6 format_description, ColorChoice, CombinedLogger, ConfigBuilder, LevelFilter, LevelPadding, SharedLogger, TermLogger, TerminalMode,
7 ThreadPadding, WriteLogger,
8};
9
10use crate::{
11 colorful_logger::{ColorConfig, ColorfulLogger},
12 path::{self, PathError},
13};
14
15pub fn init_logger(options: LoggingOptions) {
17 let mut loggers: Vec<Box<dyn SharedLogger>> = Vec::new();
18 loggers.push(color_logger(options));
19 match file_logger() {
20 Ok(file_logger) => {
21 loggers.push(file_logger);
22 init_combined(loggers);
23 }
24 Err(e) => {
25 init_combined(loggers);
26 error!("Failed to initialize file log: {e}");
27 }
28 }
29}
30
31fn init_combined(loggers: Vec<Box<dyn SharedLogger>>) {
33 if CombinedLogger::init(loggers).is_err() {
34 error!("Tried to initialize logger after already initialized");
35 }
36}
37
38#[allow(clippy::unnecessary_box_returns)]
43fn _terminal_logger() -> Box<TermLogger> {
44 TermLogger::new(
45 LevelFilter::Warn,
46 ConfigBuilder::new()
47 .add_filter_allow_str(env!("CARGO_CRATE_NAME"))
48 .set_target_level(LevelFilter::Off)
49 .set_time_level(LevelFilter::Off)
50 .set_thread_level(LevelFilter::Off)
51 .build(),
52 TerminalMode::Stderr,
53 ColorChoice::Auto,
54 )
55}
56
57#[allow(clippy::unnecessary_box_returns)]
59fn color_logger(options: LoggingOptions) -> Box<ColorfulLogger> {
60 let color_config = if options.no_color {
61 ColorConfig::no_color()
62 } else {
63 ColorConfig::default()
64 };
65 ColorfulLogger::new(LevelFilter::Warn, color_config)
66}
67
68fn file_logger() -> Result<Box<WriteLogger<File>>, FileLoggerError> {
70 let path = path::create_and_get_log_path()?;
71 let file = File::options().create(true).append(true).open(path)?;
72 Ok(WriteLogger::new(
73 LevelFilter::Debug,
74 ConfigBuilder::new()
75 .set_time_format_custom(format_description!(
76 "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"
77 ))
78 .set_target_level(LevelFilter::Off)
79 .set_level_padding(LevelPadding::Right)
80 .set_thread_level(LevelFilter::Error)
81 .set_thread_padding(ThreadPadding::Left(2))
82 .add_filter_allow_str(env!("CARGO_CRATE_NAME"))
83 .build(),
84 file,
85 ))
86}
87
88#[derive(Copy, Clone)]
89pub struct LoggingOptions {
90 pub no_color: bool,
91}
92
93#[derive(thiserror::Error, Debug)]
95enum FileLoggerError {
96 #[error("Failed to get path to log: ({0})")]
97 PathError(#[from] PathError),
98 #[error("Failed to open log file: ({0})")]
99 FileOpenError(#[from] io::Error),
100}