slog-async
Asynchronous background-thread drain that keeps slog-rs structured logging off your Rust application's hot path
Repository Health
Technical Analysis
slog-async is the reference asynchronous drain for the slog-rs structured logging ecosystem in Rust. Because serialization and I/O are typically the slowest parts of logging, slog-async hands log records off to a dedicated worker thread over a bounded channel, so the calling thread only pays the cost of a fast send rather than the full cost of formatting and writing a record.
It is deliberately positioned as a reasonable default implementation rather than a hardcoded part of slog-rs itself: the crate exposes Async and the lower-level AsyncCore so applications can either use it as-is or use its source as a starting point for a custom async logging strategy, with configurable channel size, overflow behavior (drop, drop-and-report, or block), and an optional AsyncGuard to guarantee flushing on drop.
What You Get
- An
Asyncdrain that wraps anyslog::Drainand moves its work to a background thread via a boundedcrossbeam-channel - Configurable overflow handling through
OverflowStrategy—Drop,DropAndReport(the default, which also logs a count of dropped records), orBlock - An
AsyncGuardreturned bybuild_with_guard()that flushes and joins the worker thread on drop, protecting against message loss from leftover references or panics - Lower-level
AsyncCore/AsyncCoreBuilderprimitives for building custom async drains with the same worker-thread plumbing - Optional
nested-valuesanddynamic-keysCargo features that mirror the corresponding slog crate features
Common Use Cases
- Wrapping a file- or network-backed slog-rs drain so writing log lines never blocks request-handling threads
- Keeping high-throughput services responsive under log bursts by capping in-flight records with a bounded channel and a defined overflow policy
- Guaranteeing logs are flushed before process exit by pairing
AsyncGuardwith arun()-then-exit()pattern, sincestd::process::exitskips destructors - Prototyping a custom async logging strategy by using
AsyncCoreas a documented starting point instead of writing worker-thread plumbing from scratch
Under The Hood
Architecture When Async::log() is called, it first flushes any pending “dropped records” counter via push_dropped(), then hands the Record to AsyncCore::log(), which serializes it into an owned AsyncRecord (using ToSendSerializer to convert borrowed key-value data into Send-safe owned values) and pushes it onto a crossbeam-channel created by AsyncCoreBuilder::spawn_thread(). A dedicated worker thread loops on rx.recv(), matching AsyncMsg::Record (replays the record into the wrapped drain via record.log_to(&drain)) or AsyncMsg::Finish (exits the loop), with the whole loop body wrapped in catch_unwind so a panic inside the wrapped drain is caught and reported to stderr instead of killing the worker thread silently.
Tech Stack Pure Rust with no async runtime dependency — concurrency is built directly on std::thread plus crossbeam-channel 0.5 for the bounded queue, thread_local 1 for per-thread sender caching, and take_mut 0.2 for in-place ownership swaps inside the serializer. The crate depends on slog 2.1 and forwards two optional Cargo features, nested-values and dynamic-keys, straight through to the corresponding slog features; minimum supported Rust version is declared as 1.59.0.
Code Quality Test coverage is a single integration test in lib.rs (test::integration_test) that exercises the full pipeline through a hand-written MockDrain backed by an mpsc channel, asserting on the exact formatted output for two log levels — there are no separate unit test files or benchmark suite. Error handling is idiomatic: an AsyncError enum (Full / Fatal) with From impls for the channel and mutex-poisoning error types, and an AsyncResult<T> alias used consistently across the public API. Naming and module layout follow slog-rs ecosystem conventions, and the code contains no unsafe blocks, relying on catch_unwind plus channel semantics for safety at the thread boundary.
API Design The public surface centers on two builder types, AsyncBuilder and the lower-level AsyncCoreBuilder, offering a fluent chan_size() / overflow_strategy() / thread_name() configuration chain before a final .build(), .build_no_guard(), or .build_with_guard() call. Getting started requires minimal code (Async::default(drain) plus slog’s .fuse() adapter), but productive use assumes familiarity with slog-rs’s own Drain trait and Logger construction, so the learning curve leans on understanding the wider slog-rs ecosystem rather than this crate in isolation.