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;
15
16const DEFAULT_LOG_LEVEL: &str = "info";
21
22const LOG_INFO_FILE_NAME: &str = "muse2_info.log";
24
25const LOG_ERROR_FILE_NAME: &str = "muse2_error.log";
27
28pub fn init(log_level_from_settings: Option<&str>, output_path: &Path) -> Result<()> {
47 let log_level = env::var("MUSE2_LOG_LEVEL").unwrap_or_else(|_| {
49 log_level_from_settings
50 .unwrap_or(DEFAULT_LOG_LEVEL)
51 .to_string()
52 });
53
54 let log_level = match log_level.to_lowercase().as_str() {
56 "off" => LevelFilter::Off,
57 "error" => LevelFilter::Error,
58 "warn" => LevelFilter::Warn,
59 "info" => LevelFilter::Info,
60 "debug" => LevelFilter::Debug,
61 "trace" => LevelFilter::Trace,
62 unknown => bail!("Unknown log level: {}", unknown),
63 };
64
65 let colours = ColoredLevelConfig::new()
67 .error(Color::Red)
68 .warn(Color::Yellow)
69 .info(Color::Green)
70 .debug(Color::Blue)
71 .trace(Color::Magenta);
72
73 let use_colour_stdout = std::io::stdout().is_terminal();
75 let use_colour_stderr = std::io::stderr().is_terminal();
76
77 let info_log_file = log_file(output_path.join(LOG_INFO_FILE_NAME))?;
79 let err_log_file = log_file(output_path.join(LOG_ERROR_FILE_NAME))?;
80
81 let dispatch = Dispatch::new()
83 .chain(
84 Dispatch::new()
86 .filter(|metadata| metadata.level() > LevelFilter::Warn)
87 .format(move |out, message, record| {
88 write_log_colour(out, message, record, use_colour_stdout, &colours);
89 })
90 .level(log_level)
91 .chain(std::io::stdout()),
92 )
93 .chain(
94 Dispatch::new()
96 .format(move |out, message, record| {
97 write_log_colour(out, message, record, use_colour_stderr, &colours);
98 })
99 .level(log_level.min(LevelFilter::Warn))
100 .chain(std::io::stderr()),
101 )
102 .chain(
103 Dispatch::new()
105 .filter(|metadata| metadata.level() > LevelFilter::Warn)
106 .format(write_log_plain)
107 .level(log_level.max(LevelFilter::Info))
108 .chain(info_log_file),
109 )
110 .chain(
111 Dispatch::new()
113 .format(write_log_plain)
114 .level(LevelFilter::Warn)
115 .chain(err_log_file),
116 );
117
118 dispatch.apply()?;
120
121 Ok(())
122}
123
124fn write_log<T: Display>(out: FormatCallback, level: T, target: &str, message: &Arguments) {
126 let timestamp = Local::now().format("%H:%M:%S");
127
128 out.finish(format_args!(
129 "[{} {} {}] {}",
130 timestamp, level, target, message
131 ));
132}
133
134fn write_log_plain(out: FormatCallback, message: &Arguments, record: &Record) {
136 write_log(out, record.level(), record.target(), message);
137}
138
139fn write_log_colour(
141 out: FormatCallback,
142 message: &Arguments,
143 record: &Record,
144 use_colour: bool,
145 colours: &ColoredLevelConfig,
146) {
147 if use_colour {
149 write_log(out, colours.color(record.level()), record.target(), message);
150 } else {
151 write_log_plain(out, message, record);
152 }
153}