Expand description

Here are some examples for the flexi_logger initialization.

Contents

Start minimally: Initialize, and write logs to stderr

Initialize by choosing one of three options to specify which log output you want to see, and call start() immediately:

  • Use Logger::try_with_env to provide the log specification in the environment variable RUST_LOG:

    Logger::try_with_env()?.start()?;
  • Use Logger::try_with_str to provide the log specification programmatically:

    Logger::try_with_str("info")?.start()?;
  • or use Logger::try_with_env_or_str to combine both options:

    Logger::try_with_env_or_str("info")?.start()?;

After that, you just use the log-macros from the log crate. Those log lines that match the log specification are then written to the default output channel (stderr).

Choose the log output channel

By default, logs are written to stderr. With one of Logger::log_to_stdout, Logger::log_to_file, Logger::log_to_writer, Logger::log_to_file_and_writer, or Logger::do_not_log, you can send the logs to other destinations, or write them not at all.

When writing to files or to a writer, you sometimes want to see some parts of the log additionally on the terminal; this can be achieved with Logger::duplicate_to_stderr or Logger::duplicate_to_stdout, which duplicate log messages to the terminal.

Logger::try_with_str("info")?
    .log_to_file(FileSpec::default())         // write logs to file
    .duplicate_to_stderr(Duplicate::Warn)     // print warnings and errors also to the console
    .start()?;

Choose the write mode

By default, every log line is directly written to the output, without buffering. This allows seeing new log lines in real time.

With Logger::write_mode you have some options to change this behavior, e.g.

  • with WriteMode::BufferAndFlush, or WriteMode::BufferAndFlushWith, you can reduce the program’s I/O overhead and thus increase overall performance, which can be relevant if logging is used heavily. In addition, to keep a short maximum wait time until a log line is visible in the output channel, an extra thread is created that flushes the buffers regularly.

    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let _logger = Logger::try_with_str("info")?
           .log_to_file(FileSpec::default())
           .write_mode(WriteMode::BufferAndFlush)
           .start()?;
        // ... do all your work ...
        Ok(())
    }
  • with WriteMode::Async or WriteMode::AsyncWith, logs are sent from your application threads through an unbounded channel to an output thread, which does the output (and the rotation and the cleanup, if applicable). Additionally the output is buffered, and a bounded message pool is used to reduce allocations, and flushing is used to avoid long delays. If duplication is used, the messages to stdout or stderr are written synchronously.

    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let _logger = Logger::try_with_str("info")?
           .log_to_file(FileSpec::default())
           .write_mode(WriteMode::Async)
           .start()?;
        // ... do all your work ...
        Ok(())
    }

Note that with all write modes except WriteMode::Direct (which is the default) you should keep the LoggerHandle alive up to the very end of your program, because it will, when its last instance is dropped (in case you use LoggerHandle::clone() you can have multiple instances), flush all writers to ensure that all buffered log lines are written before the program terminates, and then it calls their shutdown method.

Influence the location and name of the log file

By default, the log files are created in the current directory (where the program was started). With FileSpec:directory you can specify a concrete folder in which the files should be created.

Using FileSpec::discriminant you can add a discriminating infix to the log file name.

With FileSpec::suffix you can change the suffix that is used for the log files.

When writing to files, especially when they are in a distant folder, you may want to let the user know where the log file is.

Logger::print_message prints an info to stdout to which file the log is written.

Logger::create_symlink creates (on unix-systems only) a symbolic link at the specified path that points to the log file.

Logger::try_with_str("info")?
    .log_to_file(
        FileSpec::default()
            .directory("log_files")          // create files in folder ./log_files
            .basename("foo")
            .discriminant("Sample4711A")     // use infix in log file name
            .suffix("trc")                   // use suffix .trc instead of .log
    )
    .print_message()                         //
    .create_symlink("current_run")           // create a symbolic link to the current log file
    .start()?;

This example will print a message like “Log is written to ./log_files/foo_Sample4711A_2020-11-17_19-24-35.trc” and, on unix, create a symbolic link called current_run.

Specify the format for the log lines explicitly

With Logger::format you set the format for all used output channels of flexi_logger.

flexi_logger provides a couple of format functions, and you can also create and use your own, e.g. by copying and modifying one of the provided format functions.

Depending on the configuration, flexi_logger can write logs to multiple channels (stdout, stderr, files, or additional writers) at the same time. You can control the format for each output channel individually, using Logger::format_for_files, Logger::format_for_stderr, Logger::format_for_stdout, or Logger::format_for_writer.

As argument for these functions you can use one of the provided non-coloring format functions

or one of their coloring pendants

or your own method.

Adaptive Coloring

You can use coloring for stdout and/or stderr conditionally, such that colors

  • are used when the output goes to a tty,
  • are suppressed when you e.g. pipe the output to some other program.

You achieve that by providing one of the variants of AdaptiveFormat to the respective format method, e.g.

      flexi_logger::Logger::try_with_str("info")?
          .adaptive_format_for_stderr(AdaptiveFormat::Detailed);

