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 minimalist, customizable, easy to use logger for rust, based on the `log` crate, also adapted to `tracing`,
7//! for production and testing scenario.
8//!
9//! ## Features
10//!
11//! * Allow customize log format and time format. Refer to [LogFormat].
12//!
13//! * Support subscribe log from **tracing**: (feature `tracing`). Refer to [tracing_bridge].
14//!
15//! * Supports multiple types of sink stacking, each with its own log level.
16//!
17//!     + [LogConsole]:  Console output to stdout/stderr.
18//!
19//!     + [LogRawFile]:  Support atomic appending from multi-process on linux (with ext4, xfs)
20//!
21//!     + [LogBufFile]:  Write to log file with merged I/O and delay flush, and optional self-rotation.
22//!
23//!     + `Syslog`: (feature `syslog`), usage: [syslog]
24//!
25//!         Write to local or remote syslog server, with timeout and auto reconnect.
26//!
27//!     + `LogRingFile`: (feature `ringfile`), usage: [ringfile]
28//!
29//!         For deadlock / race condition debugging, collect log to ring buffer in memory, flush on
30//!         panic, or triggered by signal.
31//!
32//! * Provide panic hook by default.
33//!
34//! * Provide additional [macros](#macros), for example: log_assert!(), logger_assert!() ..
35//!
36//! * Supports signal listening for log-rotate. Refer to [Builder::signal()]
37//!
38//! * Provides many preset **recipes** in [recipe] module for convenience.
39//!
40//! * Supports configure by [environment](crate::env)
41//!
42//! * Fine-grain log filtering. By functionality, or track the log by API request. Refer to [crate::filter]
43//!
44//! * For test suits usage:
45//!
46//!     + Allow dynamic reconfigure logger setting in different test function.
47//!
48//!       Refer to [Unit test example](#unit-test-example).
49//!
50//!     + Provides an attribute macro #\[logfn\] to wrap test function.
51//!
52//!       Refer to [Best practice with rstest](#best-practice-with-rstest).
53//!
54//! * Provides a [LogParser](crate::parser::LogParser) to work on your log files.
55//!
56//! ## Usage
57//!
58//! Cargo.toml
59//!
60//! ``` toml
61//! [dependencies]
62//! log = { version = "0.4", features = ["std", "kv_unstable"] }
63//! captains_log = "0"
64//! ```
65//!
66//! lib.rs or main.rs:
67//!
68//! ``` rust
69//! // By default, reexport the macros from log crate
70//! #[macro_use]
71//! extern crate captains_log;
72//! ```
73//!
74//! **Optional feature flags**:
75//!
76//!- `syslog`: Enable [Syslog](crate::syslog::Syslog) sink
77//!
78//!- `ringfile`: Enable [RingFile](crate::ringfile::LogRingFile) sink
79//!
80//!- `tracing`: Receive log from tracing
81//!
82//! ## Recipes
83//!
84//! You can refer to various preset recipe in [recipe] module.
85//!
86//! Buffered sink with log rotation (See the definition of [rotation::Rotation]):
87//!
88//! ``` rust
89//! #[macro_use]
90//! extern crate captains_log;
91//! use captains_log::{*, rotation::*};
92//! // rotate when log file reaches 512M. Keep max 10 archiveed files, with recent 2 not compressed.
93//! // All archived log is moved to "/tmp/rotation/old"
94//! let rotation = Rotation::by_size(512 * 1024 * 1024, Some(10))
95//!     .compress_exclude(2).archive_dir("/tmp/rotation/old");
96//! let _ = recipe::buffered_rotated_file_logger(
97//!     "/tmp/rotation.log", Level::Debug, rotation
98//! ).build();
99//! ```
100//!
101//! The following is setup two log files for different log-level:
102//!
103//! ``` rust
104//! #[macro_use]
105//! extern crate captains_log;
106//! use captains_log::recipe;
107//!
108//! // You'll get /tmp/test.log with all logs, and /tmp/test.log.wf only with error logs.
109//! let log_builder = recipe::split_error_file_logger("/tmp", "test", log::Level::Debug);
110//! // Builder::build() is equivalent of setup_log().
111//! log_builder.build();
112//! // non-error msg will only appear in /tmp/test.log
113//! debug!("Set a course to Sol system");
114//! info!("Engage");
115//! // will appear in both /tmp/test.log and /tmp/test.log.wf
116//! error!("Engine over heat!");
117//! ```
118//!
119//! ## Customize format example
120//!
121//! ``` rust
122//! use captains_log::*;
123//!
124//! fn format_f(r: FormatRecord) -> String {
125//!     let time = r.time();
126//!     let level = r.level();
127//!     let file = r.file();
128//!     let line = r.line();
129//!     let msg = r.msg();
130//!     format!("{time}|{level}|{file}:{line}|{msg}\n").to_string()
131//! }
132//! let debug_format = LogFormat::new(
133//!     "%Y%m%d %H:%M:%S%.6f",
134//!     format_f,
135//! );
136//! let debug_file = LogRawFile::new(
137//!     "/tmp", "test.log", log::Level::Trace, debug_format,
138//! );
139//! let config = Builder::default()
140//!     .signal(signal_hook::consts::SIGINT)
141//!     .add_sink(debug_file);
142//! config.build();
143//! ```
144//!
145//! ## Unit test example
146//!
147//! To setup different log config on different tests.
148//!
149//! **Make sure that you call [Builder::test()]** in test cases.
150//! which enable dynamic log config and disable signal_hook.
151//!
152//! ```rust
153//! use captains_log::*;
154//!
155//! #[test]
156//! fn test1() {
157//!     recipe::raw_file_logger(
158//!         "/tmp/test1.log", Level::Debug).test().build().expect("setup log");
159//!     info!("doing test1");
160//! }
161//!
162//! #[test]
163//! fn test2() {
164//!     recipe::raw_file_logger(
165//!         "/tmp/test2.log", Level::Debug).test().build().expect("setup log");
166//!     info!("doing test2");
167//! }
168//! ```
169//!
170//! ## Best practice with rstest
171//!
172//! We provides proc macro [logfn], the following example shows how to combine with rstest.
173//!
174//! * When you have large test suit, you want to know which logs belong to which test case.
175//!
176//! * Sometimes your test crashes, you want to find the responsible test case.
177//!
178//! * The time spend in each test.
179//!
180//! ``` rust
181//!
182//! use rstest::*;
183//! use captains_log::*;
184//!
185//! // A show case that setup() fixture will be called twice, before each test.
186//! // In order make logs available.
187//! #[fixture]
188//! fn setup() {
189//!     let _logger = recipe::raw_file_logger(
190//!         "/tmp/log_rstest.log", log::Level::Debug)
191//!         .test().build().expect("setup_log");
192//! }
193//!
194//! #[logfn]
195//! #[rstest(file_size, case(0), case(1))]
196//! fn test_rstest_foo(setup: (), file_size: usize) {
197//!     info!("do something111");
198//! }
199//!
200//! #[logfn]
201//! #[rstest]
202//! fn test_rstest_bar(setup: ()) {
203//!     info!("do something222");
204//! }
205//!
206//! // NOTE rstest must be at the bottom to make fixture effective
207//! #[tokio::test]
208//! #[logfn]
209//! #[rstest]
210//! async fn test_rstest_async(setup: ()) {
211//!     info!("something333")
212//! }
213//! ```
214//!
215//! **Notice:** the order when combine tokio::test with rstest,
216//! `#[rstest]` attribute must be at the bottom to make setup fixture effective.
217//!
218//! After running the test with:
219//!
220//! cargo test -- --test-threads=1
221//!
222//! /tmp/log_rstest.log will have this content:
223//!
224//! ``` text
225//! [2025-07-13 18:22:39.159642][INFO][test_rstest.rs:33] <<< test_rstest_async (setup = ()) enter <<<
226//! [2025-07-13 18:22:39.160255][INFO][test_rstest.rs:37] something333
227//! [2025-07-13 18:22:39.160567][INFO][test_rstest.rs:33] >>> test_rstest_async return () in 564.047µs >>>
228//! [2025-07-13 18:22:39.161299][INFO][test_rstest.rs:26] <<< test_rstest_bar (setup = ()) enter <<<
229//! [2025-07-13 18:22:39.161643][INFO][test_rstest.rs:29] do something222
230//! [2025-07-13 18:22:39.161703][INFO][test_rstest.rs:26] >>> test_rstest_bar return () in 62.681µs >>>
231//! [2025-07-13 18:22:39.162169][INFO][test_rstest.rs:20] <<< test_rstest_foo (setup = (), file_size = 0) enter <<<
232//! [2025-07-13 18:22:39.162525][INFO][test_rstest.rs:23] do something111
233//! [2025-07-13 18:22:39.162600][INFO][test_rstest.rs:20] >>> test_rstest_foo return () in 78.457µs >>>
234//! [2025-07-13 18:22:39.163050][INFO][test_rstest.rs:20] <<< test_rstest_foo (setup = (), file_size = 1) enter <<<
235//! [2025-07-13 18:22:39.163320][INFO][test_rstest.rs:23] do something111
236//! [2025-07-13 18:22:39.163377][INFO][test_rstest.rs:20] >>> test_rstest_foo return () in 58.747µs >>>
237//! ```
238
239extern crate captains_log_helper;
240extern crate log;
241extern crate signal_hook;
242
243#[macro_use]
244extern crate enum_dispatch;
245
246mod buf_file_impl;
247mod config;
248mod console_impl;
249pub mod env;
250mod file_impl;
251mod formatter;
252mod log_impl;
253pub mod rotation;
254mod time;
255
256#[cfg(feature = "syslog")]
257#[cfg_attr(docsrs, doc(cfg(feature = "syslog")))]
258pub mod syslog;
259
260#[cfg(feature = "ringfile")]
261#[cfg_attr(docsrs, doc(cfg(feature = "ringfile")))]
262/// High speed Ring Buffer that maintained the message on memory
263pub mod ringfile;
264
265pub mod macros;
266pub mod parser;
267pub mod recipe;
268
269pub mod filter;
270
271pub use self::buf_file_impl::*;
272pub use self::console_impl::*;
273pub use self::file_impl::*;
274pub use self::{
275    config::*,
276    formatter::FormatRecord,
277    log_impl::{get_global_logger, setup_log, GlobalLogger},
278};
279pub use captains_log_helper::logfn;
280
281#[cfg(feature = "tracing")]
282pub mod tracing_bridge;
283
284/// Re-export log::Level:
285pub use log::Level;
286/// Re-export log::LevelFilter:
287pub use log::LevelFilter;
288pub use log::{debug, error, info, trace, warn};
289
290/// Re-export from signal_hook::consts:
291pub use signal_hook::consts::signal as signal_consts;