Skip to main content

Crate insomnilog

Crate insomnilog 

Source
Expand description

§About insomnilog

insomnilog logo 02:14:07 ERROR null pointer in core 02:14:08 WARN retry attempt 3/5 02:14:09 INFO handler registered 02:14:11 ERROR connection refused 02:14:12 DEBUG stack depth: 42 02:14:13 WARN memory threshold 87% 02:14:14 ERROR write failed: disk full 02:14:15 INFO shutdown initiated insomnilog I can't get no sleep

insomnilog is an asynchronous Rust logging library designed for real-time and latency-sensitive applications. All logging on the hot path is zero-allocation and lock-free — log records are serialised as raw bytes into a per-thread ring buffer and decoded by a background worker thread.

§Design goals

  • Real-time safe hot path. The logging macros never allocate heap memory, never acquire a lock, and never block. They perform a level check, encode arguments directly into a pre-allocated ring buffer, and return.

  • Asynchronous by default. Formatting, sink dispatch, and I/O happen on a dedicated backend worker thread. The calling thread pays only the cost of the ring-buffer write.

  • Explicit configuration, no global magic. You start the backend once with insomnilog::start(opts), create named loggers and sinks explicitly, and pass a logger reference to each call site. There is no implicit global logger, no auto-start, and no hidden process-exit hook.

  • Composable sinks. A Sink receives a decoded LogRecord and decides its own output format. One sink can write human-readable text, another can write structured JSON, a third can forward to a metrics system — all from the same logger.

§Inspiration

insomnilog is heavily inspired by Quill, a C++ low-latency logging library with similar goals: asynchronous background processing, per-thread lock-free queues, and a hot path that does as little as possible.

§Quick start

This chapter walks through the minimum code needed to get insomnilog logging to the console. For a deeper explanation of what each piece does, see Core concepts.

//! Minimal quick-start example for `insomnilog`.

use std::{error::Error, sync::Arc};

use insomnilog::{
    ConsoleSink, LogLevel, PatternFormatter, log_debug, log_error, log_info, log_warn,
    preallocate_thread,
};

fn main() -> Result<(), Box<dyn Error>> {
    // 1. Start the backend worker thread. The returned guard keeps it alive;
    // dropping it drains all buffered records and joins the thread.
    let _guard = insomnilog::start(insomnilog::BackendOptions::default())?;

    // 2. Create a sink — decides where and how records are written. The built-in
    // `ConsoleSink` formats records as human-readable lines and writes them to
    // stdout.
    let sink = Arc::new(ConsoleSink::new(
        PatternFormatter::default(),
        LogLevel::Trace,
    ));

    // 3. Create a logger — the handle you pass to every log macro. It holds a
    // level filter and the list of sinks that receive its records.
    let logger = insomnilog::create_logger("app", vec![sink], LogLevel::Trace)?;

    // Optional: Eagerly allocate this thread's queue so the first log call is
    // allocation-free. Omit it and allocation happens on first use.
    preallocate_thread();

    // 4. Start logging
    // The macros accept a format string and zero or more typed arguments, similar to
    // `println!`
    log_info!(logger, "application started");
    log_debug!(logger, "config loaded from {}", "/etc/app/config.toml");
    log_warn!(logger, "disk usage at {}%", 87.5_f64);
    log_error!(logger, "connection lost to {}", "db-primary");

    // _guard drops here → backend drains and exits cleanly.
    Ok(())
}

§Core concepts

There are three objects you work with directly in insomnilog: a Backend, one or more Sinks, and one or more Loggers. Understanding what each one owns and what it controls makes the rest of the API straightforward.

§Backend

The backend is the engine that drives everything. It owns the worker thread, the per-thread queues, and the registries that keep loggers and sinks alive. You start it once at the beginning of your program and shut it down at the end:

    let _guard = start(BackendOptions::default())?;

The returned ShutdownGuard keeps the backend alive. When it drops — at the end of main, or when you call insomnilog::shutdown() explicitly — the worker thread drains every pending log record and exits cleanly.

Everything else (sinks, loggers, log macros) requires the backend to be running. Calling any of them before start() panics immediately.

§Sink

A Sink is the destination for log records. It receives a decoded LogRecord and decides what to do with it — write formatted text to stdout, serialise to JSON, forward to a metrics system, or anything else.

Every sink has a level filter set at construction time. Records below that level are skipped by the backend before write_record is ever called.

