Skip to main content

lectio_diei/
logging.rs

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
15/// Initializes a combined logger included a terminal logger and a file logger. If file logger fails to be created, still initializes the terminal logger
16pub 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
31/// Tries to initialize the given loggers into a combined logger
32fn 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/// Creates an uninitialized terminal logger
39///
40/// Unused but keeping it around for reference
41// Reason: CombinedLogger::init needs boxed values
42#[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// Reason: CombinedLogger::init needs boxed values
58#[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
68/// Creates an uninitialized file logger
69fn 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/// Represents a failure to open a file for the purpose of writing logs to it
94#[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}