Shutting log4rs down cleanly?

For a program, I'm using log4rs to write logs to a file. But I have a problem when running the integration tests for this program -- which we run one at a time. For the first integration test, we run the program and it stops fine. For the second integration test, we start the same binary, but when setting up log4rs with init_config, get the error:

`Result::unwrap()` on an `Err` value: SetLoggerError(())

If I add a few second sleep before setting up the logger, the error disappears. But would prefer to find a more elegant/idiomatic solution.

Is there a way to perhaps wait to shut down the logger cleanly before exiting the binary the first time we run it?

Thanks, appreciate any help.

not familiar with log4rs, but the document of SetLoggerError says:

The type returned by set_logger if set_logger has already been called.

it seems a race condition. how are your tests organized, are you running multiple tests concurrently and each of the tests wants to set their own logger?

2 Likes

The function you mention is this, init_config in log4rs::config - Rust, right?

The problem in my opinion is : your tests shouldn't initialize logging, at all. Not by themselves.

In java LogManager.getLogger() miraculously picks a configuration if the logging system is not initialized yet using common locations for a file.

But this seems not to exist in log4rs yet.

So... log initialization should happen once in the lifetime of the entire application. It must be the very first method of the fn main inside your program. If you are using a test suite, then log initialization should be wrapped around a lazy singleton pattern, so you only initialize it once at the very start of the whole test suite.

Detail: I'm a noob in Rust. So I don't know how to do it exactly. It would certainly involve a static global variable that would contain a flag, and a function for "initialize_logging_if_not_done_so_yet"

3 Likes

You can do it with std::sync::Once. I use tracing instead of log4rs, but here an example:

use std::sync::Once;

static INIT: Once = Once::new();

pub fn init_logger() {
  INIT.call_once(|| {
    tracing_subscriber::fmt()
      .with_ansi(false)
      .without_time()
      .with_env_filter(
        tracing_subscriber::EnvFilter::from_default_env(),
      )
      .init();
  });
}

#[test]
fn test1() {
    init_logger();

    // ...
}

#[test]
fn test2() {
    init_logger();

    // ...
}
5 Likes

Did the trick, thank you!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.