insomnilog ships with two built-in sinks:

  • ConsoleSink — formats records using a Formatter and writes them to stdout. The default formatter produces human-readable timestamped lines.
  • NullSink — silently discards every record. Useful in tests.

Sinks are heap-allocated and reference-counted. You wrap one in an Arc and register it with a name before attaching it to any logger:

    let sink: Arc<dyn insomnilog::Sink> = Arc::new(ConsoleSink::new(
        PatternFormatter::default(),
        LogLevel::Trace,
    ));
    register_sink("console", Arc::clone(&sink))?;

Registration is optional if you only use the sink with a single logger — you can pass the Arc directly to create_logger without registering it. The registry exists so that multiple loggers can share a named sink looked up at configuration time. A registered sink can be retrieved at any point with insomnilog::get_sink("name").

§Logger

A Logger is the handle you pass to every log macro at the call site. It holds:

  • A name — unique within the process.
  • A level filter — the producer-side gate that runs on the calling thread before anything is written to the queue.
  • A list of sinks — fixed at creation; every record that passes the level filter is delivered to all of them.

You create a logger once, typically during application startup, and store the Arc<Logger> wherever your code needs it:

    let logger = create_logger("app", vec![Arc::clone(&sink)], LogLevel::Info)?;

    log_info!(logger, "server started on port {}", 8080_u16);

The logger’s level and the sink’s level work in series: the logger’s level is checked first on the hot path (the calling thread), and the sink’s level is checked second on the backend thread. A record must pass both filters to reach a sink’s write_record. This means the effective level for any sink is max(logger.level, sink.level) — setting a sink to Trace while the logger is at Info will never produce Debug or Trace output; lower the logger’s level instead.

The logger’s level is the only property that can be changed after creation, via Logger::set_level. The sink list is immutable. A registered logger can be retrieved at any point with insomnilog::get_logger("name").

§Putting it together

Backend (started once, lives until ShutdownGuard drops)
  │
  ├── Sink "console"  ← level: Trace
  ├── Sink "errors"   ← level: Error
  │
  ├── Logger "app"    ← level: Info, sinks: ["console"]
  └── Logger "audit"  ← level: Trace, sinks: ["console", "errors"]

A log_debug! call on logger "app" is dropped immediately on the calling thread — the logger’s Info filter rejects it before the queue is touched.

A log_error! call on logger "audit" passes the Trace filter, enters the queue, and is delivered to both sinks on the backend thread.

§Architecture

insomnilog splits logging into two distinct phases: a hot path on the calling thread and background processing on a dedicated worker thread. This separation is what allows the hot path to be zero-allocation and lock-free.

§Component overview

graph TD
    subgraph "Thread 1"
        M1["log_info!(logger, ...)"] --> CL1["Check level"] --> Q1["Encode record into\nqueue 1"]
    end

    subgraph "Thread 2"
        M2["log_info!(logger, ...)"] --> CL2["Check level"] --> Q2["Encode record into\nqueue 2"]
    end

    subgraph "Backend worker thread"
        W["Get next record from any queue\n(round-robin poll)"]
        W --> D["Decode record"]
        D --> L["Write records to sinks"]
    end

    Q1 --> W
    Q2 --> W

§Hot path: what happens on the calling thread

When you write log_info!(logger, "user {} logged in", username) the macro expands to roughly:

  1. Level check If the record is below the threshold the macro returns immediately with no further work.

  2. Header construction — a static LogMetadata is generated at the call site at compile time (file, line, level). The header written to the buffer also carries a raw *const Logger pointer so the backend can reach the logger’s sink list without a registry lookup.

  3. Encode into the queue — Reserves a contiguous slice in the per-thread SPSC queue, then writes the fixed-size header followed by each argument encoded as typed bytes. No heap allocation, no format string, no lock. If the queue is full the record is silently dropped — the calling thread is never blocked.

The per-thread queue is allocated lazily on the first log call from a new thread. Latency-sensitive threads can call preallocate_thread during startup to pay that cost upfront and keep every subsequent log call on the fast path.

§Background processing: what happens on the worker thread

The backend worker polls all known per-thread queues in a round-robin. For each queue it processes a limited number of records per pass before moving to the next, ensuring no single high-volume thread starves the others.

For each record it finds:

  1. Decode — the raw bytes are decoded into a log record containing the log level, timestamp, source location, and typed argument values.

  2. Logger dispatch — the *const Logger in the record header is dereferenced to obtain &Logger. This is safe because the logger registry holds a strong Arc<Logger> for the entire process lifetime, guaranteeing the pointer remains valid.

  3. Sink fan-out — the worker iterates logger.sinks and calls Sink::write_record(&record) on each one whose level filter passes. The entire fan-out is wrapped in std::panic::catch_unwind so a panicking sink cannot take down the backend.

