Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way to change log level in runtime? #76

Open
dmilith opened this issue Feb 3, 2021 · 4 comments
Open

A way to change log level in runtime? #76

dmilith opened this issue Feb 3, 2021 · 4 comments

Comments

@dmilith
Copy link

dmilith commented Feb 3, 2021

I wish to change the log level in runtime without restarting my long-running service. Is there a way of doing so?

@daboross
Copy link
Owner

daboross commented Feb 9, 2021

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.

@dmilith
Copy link
Author

dmilith commented Feb 12, 2021

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!

@daboross
Copy link
Owner

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.

@dmilith
Copy link
Author

dmilith commented Feb 12, 2021

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants