Skip to main content

formatter/
formatter.rs

1//! Demonstrates `PatternFormatter` with several common logging styles.
2
3use std::{error::Error, sync::Arc, thread, time::Duration};
4
5use insomnilog::{
6    BackendOptions, ConsoleSink, LogLevel, PatternFormatter, Sink, create_logger, log_info,
7    log_warn, start,
8};
9
10/// Spins calling `pred` until it returns `true` or `timeout` elapses.
11fn spin_until(pred: impl Fn() -> bool, timeout: Duration) -> bool {
12    let deadline = std::time::Instant::now() + timeout;
13    while std::time::Instant::now() < deadline {
14        if pred() {
15            return true;
16        }
17        thread::yield_now();
18    }
19    false
20}
21
22/// Replaces every ASCII digit with `'x'` so timestamps and line numbers can be
23/// matched without knowing their exact value. Also normalises the source file
24/// path and module name to fixed tokens so the assertion holds regardless of
25/// the compilation context (e.g. doctests use an auto-generated module path).
26fn normalize(s: &str) -> String {
27    s.replace("examples/examples/formatter.rs", "<file>")
28        .replace(file!(), "<file>")
29        .replace("formatter", "<module>")
30        .replace(module_path!(), "<module>")
31        .chars()
32        .map(|c| if c.is_ascii_digit() { 'x' } else { c })
33        .collect()
34}
35
36/// Creates a console sink and a capture sink from `pattern`, logs two records,
37/// and asserts the normalized capture output equals `expected` when non-empty.
38fn log_example(
39    name: &str,
40    pattern: &str,
41    expected: &str,
42) -> Result<(), Box<dyn std::error::Error>> {
43    let console = Arc::new(ConsoleSink::new(
44        PatternFormatter::new(pattern)?,
45        LogLevel::Trace,
46    ));
47    let capture = Arc::new(ConsoleSink::with_writer(
48        PatternFormatter::new(pattern)?,
49        LogLevel::Trace,
50        Vec::<u8>::new(),
51    ));
52    let logger = create_logger(
53        name,
54        vec![
55            console as Arc<dyn Sink>,
56            Arc::clone(&capture) as Arc<dyn Sink>,
57        ],
58        LogLevel::Trace,
59    )?;
60
61    // ANCHOR: message
62    log_info!(
63        logger,
64        "The Answer to the Ultimate Question of Life, the Universe, and Everything.: {}",
65        42_u16
66    );
67    log_warn!(logger, "...What{}", "?");
68    // ANCHOR_END: message
69
70    if !expected.is_empty() {
71        let ready = spin_until(
72            || capture.captured_output().len() >= expected.len(),
73            Duration::from_millis(100),
74        );
75        assert!(
76            ready,
77            "capture sink did not receive all records within 100 ms"
78        );
79        let actual =
80            String::from_utf8(capture.captured_output()).expect("sink output is valid UTF-8");
81        assert_eq!(normalize(&actual), normalize(expected));
82    }
83    Ok(())
84}
85
86#[expect(
87    clippy::literal_string_with_formatting_args,
88    reason = "{secs}, {millis:03} etc. are PatternFormatter placeholders, not Rust format args"
89)]
90fn main() -> Result<(), Box<dyn Error>> {
91    let _guard = start(BackendOptions::default())?;
92
93    // ANCHOR: definition
94    let fmt = PatternFormatter::new("{level:7} {message}")?;
95    let sink = Arc::new(ConsoleSink::new(fmt, LogLevel::Trace));
96    // ANCHOR_END: definition
97
98    let _ = sink;
99
100    // ANCHOR: minimal
101    let pattern = "{level:7} {message}";
102    let expected = concat!(
103        "INFO    The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
104        "WARNING ...What?\n",
105    );
106    // ANCHOR_END: minimal
107    log_example("minimal", pattern, expected)?;
108
109    // ANCHOR: structured
110    let pattern = "[{secs}.{millis:03}] {level:<8} {logger:<12} {message}";
111    let expected = concat!(
112        "[1779463945.562] INFO     Example 1    The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
113        "[1779463945.562] WARNING  Example 1    ...What?\n",
114    );
115    // ANCHOR_END: structured
116    log_example("Example 1", pattern, expected)?;
117
118    // ANCHOR: module
119    let pattern = "{level} [{module}] {file}:{line} {message}";
120    let expected = concat!(
121        "INFO [formatter] examples/examples/formatter.rs:54 The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
122        "WARNING [formatter] examples/examples/formatter.rs:59 ...What?\n",
123    );
124    // ANCHOR_END: module
125    log_example("Example 2", pattern, expected)?;
126
127    // ANCHOR: centered
128    let pattern = "{message:-^40}";
129    let expected = concat!(
130        "The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
131        "----------------...What?----------------\n",
132    );
133    // ANCHOR_END: centered
134    log_example("Example 3", pattern, expected)?;
135
136    Ok(())
137}