§Preallocating thread queues

Every thread that logs through insomnilog needs its own SPSC queue. By default that queue is allocated on the first log call from a new thread. That lazy allocation involves a heap allocation and a mutex lock to register the queue with the backend — work that does not belong on a real-time or latency-sensitive hot path.

preallocate_thread moves that cost to a controlled point before the hot path begins:

    // Allocate the queue for the main thread before entering the hot path.
    preallocate_thread();

After this call every subsequent log macro call on the same thread is allocation-free and lock-free. The function is idempotent — calling it more than once from the same thread is a no-op.

The same applies to any thread that will log. For spawned threads, call preallocate_thread at the start of the thread body before doing any work:

    let worker_logger = Arc::clone(&logger);
    thread::spawn(move || {
        // Allocate the queue for this thread before any log call.
        preallocate_thread();

        log_info!(worker_logger, "worker thread ready");
    })
    .join()
    .unwrap();

For thread pools, the right place is the pool’s thread-start hook (sometimes called on_thread_start or similar), so every worker pays the cost once at startup rather than on the first log call during a request.

Full example
//! Demonstrates eager per-thread queue allocation with `preallocate_thread`.

use std::{error::Error, sync::Arc, thread};

use insomnilog::{
    BackendOptions, ConsoleSink, LogLevel, PatternFormatter, create_logger, log_info,
    preallocate_thread, start,
};

fn main() -> Result<(), Box<dyn Error>> {
    let _guard = start(BackendOptions::default())?;

    let sink: Arc<dyn insomnilog::Sink> = Arc::new(ConsoleSink::new(
        PatternFormatter::default(),
        LogLevel::Info,
    ));
    let logger = create_logger("app", vec![sink], LogLevel::Info)?;

    // ANCHOR: main_thread
    // Allocate the queue for the main thread before entering the hot path.
    preallocate_thread();
    // ANCHOR_END: main_thread

    log_info!(logger, "main thread ready");

    // ANCHOR: worker_thread
    let worker_logger = Arc::clone(&logger);
    thread::spawn(move || {
        // Allocate the queue for this thread before any log call.
        preallocate_thread();

        log_info!(worker_logger, "worker thread ready");
    })
    .join()
    .unwrap();
    // ANCHOR_END: worker_thread

    Ok(())
}

§Configuring the formatter

Some sinks, e.g. the ConsoleSink, use a PatternFormatter to turn a LogRecord into a line of text using a configurable pattern string. A pattern is a mix of literal text and {field} or {field:spec} placeholders that are substituted at format time.

    let fmt = PatternFormatter::new("{level:7} {message}")?;
    let sink = Arc::new(ConsoleSink::new(fmt, LogLevel::Trace));

§Available Fields

FieldDescriptionExample value
levelLog levelINFO
secsWhole seconds of the timestamp (seconds since the Unix epoch)1234567890
millisMillisecond component of the timestamp (0–999)042
fileSource file pathsrc/server.rs
lineSource line number42
moduleRust module path of the call sitemy_app::server
loggerName of the logger that emitted the recordpayments
messageFully formatted log messageorder #4291 confirmed

§Format spec

A placeholder can carry an optional format spec after a colon: {field:spec}. The spec controls padding and alignment.

Spec elementSyntaxDescription
Align left<Pad on the right (text flush left)
Align right>Pad on the left (text flush right, default for numeric fields)
Align center^Pad equally on both sides
Fill characterAny char before <, >, or ^Character used for padding (default: space)
Zero-pad0 before widthUse 0 as fill and force right-alignment
WidthIntegerMinimum output width

Examples: {level:<8} left-aligns level in an 8-character field; {millis:03} zero-pads the milliseconds to 3 digits; {logger:->12} right-aligns the logger name with - fill in a 12-character field.

§Style examples

The following configurations showcase the output for different formatter configurations of the following log messages:

    log_info!(
        logger,
        "The Answer to the Ultimate Question of Life, the Universe, and Everything.: {}",
        42_u16
    );
    log_warn!(logger, "...What{}", "?");

§Minimal — level and message only

    let pattern = "{level:7} {message}";
    let expected = concat!(
        "INFO    The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "WARNING ...What?\n",
    );

§Structured — timestamp, aligned level, logger name

    let pattern = "[{secs}.{millis:03}] {level:<8} {logger:<12} {message}";
    let expected = concat!(
        "[1779463945.562] INFO     Example 1    The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "[1779463945.562] WARNING  Example 1    ...What?\n",
    );

§Module-style — level, module path, source location

    let pattern = "{level} [{module}] {file}:{line} {message}";
    let expected = concat!(
        "INFO [formatter] examples/examples/formatter.rs:54 The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "WARNING [formatter] examples/examples/formatter.rs:59 ...What?\n",
    );

§Center-aligned with fill — message only

    let pattern = "{message:-^40}";
    let expected = concat!(
        "The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "----------------...What?----------------\n",
    );
Full example
//! Demonstrates `PatternFormatter` with several common logging styles.

use std::{error::Error, sync::Arc, thread, time::Duration};

use insomnilog::{
    BackendOptions, ConsoleSink, LogLevel, PatternFormatter, Sink, create_logger, log_info,
    log_warn, start,
};

/// Spins calling `pred` until it returns `true` or `timeout` elapses.
fn spin_until(pred: impl Fn() -> bool, timeout: Duration) -> bool {
    let deadline = std::time::Instant::now() + timeout;
    while std::time::Instant::now() < deadline {
        if pred() {
            return true;
        }
        thread::yield_now();
    }
    false
}

/// Replaces every ASCII digit with `'x'` so timestamps and line numbers can be
/// matched without knowing their exact value. Also normalises the source file
/// path and module name to fixed tokens so the assertion holds regardless of
/// the compilation context (e.g. doctests use an auto-generated module path).
fn normalize(s: &str) -> String {
    s.replace("examples/examples/formatter.rs", "<file>")
        .replace(file!(), "<file>")
        .replace("formatter", "<module>")
        .replace(module_path!(), "<module>")
        .chars()
        .map(|c| if c.is_ascii_digit() { 'x' } else { c })
        .collect()
}

/// Creates a console sink and a capture sink from `pattern`, logs two records,
/// and asserts the normalized capture output equals `expected` when non-empty.
fn log_example(
    name: &str,
    pattern: &str,
    expected: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let console = Arc::new(ConsoleSink::new(
        PatternFormatter::new(pattern)?,
        LogLevel::Trace,
    ));
    let capture = Arc::new(ConsoleSink::with_writer(
        PatternFormatter::new(pattern)?,
        LogLevel::Trace,
        Vec::<u8>::new(),
    ));
    let logger = create_logger(
        name,
        vec![
            console as Arc<dyn Sink>,
            Arc::clone(&capture) as Arc<dyn Sink>,
        ],
        LogLevel::Trace,
    )?;

    // ANCHOR: message
    log_info!(
        logger,
        "The Answer to the Ultimate Question of Life, the Universe, and Everything.: {}",
        42_u16
    );
    log_warn!(logger, "...What{}", "?");
    // ANCHOR_END: message

    if !expected.is_empty() {
        let ready = spin_until(
            || capture.captured_output().len() >= expected.len(),
            Duration::from_millis(100),
        );
        assert!(
            ready,
            "capture sink did not receive all records within 100 ms"
        );
        let actual =
            String::from_utf8(capture.captured_output()).expect("sink output is valid UTF-8");
        assert_eq!(normalize(&actual), normalize(expected));
    }
    Ok(())
}

#[expect(
    clippy::literal_string_with_formatting_args,
    reason = "{secs}, {millis:03} etc. are PatternFormatter placeholders, not Rust format args"
)]
fn main() -> Result<(), Box<dyn Error>> {
    let _guard = start(BackendOptions::default())?;

    // ANCHOR: definition
    let fmt = PatternFormatter::new("{level:7} {message}")?;
    let sink = Arc::new(ConsoleSink::new(fmt, LogLevel::Trace));
    // ANCHOR_END: definition

    let _ = sink;

    // ANCHOR: minimal
    let pattern = "{level:7} {message}";
    let expected = concat!(
        "INFO    The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "WARNING ...What?\n",
    );
    // ANCHOR_END: minimal
    log_example("minimal", pattern, expected)?;

    // ANCHOR: structured
    let pattern = "[{secs}.{millis:03}] {level:<8} {logger:<12} {message}";
    let expected = concat!(
        "[1779463945.562] INFO     Example 1    The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "[1779463945.562] WARNING  Example 1    ...What?\n",
    );
    // ANCHOR_END: structured
    log_example("Example 1", pattern, expected)?;

    // ANCHOR: module
    let pattern = "{level} [{module}] {file}:{line} {message}";
    let expected = concat!(
        "INFO [formatter] examples/examples/formatter.rs:54 The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "WARNING [formatter] examples/examples/formatter.rs:59 ...What?\n",
    );
    // ANCHOR_END: module
    log_example("Example 2", pattern, expected)?;

    // ANCHOR: centered
    let pattern = "{message:-^40}";
    let expected = concat!(
        "The Answer to the Ultimate Question of Life, the Universe, and Everything.: 42\n",
        "----------------...What?----------------\n",
    );
    // ANCHOR_END: centered
    log_example("Example 3", pattern, expected)?;

    Ok(())
}

§Changelog

The following sections list the changes between each consecutive version of this library. The Highlights section is a short summary of the Details section.

§0.2.0 (2026-05-28)

§✨ Highlights

The library was completely rewritten with an overall architecture in mind. It now contains extensive test infrastructure and usage-based documentation.

§⚠️ Known Issues

The following things have not been tested or validated:

  • Running long-term tests to validate real-time behavior
  • Benchmarking against other libraries

Currently missing:

  • More reference implementations of sinks e.g. different types of file sinks.
  • Some features are still undocumented e.g. defining your own decodable type or handling errors.

§🔎 Details

§🚀 Features
  • 02ea02d Rewrite queue module
  • 9e01d0c 🚨 Move current implementation to a legacy module
  • 4fefd0a Introduce queue from legacy
  • 762c548 Add LogLevel enum
  • 5645541 Add LogMetadata
  • 7fdb972 Add encode module
  • 9f35f6f Add RecordHeader to store record information
  • 96e6e69 Add decode module
  • a6707b6 Add minimal backend and lifecycle modules
  • 1b99a39 Add formatter with default pattern implementation
  • fdb70f7 Add Sink trait and ConsoleSink & NullSink
  • 3a46684 Allow registering and retrieving sinks globally
  • eca32e0 Count sink errors and print them during backend shutdown
  • 480fdcf Add Logger struct
  • 78bb8b7 Allow creating and retrieving loggers globally
  • 57226f2 Add logger_ptr to the RecordHeader
  • 6e765f3 Split DecodedRecord into RawDecodedRecord and LogRecord
  • 866c2e6 Add per_thread_queue
  • c1fdbf7 Add BackendRunner
  • 571e8f9 Use BackendRunner in Backend
  • 4ee4813 Handle dropped records in backend
  • 3b30e85 Create producer for each thread in the backend
  • 0fb46f7 Allow to retrieve the process wide backend
  • 835bf12 Add frontend
  • fe5644a Allow to preallocate a thread
  • 17b45e2 Add logging macros
  • 2417122 Retrieve captured output from ConsoleSinks that use vec writers
  • b160e96 🚨 Remove legacy module
§🐛 Fixes
  • 9fd9a2b Use Box<[UnsafeCell<u8>]> for SPSC ring buffer
  • 4e8bf0f Implement Error for InvalidPatternError
§🔄️ Changes
  • 3b5d418 Swap in the rewritten queue module
  • be2992f Use wrapping arithmetic on read/write positions
§🧪 Tests
  • 87e9761 Add compile-fail doctest for peek/read mutual exclusion
  • 13e328f Add test utilities spin_until and RecordingSink
  • 55cebf4 Add end-to-end tests for general usability
§🧰 Tasks
  • 45af80d Mark new of RecordHeader as must_use
  • ed2adc0 Integrate examples into the main crate
§🛠 Build
  • 0eb6201 Add MIRI check to justfile and GitHub Actions
  • 230b2d1 Temporarily allow dead_code
  • f8c0f1d Abort jobs on force push
  • c3b6d35 Split miri test execution into default and slow
  • a85ff03 Configure tagName for JReleaser to correctly diff releases
§📝 Documentation
  • 08f53ad Add architecture decision records (ADR)
  • c65e61c Create usage based docs incl. about, quick_start and architecture
  • 6b9631c Add chapter Preallocating thread queues
  • e683b08 Add chapter Configuring the formatter
  • b7f3f72 Overhaul the developer and agent documentation
  • 282ff9d Use logo in the rendered docs

§👥 Contributors

We’d like to thank the following people for their contributions:

  • Dwayne Steinke

§0.1.0 (2026-03-12)

§✨ Highlights

This is the first release of the library. It focuses on:

  • The general setup of the repository
  • The first architecture version, such that a first simple example can be run
  • The configuration of tooling for future development.

§⚠️ Known Issues

This version only aimed to run a simple example. As such, the following things have not been tested or validated:

  • Using the logger class in a real-world scenario e.g. instantiating multiple loggers in multiple threads
  • Running long-term tests to validate real-time behavior
  • Benchmarking against other libraries
  • Supporting useful configurations e.g. multiple sinks and formatters.

§🔎 Details

§🚀 Features
  • 9010a3e Initialize project base
  • aedfaf6 Add LogLevel and LogMetadata
  • 8eb2067 Add binary encoding for log arguments
  • cde37e3 Add binary decoding for log arguments
  • 4b20505 Add lock-free SPSC ring buffer
  • 20c93e8 Add ConsoleSink and PatternFormatter
  • 920943a Add BackendWorker
  • aae8bfb Add Logger and logging macros
  • 15f9d67 Add basic_usage example
  • 9f72f98 Add Logger::preallocate() for explicit per-thread queue init
  • bbdedfe Add RTSan annotations and integration test
§🔄️ Changes
  • 0ea01d8 Avoid transmute for LogLevel conversions
§🧪 Tests
  • 98955eb Run default usage test every time
§🧰 Tasks
  • bde7e2e Add CODEOWNERS file
§🛠 Build
  • d56db1a Add generate-changelog step with JReleaser
  • 9aa0165 Add a simple CI pipeline that runs lint and test
  • e3471bb Add auto-approve bot for self reviews
  • 9f9ef96 Switch test runner to cargo-nextest
  • 4949be7 Enable ANSI color output for all Cargo commands
  • 25acfda Add realtime-sanitize job to GitHub Actions
  • 3fc1482 Run ThreadSanitizer to detect data races
  • 99b48a3 Run AddressSanitizer to detect memory errors
  • b12c3c4 Add an aggregating just command to run all sanitizers
  • 22ad7ad Fail on warnings in the dedicated lint stage
  • 3b3efc7 Add a doc-check command
  • 3aa44e0 Clean up package metadata before publishing
  • 3635d0a Add automatic deployment to crates.io on tag build
§📝 Documentation
  • 6d87199 Add NOTICES file attributing Quill logging library
  • a41430b Add architecture overview and quick start to README
  • 2bbeb07 Add CLAUDE.md with project context for AI sessions
  • 5d5db06 Document RTSan validation for insomnilog and integrators
  • 22f8d9f Improve docs before publishing first version

§👥 Contributors

We’d like to thank the following people for their contributions:

  • Dwayne Steinke
  • csph

Macros§

log_debug
Logs a message at the Debug level.
log_error
Logs a message at the Error level.
log_info
Logs a message at the Info level.
log_trace
Logs a message at the Trace level.
log_warn
Logs a message at the Warning level.

Structs§

AlreadyStarted
Error returned by start when the backend has already been initialised in this process.
BackendOptions
Configuration for the backend worker thread.
ConsoleSink
Writes formatted records to a Write destination.
Logger
Named logger: a registered, identity-bearing object holding a pinned list of sinks plus an atomic level filter.
LoggerAlreadyRegistered
Error returned by crate::create_logger when a logger is already registered under the given name.
NullSink
A no-op Sink that silently discards every record.
PatternFormatter
Default Formatter implementation that renders records using a configurable pattern string.
ShutdownGuard
RAII guard returned from start. Tears the backend down on drop.
SinkAlreadyRegistered
Error returned by create_sink when a sink is already registered under the given name.

Enums§

InvalidPatternError
Returned by PatternFormatter::new when the pattern string is invalid.
LogLevel
Severity level for a log record.

Traits§

CustomEncode
Trait for user-defined types that can be encoded into the SPSC queue.
Formatter
Renders a LogRecord into human-readable bytes for a text sink.
Sink
Receives LogRecords from the backend worker and decides their output shape.

Functions§

create_logger
Creates a new logger under name with the given sinks and level.
get_logger
Returns the logger registered under name, if any.
get_sink
Returns the sink registered under name, if any.
preallocate_thread
Eagerly registers the calling thread with the backend, paying the queue allocation and context push upfront rather than on the first log call.
register_sink
Registers sink under name.
shutdown
Drains and tears the backend down.
start
Initialises the process-wide backend.

Type Aliases§

Decoder
Function-pointer signature used by CustomEncode decoders.