pub struct ConsoleSink<F: Formatter, W: Write = BufWriter<Stdout>> { /* private fields */ }Expand description
Writes formatted records to a Write destination.
Composes a Formatter with a buffered writer. The writer plus a
reusable scratch String live behind a Mutex because
Sink::write_record takes &self; in practice the lock is
uncontended — sinks are usually invoked only from the backend worker
thread.
The writer type W defaults to BufWriter<Stdout>, which is what
ConsoleSink::new produces. Use ConsoleSink::with_writer to supply
an alternative destination (e.g. a Vec<u8> in tests).
Implementations§
Source§impl<F: Formatter> ConsoleSink<F>
impl<F: Formatter> ConsoleSink<F>
Sourcepub fn new(formatter: F, level: LogLevel) -> ConsoleSink<F, BufWriter<Stdout>>
pub fn new(formatter: F, level: LogLevel) -> ConsoleSink<F, BufWriter<Stdout>>
Constructs a ConsoleSink writing to a fresh BufWriter<Stdout>.
Examples found in repository?
10fn main() -> Result<(), Box<dyn Error>> {
11 // ANCHOR: backend
12 let _guard = start(BackendOptions::default())?;
13 // ANCHOR_END: backend
14
15 // ANCHOR: sink
16 let sink: Arc<dyn insomnilog::Sink> = Arc::new(ConsoleSink::new(
17 PatternFormatter::default(),
18 LogLevel::Trace,
19 ));
20 register_sink("console", Arc::clone(&sink))?;
21 // ANCHOR_END: sink
22
23 // ANCHOR: logger
24 let logger = create_logger("app", vec![Arc::clone(&sink)], LogLevel::Info)?;
25
26 log_info!(logger, "server started on port {}", 8080_u16);
27 // ANCHOR_END: logger
28
29 Ok(())
30}More examples
10fn main() -> Result<(), Box<dyn Error>> {
11 let _guard = start(BackendOptions::default())?;
12
13 let sink: Arc<dyn insomnilog::Sink> = Arc::new(ConsoleSink::new(
14 PatternFormatter::default(),
15 LogLevel::Info,
16 ));
17 let logger = create_logger("app", vec![sink], LogLevel::Info)?;
18
19 // ANCHOR: main_thread
20 // Allocate the queue for the main thread before entering the hot path.
21 preallocate_thread();
22 // ANCHOR_END: main_thread
23
24 log_info!(logger, "main thread ready");
25
26 // ANCHOR: worker_thread
27 let worker_logger = Arc::clone(&logger);
28 thread::spawn(move || {
29 // Allocate the queue for this thread before any log call.
30 preallocate_thread();
31
32 log_info!(worker_logger, "worker thread ready");
33 })
34 .join()
35 .unwrap();
36 // ANCHOR_END: worker_thread
37
38 Ok(())
39}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}10fn main() -> Result<(), Box<dyn Error>> {
11 // 1. Start the backend worker thread. The returned guard keeps it alive;
12 // dropping it drains all buffered records and joins the thread.
13 let _guard = insomnilog::start(insomnilog::BackendOptions::default())?;
14
15 // 2. Create a sink — decides where and how records are written. The built-in
16 // `ConsoleSink` formats records as human-readable lines and writes them to
17 // stdout.
18 let sink = Arc::new(ConsoleSink::new(
19 PatternFormatter::default(),
20 LogLevel::Trace,
21 ));
22
23 // 3. Create a logger — the handle you pass to every log macro. It holds a
24 // level filter and the list of sinks that receive its records.
25 let logger = insomnilog::create_logger("app", vec![sink], LogLevel::Trace)?;
26
27 // Optional: Eagerly allocate this thread's queue so the first log call is
28 // allocation-free. Omit it and allocation happens on first use.
29 preallocate_thread();
30
31 // 4. Start logging
32 // The macros accept a format string and zero or more typed arguments, similar to
33 // `println!`
34 log_info!(logger, "application started");
35 log_debug!(logger, "config loaded from {}", "/etc/app/config.toml");
36 log_warn!(logger, "disk usage at {}%", 87.5_f64);
37 log_error!(logger, "connection lost to {}", "db-primary");
38
39 // _guard drops here → backend drains and exits cleanly.
40 Ok(())
41}Source§impl<F: Formatter, W: Write> ConsoleSink<F, W>
impl<F: Formatter, W: Write> ConsoleSink<F, W>
Sourcepub const fn with_writer(formatter: F, level: LogLevel, writer: W) -> Self
pub const fn with_writer(formatter: F, level: LogLevel, writer: W) -> Self
Constructs a ConsoleSink writing to the given writer.
Prefer ConsoleSink::new for production use. This constructor
exists mainly to allow tests to capture output without touching stdout.
Examples found in repository?
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}Source§impl<F: Formatter> ConsoleSink<F, Vec<u8>>
impl<F: Formatter> ConsoleSink<F, Vec<u8>>
Sourcepub fn captured_output(&self) -> Vec<u8> ⓘ
pub fn captured_output(&self) -> Vec<u8> ⓘ
Returns a copy of the bytes written to the sink so far.
Examples found in repository?
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}Trait Implementations§
Source§impl<F: Formatter, W: Write + Send> Sink for ConsoleSink<F, W>
impl<F: Formatter, W: Write + Send> Sink for ConsoleSink<F, W>
Source§fn write_record(&self, record: &LogRecord) -> Result<(), SinkError>
fn write_record(&self, record: &LogRecord) -> Result<(), SinkError>
self.level() <= record.level. Read more