captains-log
A light-weight customizable logger implementation for rust
Features
-
Allow customize log format and time format. Refer to
LogFormat -
Supports multiple types of sink stacking, each with its own log level.
-
LogConsole: Console output to stdout/stderr. -
LogRawFile: Support atomic appending from multi-process on linux -
LogBufFile : Write to log file with merged I/O and delay flush, and optional self-rotation.
-
Syslog: (feature
syslog)Write to local or remote syslog server, with timeout and auto reconnect.
-
LogRingFile: (feature
ringfile)For deadlock / race condition debugging, collect log to ring buffer in memory.
-
-
Log panic message by default.
-
Provide additional macros. For example: log_assert!(), logger_assert!() ...
-
Supports signal listening for log-rotate. Refer to
Builder::signal() -
Provides many preset recipes in [recipe] module for convenience.
-
Supports configured by environment.
-
Fine-grain module-level control.
Provides LogFilter to filter specified logs on-the-fly.
-
Provides LogFilterKV for API logging with additional key.
For example, you can set
req_idinLogFilterKV, and track the complete request handling procedure from log. -
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 with rstest
-
-
Provides a
parserto work on your log files.
Usage
Cargo.toml
[]
= { = "0.4", = ["std", "kv_unstable"] }
= "0.8"
lib.rs or main.rs:
// By default, reexport the macros from log crate
#[macro_use]
extern crate captains_log;
Fast setup example
You can refer to various preset recipe in recipe module.
The following is setup two log files for different log-level:
extern crate captains_log;
use recipe;
// You'll get /tmp/test.log with all logs, and /tmp/test.log.wf only with error logs.
let mut log_builder = split_error_file_logger;
// Builder::build() is equivalent of setup_log()
log_builder.build;
// non-error msg will only appear in /tmp/test.log
debug!;
info!;
// will appear in both /tmp/test.log and /tmp/test.log.wf
error!;
Buffered sink with log rotation (See the definition of Rotation):
extern crate captains_log;
use *;
// rotate when log file reaches 512M. Keep max 10 archiveed files, with recent 2 not compressed.
// All archived log is moved to "/tmp/rotation/old"
let rotation = by_size
.compress_exclude.archive_dir;
let _ = buffered_rotated_file_logger.build;
Configure by environment
There is a recipe env_logger() to configure a file logger or
console logger from env. As simple as:
use recipe;
let _ = env_logger.build;
If you want to custom more, setup your config with env_or() helper.
Customize format example
use *;
let debug_format = new;
let debug_file = new;
let config = default
.signal
.add_sink;
config.build;
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 Arc;
use *;
set_max_level;
let logger_io = new;
let logger_req = new;
logger_io.set_level;
logger_req.set_level;
logger_debug!;
logger_debug!;
logger_error!;
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 *;
let builder = raw_file_logger_custom;
builder.build.expect;
let logger = new;
logger_debug!;
logger_debug!;
logger_info!;
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 *;
Best practice with rstest
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 *;
use *;
use *;
// A show case that setup() fixture will be called twice, before each test.
// In order make logs available.
After running the test with:
cargo test -- --test-threads=1
/tmp/log_rstest.log will have this content:
[2025-06-21 00:39:37.091326][INFO][test_rstest.rs:11] >>> setup return () >>>
[2025-06-21 00:39:37.091462][INFO][test_rstest.rs:27] <<< test_rstest_bar (setup = ()) enter <<<
[2025-06-21 00:39:37.091493][INFO][test_rstest.rs:30] do something222
[2025-06-21 00:39:37.091515][INFO][test_rstest.rs:27] >>> test_rstest_bar return () >>>
[2025-06-21 00:39:37.091719][INFO][test_rstest.rs:11] <<< setup () enter <<<
[2025-06-21 00:39:37.091826][INFO][test_rstest.rs:11] >>> setup return () >>>
[2025-06-21 00:39:37.091844][INFO][test_rstest.rs:21] <<< test_rstest_foo (setup = (), file_size = 0) enter <<<
[2025-06-21 00:39:37.091857][INFO][test_rstest.rs:24] do something111
[2025-06-21 00:39:37.091868][INFO][test_rstest.rs:21] >>> test_rstest_foo return () >>>
[2025-06-21 00:39:37.092063][INFO][test_rstest.rs:11] <<< setup () enter <<<
[2025-06-21 00:39:37.092136][INFO][test_rstest.rs:11] >>> setup return () >>>
[2025-06-21 00:39:37.092151][INFO][test_rstest.rs:21] <<< test_rstest_foo (setup = (), file_size = 1) enter <<<
[2025-06-21 00:39:37.092163][INFO][test_rstest.rs:24] do something111
[2025-06-21 00:39:37.092173][INFO][test_rstest.rs:21] >>> test_rstest_foo return () >>>