Defaults

flexi_logger initializes by default equivalently to this:

      // ...
      .adaptive_format_for_stderr(AdaptiveFormat::Default)
      .adaptive_format_for_stdout(AdaptiveFormat::Default)
      .format_for_files(default_format)
      .format_for_writer(default_format)

Use a fixed log file, and truncate or append the file on each program start

With Logger::log_to_file and without rotation, flexi_logger uses by default files with a timestamp in the name, like foo_2020-11-16_08-37-44.log (for a program called foo), which are quite unique for each program start.

With FileSpec::suppress_timestamp you get a simple fixed filename, like foo.log.

In that case, a restart of the program will truncate an existing log file.

Use additionally Logger::append to append the logs of each new run to the existing file.

Logger::try_with_str("info")? // Write all error, warn, and info messages
    // use a simple filename without a timestamp
    .log_to_file(
        FileSpec::default().suppress_timestamp()
    )
    // do not truncate the log file when the program is restarted
    .append()
    .start()?;

Rotate the log file

With rotation, the logs are always written to a file with the infix rCURRENT, like e.g. foo_rCURRENT.log.

Logger::rotate takes three enum arguments that define its behavior:

  • Criterion

    • with Criterion::Age the rotation happens when the clock switches to a new day, hour, minute, or second
    • with Criterion::Size the rotation happens when the current log file exceeds the specified limit
    • with Criterion::AgeOrSize the rotation happens when either of the two limits is reached
  • Naming
    The current file is then renamed

    and a fresh rCURRENT file is created.

  • Cleanup defines if and how you avoid accumulating log files indefinitely:

    • with Cleanup::KeepLogFiles you specify the number of log files that should be retained; if there are more, the older ones are getting deleted
    • with Cleanup::KeepCompressedFiles you specify the number of log files that should be retained, and these are being compressed additionally
    • with Cleanup::KeepLogAndCompressedFiles you specify the number of log files that should be retained as is, and an additional number that are being compressed
    • with Cleanup::Never no cleanup is done, all files are retained.
Logger::try_with_str("info")?      // Write all error, warn, and info messages
    .log_to_file(
        FileSpec::default()
    )
    .rotate(                      // If the program runs long enough,
        Criterion::Age(Age::Day), // - create a new file every day
        Naming::Timestamps,       // - let the rotated files have a timestamp in their name
        Cleanup::KeepLogFiles(7), // - keep at most 7 log files
    )
    .start()?;

Reconfigure the log specification programmatically

This can be especially handy in debugging situations where you want to see log output only for a short instant.

Obtain the LoggerHandle

let mut logger = Logger::try_with_str("info").unwrap()
    // ... logger configuration ...
    .start()
    .unwrap();

and modify the effective log specification from within your code:

// ...
logger.parse_and_push_temp_spec("info, critical_mod = trace");
// ... critical calls ...
logger.pop_temp_spec();
// ... continue with the log spec you had before.

Reconfigure the log specification dynamically by editing a spec-file

If you start flexi_logger with a specfile,

Logger::try_with_str("info").unwrap()
    // ... logger configuration ...
   .start_with_specfile("./server/config/logspec.toml")
   .unwrap();

then you can change the log specification dynamically, while your program is running, by editing the specfile. This can be a great help e.g. if you want to get detailed log output for some requests to a long running server.

See Logger::start_with_specfile for more information.

Reconfigure the file log writer

When using Logger::log_to_file(), you can change most of the properties of the embedded FileLogWriter while the program is running using Logger::reset_flw.

Obtain the LoggerHandle when the program is started

use flexi_logger::{writers::FileLogWriter, Cleanup, Criterion, FileSpec, Naming};

let logger = flexi_logger::Logger::try_with_str("info")?
    .log_to_file(
        FileSpec::default()
            .basename("phase1")
            .directory("./log_files")
    )
    .start()?;

log::info!("start of phase 1");

and modify the file log writer later:

logger.reset_flw(
    &FileLogWriter::builder(
        FileSpec::default()
            .basename("phase2")
            .directory("./log_files")
    )
    .append()
    .rotate(
        Criterion::Size(1024 * 1000 * 1),
        Naming::Numbers,
        Cleanup::KeepLogFiles(3),
    ),
)?;

log::info!("start of phase 2");

External file rotators

If the log is written to files, flexi_logger decides, based on your configuration, to which file(s) the log is written, and expects that nobody else modifies these files. It offers quite some functionality to rotate, compress, and clean up log files.

Alternatively, tools like linux’ logrotate can be used to rotate, compress or remove log files. But renaming or deleting the current output file e.g. might not stop flexi_logger from writing to the now renamed file! See LoggerHandle::reopen_outputfile to understand how to cope with external rotators.

Miscellaneous

For the sake of completeness, we refer here to some more configuration methods. See their documentation for more details.

Logger::set_palette

Logger::cleanup_in_background_thread

Logger::use_windows_line_ending

Logger::add_writer