flexi_logger Documentation

repository·main·Indexed 19 days ago

https://github.com/emabee/flexi_logger

A flexible and high-performance logging implementation for Rust that integrates with the standard `log` crate. Version 0.31.9 supports various output channels (file, stdout, stderr, writers), advanced rotation, buffering, and asynchronous logging modes. It features a sophisticated threading model for flushing and I/O, dynamic log level reconfiguration via `LoggerHandle` or specfiles, and customizable log file management including rotation criteria, naming conventions, and cleanup policies.

Tokens
18.7K
Snippets
52
Records
72
Agent score
63%

What's inside flexi_logger

  1. Configure the flexi_logger-flusher thread

    main

    The flexi_logger-flusher is a background thread responsible for flushing the primary writer and other writers at a cadence defined by flush_interval.

    Trigger Conditions: It is automatically started during Logger::build if:

    1. Logger.flush_interval is not empty.
    2. The chosen WriteMode is NOT one of the following: WriteMode::Direct, SupportCapture, BufferDontFlush, or BufferDontFlushWith(_).

    Technical Details:

    • Stack Size: 1024 bytes.
  2. Understand the flexi_logger threading model and initialization

    main

    The flexi_logger architecture uses specialized background threads to handle log flushing and file management, depending on your chosen LogTarget and WriteMode.

    Key threading behaviors include:

    • Flushing: A dedicated flexi_logger-flusher thread is started if a flush_interval is provided and specific non-direct write modes are used.
    • Async Writing: When using WriteMode::Async or WriteMode::AsyncWith, specialized writer threads (flexi_logger-std-writer or flexi_logger-file-writer) are spawned to handle the actual I/O, which can be pinned to specific CPU cores.
    • Cleanup: A flexi_logger-file-cleanup thread can be spawned to manage log file rotation and compression.
  3. Rotate log files using Criterion, Naming, and Cleanup

    main

    To prevent log files from growing indefinitely, use Logger::rotate. This method requires three parameters:

    1. Criterion: Defines when rotation happens.

      • Criterion::Age(Age): Rotates based on time (e.g., Age::Day, Age::Hour).
      • Criterion::Size(usize): Rotates when the file exceeds a certain size in bytes.
      • Criterion::AgeOrSize(...): Rotates when either limit is reached.
    2. Naming: Defines how the old file is renamed.

      • Naming::Timestamps: Renames to include a timestamp (e.g., foo_r2020-11-16_08-56-52.log).
      • Naming::Numbers: Renames using a sequence of numbers (e.g., foo_r00000.log).
    3. Cleanup: Defines how many files to keep.

      • Cleanup::KeepLogFiles(n): Retains n log files, deleting older ones.
      • Cleanup::KeepCompressedFiles(n): Retains n files and compresses them.
      • Cleanup::KeepLogAndCompressedFiles(n, m): Retains n uncompressed and m compressed files.
      • Cleanup::Never: Keeps all files.

    When rotation occurs, the active file is always named with the infix rCURRENT (e.g., foo_rCURRENT.log).

    use flexi_logger::{Age, Cleanup, Criterion, FileSpec, Logger, Naming};
    use log::{error, warn, info};
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        Logger::try_with_str("info")?
            .log_to_file(
                FileSpec::default()
            )
            .rotate(
                Criterion::Age(Age::Day), // create a new file every day
                Naming::Timestamps,       // use timestamps in rotated filenames
                Cleanup::KeepLogFiles(7), // keep at most 7 log files
            )
            .start()?;
    
        error!("This is an error message");
        Ok(())
    }
  4. Configure write modes for performance and testing

    main

    You can change how logs are written using write_mode.

    Important: For all modes except WriteMode::Direct and WriteMode::SupportCapture, you must keep the LoggerHandle alive until the end of your program to ensure buffers are flushed and writers are shut down correctly.

    Available modes:

    • WriteMode::Direct (Default): No buffering, real-time output.
    • WriteMode::BufferAndFlush / WriteMode::BufferAndFlushWith: Reduces I/O overhead by buffering logs and using a background thread to flush them regularly.
    • WriteMode::Async / WriteMode::AsyncWith: Sends logs through an unbounded channel to an output thread. This is highly performant as it offloads rotation and cleanup to a separate thread. (Requires async feature).
    • WriteMode::SupportCapture: Allows cargo test to capture log output and only print it for failing tests.
    // Example: Using Buffered writing to reduce I/O overhead
    use flexi_logger::{WriteMode, FileSpec, Logger};
    
    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()?;
        Ok(())
    }
  5. Use a fixed log file with truncation or appending

    main

    By default, log_to_file creates unique filenames containing a timestamp. To use a single fixed filename (e.g., foo.log), use FileSpec::suppress_timestamp().

    Note that using a fixed filename without further configuration will cause the log file to be truncated (cleared) every time the program starts. To prevent this and instead append new logs to the existing file, call .append() on the Logger builder.

    use flexi_logger::{FileSpec, Logger};
    use log::{error, warn, info};
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        Logger::try_with_str("info")?
            // 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()?;
    
        error!("This is an error message");
        warn!("This is a warning");
        info!("This is an info message");
        Ok(())
    }
  6. Start minimally: Initialize and write logs to stderr

    main

    To quickly set up flexi_logger, you can initialize it using one of three methods. Once started, you can use standard log crate macros (e.g., info!, error!). By default, logs are written to stderr.

    // Option 1: Use the RUST_LOG environment variable
    Logger::try_with_env()?.start()?;
    
    // Option 2: Provide the log specification programmatically
    Logger::try_with_str("info")?.start()?;
    
    // Option 3: Combine both (env has precedence over the string parameter)
    Logger::try_with_env_or_str("info")?.start()?;
    
    // Shorthand for the combined option
    flexi_logger::init();
  7. Install flexi_logger and log

    main

    To use flexi_logger, you must also include the log crate in your Cargo.toml dependencies, as flexi_logger implements the standard Rust logging facade. Use the log macros (e.g., info!, warn!) to emit log lines from your application code.

    [dependencies]
    flexi_logger = "0.31"
    log = "0.4"
  8. Reconfigure log levels dynamically via a specfile

    main

    If you enable the specfile feature, you can use Logger::start_with_specfile(path) to load log configurations from a file (e.g., a .toml file).

    Because flexi_logger monitors this file, you can change the log levels dynamically while the program is running by simply editing the file on disk. This is ideal for long-running servers where you need to increase verbosity without restarting the process.

    use flexi_logger::Logger;
    
    let logger = Logger::try_with_str("info").unwrap();
    
    #[cfg(feature = "specfile")]
    let logger = logger.start_with_specfile("./server/config/logspec.toml").unwrap();
    
    // If the 'specfile' feature is enabled, editing ./server/config/logspec.toml 
    // will update the running application's log levels immediately.
  9. Manage the Logger lifecycle with `LoggerHandle`

    main

    When you call .start() or .start_with_specfile(), you receive a LoggerHandle.

    CRITICAL: You must keep the LoggerHandle alive for the entire duration of your program. Dropping the LoggerHandle triggers a flush and shuts down all writers (including files). If dropped too early, subsequent log calls will fail to write.

    The LoggerHandle also provides the capability to update the log specification programmatically at runtime.

  10. Manage logs at runtime with LoggerHandle

    main
    When you call .start() on a Logger builder, it returns a LoggerHandle. This handle can be used to interact with the logger while the program is running, such as changing the log specification or selecting different log files.
  11. Manage logger configuration with LoggerHandle

    main

    A LoggerHandle is returned from Logger::start() and Logger::start_with_specfile(). It allows you to reconfigure the logger at runtime, such as changing log levels or swapping log specifications.

    CRITICAL: If you are logging to a file, using a buffering/asynchronous WriteMode, or using custom writers, you must keep the LoggerHandle alive until the end of your program. When the LoggerHandle is dropped, it shuts down the logger. For trivial configurations (e.g., logging only to stdout), you can safely ignore the return value.

    use flexi_logger::{FileSpec, Logger};
    use std::error::Error;
    
    fn main() -> Result<(), Box<dyn Error>> {
        // Keep '_logger' alive for the duration of the program
        let _logger = Logger::try_with_str("info")?
            .log_to_file(FileSpec::default())
            .start()?;
    
        // do work
        Ok(())
    }
  12. Configure log writing modes with `WriteMode`

    main

    The WriteMode enum determines how log output is written (synchronously vs asynchronously) and how I/O is buffered or flushed. This setting is used via Logger::write_mode.

    Key Considerations:

    • Buffering: Reduces I/O overhead and increases performance, but can delay the appearance of log lines. For low-frequency logging, regular flushing is recommended.
    • Lifecycle: For all modes except Direct, you must keep the LoggerHandle alive until the end of your program. When the LoggerHandle is dropped, all buffered log lines are automatically flushed.
    • Performance: WriteMode::Direct is the slowest option because it lacks buffering. WriteMode::Async is generally faster but may cause log lines to appear with a delay during high-output phases.
    • Flushing: Flushing uses an extra thread with a minimal stack.