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