captains_log/
lib.rs

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