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