use anyhow::{Result, bail};
use chrono::Local;
use fern::colors::{Color, ColoredLevelConfig};
use fern::{Dispatch, FormatCallback};
use log::{LevelFilter, Record};
use std::env;
use std::fmt::{Arguments, Display};
use std::fs::OpenOptions;
use std::io::IsTerminal;
use std::path::Path;
use std::sync::OnceLock;
static LOGGER_INIT: OnceLock<()> = OnceLock::new();
pub const DEFAULT_LOG_LEVEL: &str = "info";
const LOG_INFO_FILE_NAME: &str = "muse2_info.log";
const LOG_ERROR_FILE_NAME: &str = "muse2_error.log";
pub fn is_logger_initialised() -> bool {
LOGGER_INIT.get().is_some()
}
pub fn init(log_level_from_settings: &str, log_file_path: Option<&Path>) -> Result<()> {
let log_level =
env::var("MUSE2_LOG_LEVEL").unwrap_or_else(|_| log_level_from_settings.to_string());
let log_level = match log_level.to_lowercase().as_str() {
"off" => LevelFilter::Off,
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
unknown => bail!("Unknown log level: {unknown}"),
};
let colours = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
.info(Color::Green)
.debug(Color::Blue)
.trace(Color::Magenta);
let use_colour_stdout = std::io::stdout().is_terminal();
let use_colour_stderr = std::io::stderr().is_terminal();
let (info_log_file, err_log_file) = if let Some(log_file_path) = log_file_path {
let new_log_file = |file_name| {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(log_file_path.join(file_name))
};
(
Some(new_log_file(LOG_INFO_FILE_NAME)?),
Some(new_log_file(LOG_ERROR_FILE_NAME)?),
)
} else {
(None, None)
};
let mut dispatch = Dispatch::new()
.level_for("highs", LevelFilter::Off) .chain(
Dispatch::new()
.filter(|metadata| metadata.level() > LevelFilter::Warn)
.format(move |out, message, record| {
write_log_colour(out, message, record, use_colour_stdout, &colours);
})
.level(log_level)
.chain(std::io::stdout()),
)
.chain(
Dispatch::new()
.format(move |out, message, record| {
write_log_colour(out, message, record, use_colour_stderr, &colours);
})
.level(log_level.min(LevelFilter::Warn))
.chain(std::io::stderr()),
);
if let Some(info_log_file) = info_log_file {
dispatch = dispatch.chain(
Dispatch::new()
.filter(|metadata| metadata.level() > LevelFilter::Warn)
.format(write_log_plain)
.level(log_level.max(LevelFilter::Info))
.chain(info_log_file),
);
}
if let Some(err_log_file) = err_log_file {
dispatch = dispatch.chain(
Dispatch::new()
.format(write_log_plain)
.level(LevelFilter::Warn)
.chain(err_log_file),
);
}
dispatch.apply().expect("Logger already initialised");
LOGGER_INIT.set(()).unwrap();
Ok(())
}
fn write_log<T: Display>(out: FormatCallback, level: T, target: &str, message: &Arguments) {
let timestamp = Local::now().format("%H:%M:%S");
out.finish(format_args!("[{timestamp} {level} {target}] {message}"));
}
fn write_log_plain(out: FormatCallback, message: &Arguments, record: &Record) {
write_log(out, record.level(), record.target(), message);
}
fn write_log_colour(
out: FormatCallback,
message: &Arguments,
record: &Record,
use_colour: bool,
colours: &ColoredLevelConfig,
) {
if use_colour {
write_log(out, colours.color(record.level()), record.target(), message);
} else {
write_log_plain(out, message, record);
}
}