Crate captains_log

Source
Expand description

§captains-log

A light-weight logger for rust, implementation base on the crate log.

§Features

§Usage

Cargo.toml

[dependencies]
log = { version = "0.4", features = ["std", "kv_unstable"] }
captains_log = "0.5"

lib.rs or main.rs:


// By default, reexport the macros from log crate
#[macro_use]
extern crate captains_log;

§Fast setup examples

You can refer to various preset recipe in recipe module.

The following is setup two log files for different log-level:

#[macro_use]
extern crate captains_log;
use captains_log::recipe;

// You'll get /tmp/test.log with all logs, and /tmp/test.log.wf only with error logs.
let mut log_builder = recipe::split_error_file_logger("/tmp", "test", log::Level::Debug);
// Builder::build() is equivalent of setup_log().
log_builder.build();

// non-error msg will only appear in /tmp/test.log
debug!("Set a course to Sol system");
info!("Engage");

// will appear in both /tmp/test.log and /tmp/test.log.wf
error!("Engine over heat!");

§Configure by environment

There is a recipe env_logger() to configure a file logger or console logger from env. As simple as:

use captains_log::recipe;
let _ = recipe::env_logger("LOG_FILE", "LOG_LEVEL").build();

§Customize format example

use captains_log::*;

fn format_f(r: FormatRecord) -> String {
    let time = r.time();
    let level = r.level();
    let file = r.file();
    let line = r.line();
    let msg = r.msg();
    format!("{time}|{level}|{file}:{line}|{msg}\n").to_string()
}
let debug_format = LogFormat::new(
    "%Y%m%d %H:%M:%S%.6f",
    format_f,
);
let debug_file = LogRawFile::new(
    "/tmp", "test.log", log::Level::Trace, debug_format);
let config = Builder::default()
    .signal(signal_hook::consts::SIGINT)
    .raw_file(debug_file);
config.build();

If you want to custom more, setup your config with env_or helper.

§Fine-grain module-level control

Place LogFilter in Arc and share among coroutines. Log level can be changed on-the-fly.

There’re a set of macro “logger_XXX” to work with LogFilter.

use std::sync::Arc;
use captains_log::*;
log::set_max_level(log::LevelFilter::Debug);
let logger_io = Arc::new(LogFilter::new());
let logger_req = Arc::new(LogFilter::new());
logger_io.set_level(log::Level::Error);
logger_req.set_level(log::Level::Debug);
logger_debug!(logger_req, "Begin handle req ...");
logger_debug!(logger_io, "Issue io to disk ...");
logger_error!(logger_req, "Req invalid ...");

§API-level log handling

Request log can be track by customizable key (for example, “req_id”), which kept in LogFilterKV, and LogFilterKV is inherit from LogFilter. You need macro “logger_XXX” to work with it.

use captains_log::*;
fn debug_format_req_id_f(r: FormatRecord) -> String {
    let time = r.time();
    let level = r.level();
    let file = r.file();
    let line = r.line();
    let msg = r.msg();
    let req_id = r.key("req_id");
    format!("[{time}][{level}][{file}:{line}] {msg}{req_id}\n").to_string()
}
let builder = recipe::raw_file_logger_custom("/tmp/log_filter.log", log::Level::Debug,
    recipe::DEFAULT_TIME, debug_format_req_id_f);
builder.build().expect("setup_log");
let logger = LogFilterKV::new("req_id", format!("{:016x}", 123).to_string());
info!("API service started");
logger_debug!(logger, "Req / received");
logger_debug!(logger, "header xxx");
logger_info!(logger, "Req / 200 complete");

The log will be:

[2025-06-11 14:33:08.089090][DEBUG][request.rs:67] API service started
[2025-06-11 14:33:10.099092][DEBUG][request.rs:67] Req / received (000000000000007b)
[2025-06-11 14:33:10.099232][WARN][request.rs:68] header xxx (000000000000007b)
[2025-06-11 14:33:11.009092][DEBUG][request.rs:67] Req / 200 complete (000000000000007b)

§Unit test example

To setup different log config on different tests.

Make sure that you call Builder::test() in test cases. which enable dynamic log config and disable signal_hook.


use captains_log::*;

#[test]
fn test1() {
    recipe::raw_file_logger(
        "/tmp/test1.log", Level::Debug).test().build();
    info!("doing test1");
}

#[test]
fn test2() {
    recipe::raw_file_logger(
        "/tmp/test2.log", Level::Debug).test().build();
    info!("doing test2");
}

§Best practice with tests

We provides proc macro logfn, the following example shows how to combine with rstest.

  • When you have large test suit, you want to know which logs belong to which test case.

  • Sometimes your test crashes, you want to find the responsible test case.

  • The time spend in each test.


use rstest::*;
use captains_log::*;

