A way to change log level in runtime?
dmilith opened this issue · 4 comments
I wish to change the log level in runtime without restarting my long-running service. Is there a way of doing so?
Hi!
There isn't a built-in way, but it's fairly possible to build this on top of fern & something to synchronize the level get/set. Something like the following should work:
lazy_static! {
static LOG_LEVEL: RwLock<log::LevelFilter> = RwLock::new(log::LevelFilter::Off);
}
fn set_log_level(level: log::LevelFilter) {
*LOG_LEVEL.write() = level;
}
fn setup_logging() -> Result<(), fern::InitError> {
fern::Dispatch::new()
.filter(|metadata| {
metadata.level() < *LOG_LEVEL.read()
})
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
))
})
.chain(fern::log_file("program.log")?)
.apply()?;
Ok(())
}
I don't plan on adding this to fern natively for performance reasons, but if you get this working, it could be a great example to add.
Ha! It works. I had to add unwrap() for RwLock since it's Result, but the key here is not to set .level() in initialization chain. Then it works like a charm! Thank you very much!
Nice! Glad you got it working.
I'll leave this open for the sake of documenting this somewhere, or making an example of it, if that's alright.
I did it this way:
use std::sync::RwLock;
lazy_static! {
static ref LOG_LEVEL: RwLock<LevelFilter> = RwLock::new(LevelFilter::Info);
}
/// Set log level dynamically at runtime
fn set_log_level() {
let level = Config::load().get_log_level();
match LOG_LEVEL.read() {
Ok(loglevel) => {
if level != *loglevel {
drop(loglevel);
match LOG_LEVEL.write() {
Ok(mut log) => {
println!("Changing log level to: {}", level);
*log = level
}
Err(err) => {
println!("Failed to change log level to: {}, cause: {}", level, err);
}
}
}
}
Err(_) => {}
}
}
fn setup_logger() -> Result<(), SetLoggerError> {
let log_file = Config::load()
.log_file
.unwrap_or_else(|| String::from("krecik.log"));
let colors_line = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
.info(Color::White)
.debug(Color::Magenta)
.trace(Color::Cyan);
Dispatch::new()
.filter(|metadata| {
match LOG_LEVEL.read() {
Ok(log) => metadata.level() <= *log,
Err(_err) => true,
}
})
.format(move |out, message, record| {
out.finish(format_args!(
"{color_line}[{date}][{target}][{level}{color_line}] {message}\x1B[0m",
color_line = format_args!(
"\x1B[{}m",
colors_line.get_color(&record.level()).to_fg_str()
),
date = Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
target = record.target(),
level = record.level(),
message = message
))
})
// .level(level) -- it's very important to not do this, otherwise level never changes in runtime!!
.chain(std::io::stdout())
.chain(fern::DateBased::new(format!("{}.", log_file), "%Y-%m-%d"))
.apply()
}
and then I just run
set_log_level();
in my server main loop :)