1use 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
10fn 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
22fn 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
36fn 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 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 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 let fmt = PatternFormatter::new("{level:7} {message}")?;
95 let sink = Arc::new(ConsoleSink::new(fmt, LogLevel::Trace));
96 let _ = sink;
99
100 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 log_example("minimal", pattern, expected)?;
108
109 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 log_example("Example 1", pattern, expected)?;
117
118 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 log_example("Example 2", pattern, expected)?;
126
127 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 log_example("Example 3", pattern, expected)?;
135
136 Ok(())
137}