1use anyhow::{bail, Result};
7use chrono::Local;
8use fern::colors::{Color, ColoredLevelConfig};
9use fern::{log_file, Dispatch, FormatCallback};
10use log::{LevelFilter, Record};
11use std::env;
12use std::fmt::{Arguments, Display};
13use std::io::IsTerminal;
14use std::path::Path;
15use std::sync::OnceLock;
16
17static LOGGER_INIT: OnceLock<()> = OnceLock::new();
19
20const DEFAULT_LOG_LEVEL: &str = "info";
25
26const LOG_INFO_FILE_NAME: &str = "muse2_info.log";
28
29const LOG_ERROR_FILE_NAME: &str = "muse2_error.log";
31
32pub fn is_logger_initialised() -> bool {
34 LOGGER_INIT.get().is_some()
35}
36
37pub fn init(log_level_from_settings: Option<&str>, output_path: &Path) -> Result<()> {
56 let log_level = env::var("MUSE2_LOG_LEVEL").unwrap_or_else(|_| {
58 log_level_from_settings
59 .unwrap_or(DEFAULT_LOG_LEVEL)
60 .to_string()
61 });
62
63 let log_level = match log_level.to_lowercase().as_str() {
65 "off" => LevelFilter::Off,
66 "error" => LevelFilter::Error,
67 "warn" => LevelFilter::Warn,
68 "info" => LevelFilter::Info,
69 "debug" => LevelFilter::Debug,
70 "trace" => LevelFilter::Trace,
71 unknown => bail!("Unknown log level: {}", unknown),
72 };
73
74 let colours = ColoredLevelConfig::new()
76 .error(Color::Red)
77 .warn(Color::Yellow)
78 .info(Color::Green)
79 .debug(Color::Blue)
80 .trace(Color::Magenta);
81
82 let use_colour_stdout = std::io::stdout().is_terminal();
84 let use_colour_stderr = std::io::stderr().is_terminal();
85
86 let info_log_file = log_file(output_path.join(LOG_INFO_FILE_NAME))?;
88 let err_log_file = log_file(output_path.join(LOG_ERROR_FILE_NAME))?;
89
90 let dispatch = Dispatch::new()
92 .chain(
93 Dispatch::new()
95 .filter(|metadata| metadata.level() > LevelFilter::Warn)
96 .format(move |out, message, record| {
97 write_log_colour(out, message, record, use_colour_stdout, &colours);
98 })
99 .level(log_level)
100 .chain(std::io::stdout()),
101 )
102 .chain(
103 Dispatch::new()
105 .format(move |out, message, record| {
106 write_log_colour(out, message, record, use_colour_stderr, &colours);
107 })
108 .level(log_level.min(LevelFilter::Warn))
109 .chain(std::io::stderr()),
110 )
111 .chain(
112 Dispatch::new()
114 .filter(|metadata| metadata.level() > LevelFilter::Warn)
115 .format(write_log_plain)
116 .level(log_level.max(LevelFilter::Info))
117 .chain(info_log_file),
118 )
119 .chain(
120 Dispatch::new()
122 .format(write_log_plain)
123 .level(LevelFilter::Warn)
124 .chain(err_log_file),
125 );
126
127 dispatch.apply().expect("Logger already initialised");
129
130 LOGGER_INIT.set(()).unwrap();
132
133 Ok(())
134}
135
136fn write_log<T: Display>(out: FormatCallback, level: T, target: &str, message: &Arguments) {
138 let timestamp = Local::now().format("%H:%M:%S");
139
140 out.finish(format_args!(
141 "[{} {} {}] {}",
142 timestamp, level, target, message
143 ));
144}
145
146fn write_log_plain(out: FormatCallback, message: &Arguments, record: &Record) {
148 write_log(out, record.level(), record.target(), message);
149}
150
151fn write_log_colour(
153 out: FormatCallback,
154 message: &Arguments,
155 record: &Record,
156 use_colour: bool,
157 colours: &ColoredLevelConfig,
158) {
159 if use_colour {
161 write_log(out, colours.color(record.level()), record.target(), message);
162 } else {
163 write_log_plain(out, message, record);
164 }
165}