Expand description
§captains-log
A light-weight logger for rust, implementation base on the crate log
.
§Features
-
Allow customize log format and time format. Refer to LogFormat.
-
Supports multiple types of sink stacking, each with its own log level.
-
Builder::console() : Console output to stdout/stderr.
-
Builder::raw_file() : Support atomic appending from multi-process on linux
-
-
Log panic message by default.
-
Provide additional macros
-
Supports signal listening for log-rotate. Refer to Builder::signal()
-
Provides many preset recipes in recipe module for convenience.
-
For test suits usage:
-
Allow dynamic reconfigure logger setting in different test function.
Refer to Unit test example.
-
Provides an attribute macro #[logfn] to wrap test function.
Refer to [Best practice][#best-practice-with-tests].
-
-
Provides a LogParser to work on your log files.
§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
- EnvVar
Default - Format
Record - 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
- LogRaw
File - Config for file sink that supports atomic append from multiprocess. For log rotation, you need system log-rotate service to notify with signal.
Enums§
- Console
Target - Level
- An enum representing the available verbosity levels of the logger.
- Level
Filter - An enum representing the available verbosity level filters of the logger.
Traits§
Functions§
Type Aliases§
Attribute Macros§
- logfn
- Provide an proc_macro
#[logfn]
which log the information: