1mod config;
7
8pub use config::{LoggerConfig, LoggerConfigBuilder, LoggerOutputConfig, LoggerOutputConfigBuilder};
9
10use fern::{
11 colors::{Color, ColoredLevelConfig},
12 Dispatch,
13};
14use thiserror::Error;
15
16pub const LOGGER_STDOUT_NAME: &str = "stdout";
18
19#[derive(Error, Debug)]
21#[non_exhaustive]
22pub enum Error {
23 #[error("Creating output file failed.")]
25 CreatingFileFailed,
26 #[error("Initializing the logger backend failed.")]
28 InitializationFailed,
29}
30
31macro_rules! log_format {
32 ($target:expr, $level:expr, $message:expr, $target_width:expr, $level_width:expr) => {
33 format_args!(
34 "{} {:target_width$} {:level_width$} {}",
35 time_helper::format(&time_helper::now_utc()),
36 $target,
37 $level,
38 $message,
39 target_width = $target_width,
40 level_width = $level_width
41 )
42 };
43}
44
45pub fn logger_init(config: LoggerConfig) -> Result<(), Error> {
51 let target_width = config.target_width;
52 let level_width = config.level_width;
53
54 let mut logger = Dispatch::new();
55
56 for output in config.outputs {
57 let mut dispatch = if output.color_enabled {
59 let colors = ColoredLevelConfig::new()
60 .trace(Color::BrightMagenta)
61 .debug(Color::BrightBlue)
62 .info(Color::BrightGreen)
63 .warn(Color::BrightYellow)
64 .error(Color::BrightRed);
65
66 Dispatch::new().format(move |out, message, record| {
68 out.finish(log_format!(
69 record.target(),
70 colors.color(record.level()),
71 message,
72 target_width,
73 level_width
74 ))
75 })
76 } else {
77 Dispatch::new().format(move |out, message, record| {
79 out.finish(log_format!(
80 record.target(),
81 record.level(),
82 message,
83 target_width,
84 level_width
85 ))
86 })
87 }
88 .level(output.level_filter);
89
90 if !output.target_filters.is_empty() {
91 let target_filters = output.target_filters;
92 dispatch = dispatch.filter(move |metadata| {
93 let target = metadata.target().to_lowercase();
94 target_filters.iter().any(|f| target.contains(f))
95 });
96 }
97
98 if !output.target_exclusions.is_empty() {
99 let target_exclusions = output.target_exclusions;
100 dispatch = dispatch.filter(move |metadata| {
101 let target = metadata.target().to_lowercase();
102 !target_exclusions.iter().any(|f| target.contains(f))
103 });
104 }
105
106 dispatch = if output.name == LOGGER_STDOUT_NAME {
108 dispatch.chain(std::io::stdout())
109 } else {
110 dispatch.chain(fern::log_file(output.name).map_err(|_| Error::CreatingFileFailed)?)
111 };
112
113 logger = logger.chain(dispatch);
114 }
115
116 logger.apply().map_err(|_| Error::InitializationFailed)?;
117
118 Ok(())
119}