// A show case that setup() fixture will be called twice, before each test.
// In order make logs available.
#[fixture]
fn setup() {
    let builder = recipe::raw_file_logger("/tmp/log_rstest.log", log::Level::Debug).test();
    builder.build().expect("setup_log");
}

#[logfn]
#[rstest(file_size, case(0), case(1))]
fn test_rstest_foo(setup: (), file_size: usize) {
    info!("do something111");
}

#[logfn]
#[rstest]
fn test_rstest_bar(setup: ()) {
    info!("do something222");
}

// NOTE rstest must be at the bottom to make fixture effective
#[tokio::test]
#[logfn]
#[rstest]
async fn test_rstest_async(setup: ()) {
    info!("something333")
}

Notice: the order when combine tokio::test with rstest, #[rstest] attribute must be at the bottom to make setup fixture effective.

After running the test with:

cargo test – –test-threads=1

/tmp/log_rstest.log will have this content:

[2025-07-13 18:22:39.159642][INFO][test_rstest.rs:33] <<< test_rstest_async (setup = ()) enter <<<
[2025-07-13 18:22:39.160255][INFO][test_rstest.rs:37] something333
[2025-07-13 18:22:39.160567][INFO][test_rstest.rs:33] >>> test_rstest_async return () in 564.047µs >>>
[2025-07-13 18:22:39.161299][INFO][test_rstest.rs:26] <<< test_rstest_bar (setup = ()) enter <<<
[2025-07-13 18:22:39.161643][INFO][test_rstest.rs:29] do something222
[2025-07-13 18:22:39.161703][INFO][test_rstest.rs:26] >>> test_rstest_bar return () in 62.681µs >>>
[2025-07-13 18:22:39.162169][INFO][test_rstest.rs:20] <<< test_rstest_foo (setup = (), file_size = 0) enter <<<
[2025-07-13 18:22:39.162525][INFO][test_rstest.rs:23] do something111
[2025-07-13 18:22:39.162600][INFO][test_rstest.rs:20] >>> test_rstest_foo return () in 78.457µs >>>
[2025-07-13 18:22:39.163050][INFO][test_rstest.rs:20] <<< test_rstest_foo (setup = (), file_size = 1) enter <<<
[2025-07-13 18:22:39.163320][INFO][test_rstest.rs:23] do something111
[2025-07-13 18:22:39.163377][INFO][test_rstest.rs:20] >>> test_rstest_foo return () in 58.747µs >>>

Modules§

macros
parser
recipe
The recipe module contains some prelude functions that construct a Builder for convenience use. Please click to the description and source for reference.

Macros§

debug
Logs a message at the debug level.
error
Logs a message at the error level.
info
Logs a message at the info level.
log_assert
Will log and panic when condition not met.
log_assert_eq
Will log and panic when condition not met.
log_debug_assert
On Debug build, will log and panic when condition not met. Skip the check on release build.
log_debug_assert_eq
On Debug build, will log and panic when condition not met. Skip the check on release build.
log_eprintln
log and println to stderr.
log_println
log and println to stdout.
logger_assert
Will log with log_filter and panic when condition not met.
logger_assert_eq
Will log with log_filter and panic when condition not met.
logger_debug
Similar to debug!(), but the first argument is LogFilter or LogFilterKV
logger_debug_assert
On debug build, will log with log_filter and panic when condition not met. Skip the check on release build.
logger_debug_assert_eq
On debug build, will log with log_filter and panic when condition not met. Skip the check on release build.
logger_error
Similar to error!(), but the first argument is LogFilter or LogFilterKV.
logger_info
Similar to info!(), but the first argument is LogFilter or LogFilterKV.
logger_trace
Similar to trace!(), but the first argument is LogFilter or LogFilterKV
logger_warn
Similar to warn!(), but the first argument is LogFilter or LogFilterKV.
trace
Logs a message at the trace level.
warn
Logs a message at the warn level.

Structs§

Builder
Global config to setup logger See crate::recipe for usage
EnvVarDefault
FormatRecord
LogConsole
LogFilter
A LogFilter supports concurrent control the log level. Use in combine with macros logger_XXX
LogFilterKV
LogFilter that carries one additional value into log format
LogFormat
Custom formatter which adds into a log sink
LogRawFile
Config for file sink that supports atomic append from multiprocess. For log rotation, you need system log-rotate service to notify with signal.

Enums§

ConsoleTarget
Level
An enum representing the available verbosity levels of the logger.
LevelFilter
An enum representing the available verbosity level filters of the logger.

Traits§

SinkConfigTrait

Functions§

env_or
To config some logger setting with env.
setup_log
Initialize global logger from Builder

Type Aliases§

FormatFunc

Attribute Macros§

logfn
Provide an proc_macro #[logfn] which log the information: