captains_log/
lib.rs

1//! # captains-log
2//!
3//! A light-weight logger for rust, implementation base on the crate `log`.
4//!
5//! ## Features
6//!
7//! * Allow customize log format and time format. Refer to [LogFormat].
8//!
9//! * Supports multiple types of sink stacking, each with its own log level.
10//!
11//!     + [Builder::console()] :   Console output to stdout/stderr.
12//!
13//!     + [Builder::raw_file()] :  Support atomic appending from multi-process on linux
14//!
15//!     + [Builder::buf_file()] :  Write to log file with merged I/O and delay flush, and optional self-rotation.
16//!
17//! * Log panic message by default.
18//!
19//! * Provide additional [macros](#macros)
20//!
21//! * Supports signal listening for log-rotate. Refer to [Builder::signal()]
22//!
23//! * Provides many preset recipes in [recipe] module for convenience.
24//!
25//! * [Supports configure by environment](#configure-by-environment)
26//!
27//! * [Fine-grain module-level control](#fine-grain-module-level-control)
28//!
29//! * [API-level log handling](#api-level-log-handling)
30//!
31//! * For test suits usage:
32//!
33//!     + Allow dynamic reconfigure logger setting in different test function.
34//!
35//!       Refer to [Unit test example](#unit-test-example).
36//!
37//!     + Provides an attribute macro #\[logfn\] to wrap test function.
38//!
39//!       Refer to [Best practice with rstest](#best-practice-with-rstest).
40//!
41//! * Provides a [LogParser](crate::parser::LogParser) to work on your log files.
42//!
43//! ## Usage
44//!
45//! Cargo.toml
46//!
47//! ``` toml
48//! [dependencies]
49//! log = { version = "0.4", features = ["std", "kv_unstable"] }
50//! captains_log = "0.6"
51//! ```
52//!
53//! lib.rs or main.rs:
54//!
55//! ``` rust
56//!
57//! // By default, reexport the macros from log crate
58//! #[macro_use]
59//! extern crate captains_log;
60//!
61//! ```
62//!
63//! ## Fast setup examples
64//!
65//! You can refer to various preset recipe in [recipe] module.
66//!
67//! The following is setup two log files for different log-level:
68//!
69//! ``` rust
70//! #[macro_use]
71//! extern crate captains_log;
72//! use captains_log::recipe;
73//!
74//! // You'll get /tmp/test.log with all logs, and /tmp/test.log.wf only with error logs.
75//! let log_builder = recipe::split_error_file_logger("/tmp", "test", log::Level::Debug);
76//! // Builder::build() is equivalent of setup_log().
77//! log_builder.build();
78//! // non-error msg will only appear in /tmp/test.log
79//! debug!("Set a course to Sol system");
80//! info!("Engage");
81//! // will appear in both /tmp/test.log and /tmp/test.log.wf
82//! error!("Engine over heat!");
83//! ```
84//!
85//! Buffered sink with log rotation (See the definition of [Rotation]):
86//!
87//! ``` rust
88//! #[macro_use]
89//! extern crate captains_log;
90//! use captains_log::*;
91//! // rotate when log file reaches 512M. Keep max 10 archiveed files, with recent 2 not compressed.
92//! // All archived log is moved to "/tmp/rotation/old"
93//! let rotation = Rotation::by_size(512 * 1024 * 1024, Some(10))
94//!     .compress_exclude(2).archive_dir("/tmp/rotation/old");
95//! let _ = recipe::buffered_rotated_file_logger("/tmp/rotation.log", Level::Debug, rotation).build();
96//! ```
97//!
98//! ## Configure by environment
99//!
100//! There is a recipe [env_logger()](crate::recipe::env_logger()) to configure a file logger or
101//! console logger from env. As simple as:
102//!
103//! ``` rust
104//! use captains_log::recipe;
105//! let _ = recipe::env_logger("LOG_FILE", "LOG_LEVEL").build();
106//! ```
107//!
108//! If you want to custom more, setup your config with [env_or] helper.
109//!
110//! ## Customize format example
111//!
112//! ``` rust
113//! use captains_log::*;
114//!
115//! fn format_f(r: FormatRecord) -> String {
116//!     let time = r.time();
117//!     let level = r.level();
118//!     let file = r.file();
119//!     let line = r.line();
120//!     let msg = r.msg();
121//!     format!("{time}|{level}|{file}:{line}|{msg}\n").to_string()
122//! }
123//! let debug_format = LogFormat::new(
124//!     "%Y%m%d %H:%M:%S%.6f",
125//!     format_f,
126//! );
127//! let debug_file = LogRawFile::new(
128//!     "/tmp", "test.log", log::Level::Trace, debug_format);
129//! let config = Builder::default()
130//!     .signal(signal_hook::consts::SIGINT)
131//!     .raw_file(debug_file);
132//! config.build();
133//! ```
134//!
135//! ## Fine-grain module-level control
136//!
137//! Place [LogFilter] in Arc and share among coroutines.
138//! Log level can be changed on-the-fly.
139//!
140//! There're a set of macro "logger_XXX" to work with `LogFilter`.
141//!
142//! ``` rust
143//! use std::sync::Arc;
144//! use captains_log::*;
145//! log::set_max_level(log::LevelFilter::Debug);
146//! let logger_io = Arc::new(LogFilter::new());
147//! let logger_req = Arc::new(LogFilter::new());
148//! logger_io.set_level(log::Level::Error);
149//! logger_req.set_level(log::Level::Debug);
150//! logger_debug!(logger_req, "Begin handle req ...");
151//! logger_debug!(logger_io, "Issue io to disk ...");
152//! logger_error!(logger_req, "Req invalid ...");
153//! ```
154//!
155//!
156//! ## API-level log handling
157//!
158//! Request log can be track by customizable key (for example, "req_id"), which kept in [LogFilterKV],
159//! and `LogFilterKV` is inherit from `LogFilter`.
160//! You need macro "logger_XXX" to work with it.
161//!
162//! ``` rust
163//! use captains_log::*;
164//! fn debug_format_req_id_f(r: FormatRecord) -> String {
165//!     let time = r.time();
166//!     let level = r.level();
167//!     let file = r.file();
168//!     let line = r.line();
169//!     let msg = r.msg();
170//!     let req_id = r.key("req_id");
171//!     format!("[{time}][{level}][{file}:{line}] {msg}{req_id}\n").to_string()
172//! }
173//! let builder = recipe::raw_file_logger_custom("/tmp/log_filter.log", log::Level::Debug,
174//!     recipe::DEFAULT_TIME, debug_format_req_id_f);
175//! builder.build().expect("setup_log");
176//! let logger = LogFilterKV::new("req_id", format!("{:016x}", 123).to_string());
177//! info!("API service started");
178//! logger_debug!(logger, "Req / received");
179//! logger_debug!(logger, "header xxx");
180//! logger_info!(logger, "Req / 200 complete");
181//! ```
182//!
183//! The log will be:
184//!
185//! ``` text
186//! [2025-06-11 14:33:08.089090][DEBUG][request.rs:67] API service started
187//! [2025-06-11 14:33:10.099092][DEBUG][request.rs:67] Req / received (000000000000007b)
188//! [2025-06-11 14:33:10.099232][WARN][request.rs:68] header xxx (000000000000007b)
189//! [2025-06-11 14:33:11.009092][DEBUG][request.rs:67] Req / 200 complete (000000000000007b)
190//! ```
191//!
192//!
193//! ## Unit test example
194//!
195//! To setup different log config on different tests.
196//!
197//! **Make sure that you call [Builder::test()]** in test cases.
198//! which enable dynamic log config and disable signal_hook.
199//!
200//! ```rust
201//! use captains_log::*;
202//!
203//! #[test]
204//! fn test1() {
205//!     recipe::raw_file_logger(
206//!         "/tmp/test1.log", Level::Debug).test().build();
207//!     info!("doing test1");
208//! }
209//!
210//! #[test]
211//! fn test2() {
212//!     recipe::raw_file_logger(
213//!         "/tmp/test2.log", Level::Debug).test().build();
214//!     info!("doing test2");
215//! }
216//! ```
217//!
218//! ## Best practice with rstest
219//!
220//! We provides proc macro [logfn], the following example shows how to combine with rstest.
221//!
222//! * When you have large test suit, you want to know which logs belong to which test case.
223//!
224//! * Sometimes your test crashes, you want to find the responsible test case.
225//!
226//! * The time spend in each test.
227//!
228//! ``` rust
229//!
230//! use rstest::*;
231//! use captains_log::*;
232//!
233//! // A show case that setup() fixture will be called twice, before each test.
234//! // In order make logs available.
235//! #[fixture]
236//! fn setup() {
237//!     let builder = recipe::raw_file_logger("/tmp/log_rstest.log", log::Level::Debug).test();
238//!     builder.build().expect("setup_log");
239//! }
240//!
241//! #[logfn]
242//! #[rstest(file_size, case(0), case(1))]
243//! fn test_rstest_foo(setup: (), file_size: usize) {
244//!     info!("do something111");
245//! }
246//!
247//! #[logfn]
248//! #[rstest]
249//! fn test_rstest_bar(setup: ()) {
250//!     info!("do something222");
251//! }
252//!
253//! // NOTE rstest must be at the bottom to make fixture effective
254//! #[tokio::test]
255//! #[logfn]
256//! #[rstest]
257//! async fn test_rstest_async(setup: ()) {
258//!     info!("something333")
259//! }
260//! ```
261//!
262//! **Notice:** the order when combine tokio::test with rstest,
263//! `#[rstest]` attribute must be at the bottom to make setup fixture effective.
264//!
265//! After running the test with:
266//!
267//! cargo test -- --test-threads=1
268//!
269//! /tmp/log_rstest.log will have this content:
270//!
271//! ``` text
272//! [2025-07-13 18:22:39.159642][INFO][test_rstest.rs:33] <<< test_rstest_async (setup = ()) enter <<<
273//! [2025-07-13 18:22:39.160255][INFO][test_rstest.rs:37] something333
274//! [2025-07-13 18:22:39.160567][INFO][test_rstest.rs:33] >>> test_rstest_async return () in 564.047µs >>>
275//! [2025-07-13 18:22:39.161299][INFO][test_rstest.rs:26] <<< test_rstest_bar (setup = ()) enter <<<
276//! [2025-07-13 18:22:39.161643][INFO][test_rstest.rs:29] do something222
277//! [2025-07-13 18:22:39.161703][INFO][test_rstest.rs:26] >>> test_rstest_bar return () in 62.681µs >>>
278//! [2025-07-13 18:22:39.162169][INFO][test_rstest.rs:20] <<< test_rstest_foo (setup = (), file_size = 0) enter <<<
279//! [2025-07-13 18:22:39.162525][INFO][test_rstest.rs:23] do something111
280//! [2025-07-13 18:22:39.162600][INFO][test_rstest.rs:20] >>> test_rstest_foo return () in 78.457µs >>>
281//! [2025-07-13 18:22:39.163050][INFO][test_rstest.rs:20] <<< test_rstest_foo (setup = (), file_size = 1) enter <<<
282//! [2025-07-13 18:22:39.163320][INFO][test_rstest.rs:23] do something111
283//! [2025-07-13 18:22:39.163377][INFO][test_rstest.rs:20] >>> test_rstest_foo return () in 58.747µs >>>
284//! ```
285
286extern crate captains_log_helper;
287extern crate log;
288extern crate signal_hook;
289
290#[macro_use]
291extern crate enum_dispatch;
292
293mod buf_file_impl;
294mod config;
295mod console_impl;
296mod env;
297mod file_impl;
298mod formatter;
299mod log_impl;
300mod rotation;
301mod time;
302
303pub mod macros;
304pub mod parser;
305pub mod recipe;
306
307mod log_filter;
308
309pub use self::buf_file_impl::*;
310pub use self::console_impl::*;
311pub use self::env::*;
312pub use self::file_impl::*;
313pub use self::rotation::*;
314pub use self::{config::*, formatter::FormatRecord, log_filter::*, log_impl::setup_log};
315pub use captains_log_helper::logfn;
316
317pub use log::{Level, LevelFilter};
318pub use log::{debug, error, info, trace, warn};
319
320#[cfg(test)]
321mod tests;