Expand description
§About insomnilog
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
Sinkreceives a decodedLogRecordand 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 aFormatterand 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:
-
Level check If the record is below the threshold the macro returns immediately with no further work.
-
Header construction — a
static LogMetadatais generated at the call site at compile time (file, line, level). The header written to the buffer also carries a raw*const Loggerpointer so the backend can reach the logger’s sink list without a registry lookup. -
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:
-
Decode — the raw bytes are decoded into a log record containing the log level, timestamp, source location, and typed argument values.
-
Logger dispatch — the
*const Loggerin the record header is dereferenced to obtain&Logger. This is safe because the logger registry holds a strongArc<Logger>for the entire process lifetime, guaranteeing the pointer remains valid. -
Sink fan-out — the worker iterates
logger.sinksand callsSink::write_record(&record)on each one whose level filter passes. The entire fan-out is wrapped instd::panic::catch_unwindso 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
| Field | Description | Example value |
|---|---|---|
level | Log level | INFO |
secs | Whole seconds of the timestamp (seconds since the Unix epoch) | 1234567890 |
millis | Millisecond component of the timestamp (0–999) | 042 |
file | Source file path | src/server.rs |
line | Source line number | 42 |
module | Rust module path of the call site | my_app::server |
logger | Name of the logger that emitted the record | payments |
message | Fully formatted log message | order #4291 confirmed |
§Format spec
A placeholder can carry an optional format spec after a colon:
{field:spec}. The spec controls padding and alignment.
| Spec element | Syntax | Description |
|---|---|---|
| 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 character | Any char before <, >, or ^ | Character used for padding (default: space) |
| Zero-pad | 0 before width | Use 0 as fill and force right-alignment |
| Width | Integer | Minimum 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
legacymodule - 4fefd0a Introduce
queuefromlegacy - 762c548 Add
LogLevelenum - 5645541 Add
LogMetadata - 7fdb972 Add
encodemodule - 9f35f6f Add
RecordHeaderto store record information - 96e6e69 Add
decodemodule - a6707b6 Add minimal
backendandlifecyclemodules - 1b99a39 Add
formatterwith default pattern implementation - fdb70f7 Add
Sinktrait andConsoleSink&NullSink - 3a46684 Allow registering and retrieving sinks globally
- eca32e0 Count sink errors and print them during backend shutdown
- 480fdcf Add
Loggerstruct - 78bb8b7 Allow creating and retrieving loggers globally
- 57226f2 Add
logger_ptrto theRecordHeader - 6e765f3 Split
DecodedRecordintoRawDecodedRecordandLogRecord - 866c2e6 Add
per_thread_queue - c1fdbf7 Add
BackendRunner - 571e8f9 Use
BackendRunnerinBackend - 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
legacymodule
§🐛 Fixes
- 9fd9a2b Use
Box<[UnsafeCell<u8>]>for SPSC ring buffer - 4e8bf0f Implement
ErrorforInvalidPatternError
§🔄️ 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_untilandRecordingSink - 55cebf4 Add end-to-end tests for general usability
§🧰 Tasks
- 45af80d Mark
newofRecordHeaderasmust_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
miritest execution intodefaultandslow - a85ff03 Configure
tagNameforJReleaserto correctly diff releases
§📝 Documentation
- 08f53ad Add architecture decision records (ADR)
- c65e61c Create usage based docs incl.
about,quick_startandarchitecture - 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
LogLevelandLogMetadata - 8eb2067 Add binary encoding for log arguments
- cde37e3 Add binary decoding for log arguments
- 4b20505 Add lock-free SPSC ring buffer
- 20c93e8 Add
ConsoleSinkandPatternFormatter - 920943a Add
BackendWorker - aae8bfb Add Logger and logging macros
- 15f9d67 Add
basic_usageexample - 9f72f98 Add
Logger::preallocate()for explicit per-thread queue init - bbdedfe Add
RTSanannotations and integration test
§🔄️ Changes
- 0ea01d8 Avoid
transmuteforLogLevelconversions
§🧪 Tests
- 98955eb Run default usage test every time
§🧰 Tasks
- bde7e2e Add
CODEOWNERSfile
§🛠 Build
- d56db1a Add
generate-changelogstep withJReleaser - 9aa0165 Add a simple CI pipeline that runs
lintandtest - 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
ThreadSanitizerto detect data races - 99b48a3 Run
AddressSanitizerto detect memory errors - b12c3c4 Add an aggregating
justcommand to run all sanitizers - 22ad7ad Fail on warnings in the dedicated lint stage
- 3b3efc7 Add a
doc-checkcommand - 3aa44e0 Clean up package metadata before publishing
- 3635d0a Add automatic deployment to
crates.ioon 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
RTSanvalidation 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
Debuglevel. - log_
error - Logs a message at the
Errorlevel. - log_
info - Logs a message at the
Infolevel. - log_
trace - Logs a message at the
Tracelevel. - log_
warn - Logs a message at the
Warninglevel.
Structs§
- Already
Started - Error returned by
startwhen the backend has already been initialised in this process. - Backend
Options - Configuration for the backend worker thread.
- Console
Sink - Writes formatted records to a
Writedestination. - Logger
- Named logger: a registered, identity-bearing object holding a pinned list of sinks plus an atomic level filter.
- Logger
Already Registered - Error returned by
crate::create_loggerwhen a logger is already registered under the given name. - Null
Sink - A no-op
Sinkthat silently discards every record. - Pattern
Formatter - Default
Formatterimplementation that renders records using a configurable pattern string. - Shutdown
Guard - RAII guard returned from
start. Tears the backend down on drop. - Sink
Already Registered - Error returned by
create_sinkwhen a sink is already registered under the given name.
Enums§
- Invalid
Pattern Error - Returned by
PatternFormatter::newwhen the pattern string is invalid. - LogLevel
- Severity level for a log record.
Traits§
- Custom
Encode - Trait for user-defined types that can be encoded into the SPSC queue.
- Formatter
- Renders a
LogRecordinto 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
namewith the givensinksandlevel. - 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
sinkundername. - shutdown
- Drains and tears the backend down.
- start
- Initialises the process-wide backend.
Type Aliases§
- Decoder
- Function-pointer signature used by
CustomEncodedecoders.