Skip to main content

ConsoleSink

Struct ConsoleSink 

Source
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>

Source

pub fn new(formatter: F, level: LogLevel) -> ConsoleSink<F, BufWriter<Stdout>>

Constructs a ConsoleSink writing to a fresh BufWriter<Stdout>.

Examples found in repository?
examples/core_concepts.rs (lines 16-19)
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
Hide additional examples
examples/preallocating.rs (lines 13-16)
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}
examples/formatter.rs (lines 43-46)
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}
examples/quick_start.rs (lines 18-21)
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>

Source

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?
examples/formatter.rs (lines 47-51)
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>>

Source

pub fn captured_output(&self) -> Vec<u8>

Returns a copy of the bytes written to the sink so far.

Examples found in repository?
examples/formatter.rs (line 72)
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>

Source§

fn write_record(&self, record: &LogRecord) -> Result<(), SinkError>

Processes a log record. Called by the backend worker once per record, after the worker confirms self.level() <= record.level. Read more
Source§

fn flush(&self) -> Result<(), SinkError>

Flushes any buffered output. Called by the worker after each batch of records and at shutdown. Read more
Source§

fn level(&self) -> LogLevel

Returns the sink’s filter level. Fixed at construction.

Auto Trait Implementations§

§

impl<F, W = BufWriter<Stdout>> !Freeze for ConsoleSink<F, W>

§

impl<F, W> RefUnwindSafe for ConsoleSink<F, W>
where F: RefUnwindSafe,

§

impl<F, W> Send for ConsoleSink<F, W>
where W: Send,

§

impl<F, W> Sync for ConsoleSink<F, W>
where W: Send,

§

impl<F, W> Unpin for ConsoleSink<F, W>
where F: Unpin, W: Unpin,

§

impl<F, W> UnsafeUnpin for ConsoleSink<F, W>
where F: UnsafeUnpin, W: UnsafeUnpin,

§

impl<F, W> UnwindSafe for ConsoleSink<F, W>
where F: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.