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//! * Log panic message by default.
16//!
17//! * Supports signal listening for log-rotate. Refer to [Builder::signal()]
18//!
19//! * [Fine-grain module-level control](#fine-grain-module-level-control)
20//!
21//! * [API-level log handling](#api-level-log-handling)
22//!
23//! * For test suits usage:
24//!
25//!     + Allow dynamic reconfigure logger setting in different test function.
26//!
27//!       Refer to [Unit test example](#unit-test-example).
28//!
29//!     + Provides an attribute macro #\[logfn\] to wrap test function.
30//!
31//!       Refer to [Best practice][crate::logfn].
32//!
33//!
34//! * Provides a [LogParser](crate::parser::LogParser) to work on your log files.
35//!
36//! ## Usage
37//!
38//! Cargo.toml
39//!
40//! ``` toml
41//! [dependencies]
42//! log = { version = "0.4", features = ["std", "kv_unstable"] }
43//! captains_log = "0.4"
44//! ```
45//!
46//! lib.rs or main.rs:
47//! ```
48//! #[macro_use]
49//! extern crate captains_log;
50//! #[macro_use]
51//! extern crate log;
52//! ```
53//!
54//! ## Production example
55//!
56//! Refer to [recipe] module for more example.
57//!
58//! ```rust
59//! use log::{debug, info, error};
60//! use captains_log::recipe::split_error_file_logger;
61//!
62//! // You'll get /tmp/test.log with all logs, and /tmp/test.log.wf only with error logs.
63//! let mut log_builder = split_error_file_logger("/tmp", "test", log::Level::Debug);
64//! // Builder::build() is equivalent of setup_log().
65//! log_builder.build();
66//!
67//! // non-error msg will only appear in /tmp/test.log
68//! debug!("Set a course to Sol system");
69//! info!("Engage");
70//!
71//! // will appear in both /tmp/test.log and /tmp/test.log.wf
72//! error!("Engine over heat!");
73//! ```
74//!
75//! ## Customize format example
76//!
77//! ``` rust
78//! extern crate signal_hook;
79//! extern crate chrono;
80//! use captains_log::*;
81//!
82//! fn format_f(r: FormatRecord) -> String {
83//!     let time = r.time();
84//!     let level = r.level();
85//!     let file = r.file();
86//!     let line = r.line();
87//!     let msg = r.msg();
88//!     format!("{time}|{level}|{file}:{line}|{msg}\n").to_string()
89//! }
90//! let debug_format = LogFormat::new(
91//!     "%Y%m%d %H:%M:%S%.6f",
92//!     format_f,
93//! );
94//! let debug_file = LogRawFile::new(
95//!     "/tmp", "test.log", log::Level::Trace, debug_format);
96//! let config = Builder::default()
97//!     .signal(signal_hook::consts::SIGINT)
98//!     .raw_file(debug_file);
99//! config.build();
100//! ```
101//!
102//! ## Fine-grain module-level control
103//!
104//! Place [LogFilter] in Arc and share among coroutines.
105//! Log level can be changed on-the-fly.
106//!
107//! ``` rust
108//! use std::sync::Arc;
109//! use captains_log::*;
110//! log::set_max_level(log::LevelFilter::Debug);
111//! let logger_io = Arc::new(LogFilter::new());
112//! let logger_req = Arc::new(LogFilter::new());
113//! logger_io.set_level(log::Level::Error);
114//! logger_req.set_level(log::Level::Debug);
115//! logger_debug!(logger_req, "Begin handle req ...");
116//! logger_debug!(logger_io, "Issue io to disk ...");
117//! logger_error!(logger_req, "Req invalid ...");
118//! ```
119//!
120//! ## API-level log handling
121//!
122//! Request log can be track by custom key `req_id`, which kept in [LogFilterKV].
123//!
124//! ``` rust
125//! use captains_log::*;
126//! use log::*;
127//! fn debug_format_req_id_f(r: FormatRecord) -> String {
128//!     let time = r.time();
129//!     let level = r.level();
130//!     let file = r.file();
131//!     let line = r.line();
132//!     let msg = r.msg();
133//!     let req_id = r.key("req_id");
134//!     format!("[{time}][{level}][{file}:{line}] {msg}{req_id}\n").to_string()
135//! }
136//! let builder = recipe::raw_file_logger_custom("/tmp", "log_filter", log::Level::Debug,
137//!     recipe::DEFAULT_TIME, debug_format_req_id_f);
138//! builder.build().expect("setup_log");
139//! let logger = LogFilterKV::new("req_id", format!("{:016x}", 123).to_string());
140//! info!("API service started");
141//! logger_debug!(logger, "Req / received");
142//! logger_debug!(logger, "header xxx");
143//! logger_info!(logger, "Req / 200 complete");
144//! ```
145//!
146//! The log will be:
147//!
148//! ``` text
149//! [2025-06-11 14:33:08.089090][DEBUG][request.rs:67] API service started
150//! [2025-06-11 14:33:10.099092][DEBUG][request.rs:67] Req / received (000000000000007b)
151//! [2025-06-11 14:33:10.099232][WARN][request.rs:68] header xxx (000000000000007b)
152//! [2025-06-11 14:33:11.009092][DEBUG][request.rs:67] Req / 200 complete (000000000000007b)
153//! ```
154//!
155//! ## Unit test example
156//!
157//! To setup different log config on different tests.
158//!
159//! **Make sure that you call [Builder::test()]** in test cases.
160//! which enable dynamic log config and disable signal_hook.
161//!
162//! ```rust
163//! use log::{debug, info, error, Level};
164//! use captains_log::recipe;
165//!
166//! #[test]
167//! fn test1() {
168//!     recipe::raw_file_logger(
169//!         "/tmp", "test1.log", Level::Debug).test().build();
170//!     info!("doing test1");
171//! }
172//!
173//! #[test]
174//! fn test2() {
175//!     recipe::raw_file_logger(
176//!         "/tmp", "test2.log", Level::Debug).test().build();
177//!     info!("doing test2");
178//! }
179//! ```
180//!
181//! ## Best practice with tests
182//!
183//! We provides proc macro [logfn], nice to combine with rstest.
184//!
185//! * When you have large test suit, you want to know which logs belong to which test case.
186//!
187//! * Sometimes your test crashes, you want to find the resposible test case.
188//!
189//! ``` rust
190//! use rstest::*;
191//! use log::*;
192//! use captains_log::*;
193//!
194//! // A show case that setup() fixture will be called twice, before each test.
195//! // In order make logs available.
196//! #[logfn]
197//! #[fixture]
198//! fn setup() {
199//!     let builder = recipe::raw_file_logger("/tmp", "log_rstest", log::Level::Debug).test();
200//!     builder.build().expect("setup_log");
201//! }
202//!
203//! #[logfn]
204//! #[rstest(file_size, case(0), case(1))]
205//! fn test_rstest_foo(setup: (), file_size: usize) {
206//!     info!("do something111");
207//! }
208//!
209//! #[logfn]
210//! #[rstest]
211//! fn test_rstest_bar(setup: ()) {
212//!     info!("do something222");
213//! }
214//!
215//! ```
216//!
217//! After running the test with:
218//! `cargo test -- --test-threads=1`
219//!
220//! /tmp/log_rstest.log will have this content:
221//!
222//! ``` text
223//! [2025-06-21 00:39:37.091326][INFO][test_rstest.rs:11] >>> setup return () >>>
224//! [2025-06-21 00:39:37.091462][INFO][test_rstest.rs:27] <<< test_rstest_bar (setup = ()) enter <<<
225//! [2025-06-21 00:39:37.091493][INFO][test_rstest.rs:30] do something222
226//! [2025-06-21 00:39:37.091515][INFO][test_rstest.rs:27] >>> test_rstest_bar return () >>>
227//! [2025-06-21 00:39:37.091719][INFO][test_rstest.rs:11] <<< setup () enter <<<
228//! [2025-06-21 00:39:37.091826][INFO][test_rstest.rs:11] >>> setup return () >>>
229//! [2025-06-21 00:39:37.091844][INFO][test_rstest.rs:21] <<< test_rstest_foo (setup = (), file_size = 0) enter <<<
230//! [2025-06-21 00:39:37.091857][INFO][test_rstest.rs:24] do something111
231//! [2025-06-21 00:39:37.091868][INFO][test_rstest.rs:21] >>> test_rstest_foo return () >>>
232//! [2025-06-21 00:39:37.092063][INFO][test_rstest.rs:11] <<< setup () enter <<<
233//! [2025-06-21 00:39:37.092136][INFO][test_rstest.rs:11] >>> setup return () >>>
234//! [2025-06-21 00:39:37.092151][INFO][test_rstest.rs:21] <<< test_rstest_foo (setup = (), file_size = 1) enter <<<
235//! [2025-06-21 00:39:37.092163][INFO][test_rstest.rs:24] do something111
236//! [2025-06-21 00:39:37.092173][INFO][test_rstest.rs:21] >>> test_rstest_foo return () >>>
237//! ```
238
239extern crate captains_log_helper;
240extern crate log;
241extern crate signal_hook;
242
243#[macro_use]
244extern crate enum_dispatch;
245
246mod config;
247mod console_impl;
248mod file_impl;
249mod formatter;
250mod log_impl;
251mod time;
252
253pub mod macros;
254pub mod parser;
255pub mod recipe;
256
257mod log_filter;
258
259pub use self::{config::*, formatter::FormatRecord, log_filter::*, log_impl::setup_log};
260pub use captains_log_helper::logfn;
261
262#[cfg(test)]
263mod tests;