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