Async-logger
Create an AsyncLogger[S] on top of a sink and async queue configuration. This API is the main entry for queue-backed async logging, including overflow policy, batching, lifecycle control, and runtime observability.
Interface
pub fn[S] async_logger(
sink : S,
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
min_level~ : @bitlogger.Level = @bitlogger.Level::Info,
target~ : String = "",
flush~ : (S) -> Int raise = fn(_) { 0 },
) -> AsyncLogger[S] {}input
sink : S- Underlying sink used after queue drain.config : AsyncLoggerConfig- Queue size, overflow behavior, batching, linger, and flush policy.min_level : Level- Level gate applied before enqueue.target : String- Default target for emitted records.flush : (S) -> Int raise- Flush callback used by batch/shutdown flush policies and allowed to raise if sink flushing fails.
output
AsyncLogger[S]- A queue-backed async logger with lifecycle and state helpers.
Explanation
Detailed rules explaining key parameters and behaviors
async_logger(...)only builds the logger. Actual background draining is started byrun().async_logger(...)returns the fullAsyncLogger[S]surface directly. It is therefore the underlying constructor used by both application-facing async aliases and the narrowerLibraryAsyncLogger[S]wrapper line.- The constructed logger starts with
is_closed=false,is_running=false,has_failed=false,last_error="", and zeroed pending/dropped counters. - The constructed logger also keeps the core async target contract unchanged:
log(..., target=...)can override the target for one call, while fixed-level helpers such asinfo(...),warn(...), anderror(...)continue using the stored logger target unless code derives another logger first withwith_target(...)orchild(...). - Unlike synchronous
Logger, asyncwith_context_fields(...)andbind(...)preserve the visibleAsyncLogger[S]type because shared fields are stored directly on the async logger value instead of being modeled as a separate sink wrapper. ApplicationAsyncLoggerandApplicationTextAsyncLoggerare only alias names over concreteAsyncLogger[...]shapes, so they keep the same lifecycle, queue, failure, and state helpers without adding a wrapper layer.LibraryAsyncLogger[S]wraps anAsyncLogger[S]value instead of aliasing it. That library facade preserves queue-backed logging behavior, but it narrows the directly exposed helper surface until callers recover the full logger withto_async_logger().- In non-native targets, the implementation uses compatibility behavior while keeping the same public surface.
src-asyncis designed fornative / llvm / js / wasm / wasm-gc, but current release-facing local verification is stronger fornative / js / wasm / wasm-gcthan forllvm.llvmshould currently be read as experimental and locally unverified in this environment rather than as a stable checked target.flushis used only when batch or shutdown policy wants explicit flushing.- If the supplied flush callback raises, worker failure state is recorded through
has_failed()andlast_error(). wait_idle()is failure-aware rather than a pure backlog-to-zero guarantee. If a worker failure setshas_failed=true, waiting stops early and the logger can still reportpending_count() > 0until later cleanup or restart work happens.- A later
run()attempt starts by clearing stale failure state back tohas_failed=falseandlast_error=""before it resumes draining any backlog still left in the queue. - The exact behavior of late log attempts after closure is runtime-dependent, so callers should use lifecycle helpers like
is_closed()andshutdown()instead of assuming identical post-close enqueue semantics everywhere. - Queue overflow behavior depends on
AsyncOverflowPolicy.
How to Use
Here are some specific examples provided.
When Need Background Queue Drain
When your sink should not be written directly on the caller path:
let logger = async_logger(callback_sink(fn(rec) { println(rec.message) }))
@async.with_task_group(group => {
group.spawn_bg(() => logger.run())
logger.info("hello")
logger.shutdown()
})In this example, the worker drains queued records in the background and shutdown() waits for completion.
And the logging call path stays queue-oriented rather than direct-sink oriented.
When Need A One-call Target Override On The Root Async Logger
When async code should keep one logger value but emit a single record under a different target:
logger.log(@bitlogger.Level::Error, "boom", target="app.async.audit")In this example, the emitted record uses app.async.audit only for that one call.
And later info(...), warn(...), or error(...) calls still use the logger's stored target unless code derives another logger first with with_target(...) or child(...).
When Need Shared Context On A Root Async Logger
When async code should attach stable metadata to later queued records:
let contextual = logger.with_context_fields([@bitlogger.field("service", "billing")])In this example, the returned value still has the visible type AsyncLogger[S].
And that shape preservation is intentional because async context binding updates stored logger metadata instead of changing the exposed sink type.
When Need Configurable Overflow And Flush Behavior
When queue semantics matter for service durability and load:
let logger = async_logger(
console_sink(),
config=AsyncLoggerConfig::new(
max_pending=128,
overflow=AsyncOverflowPolicy::DropOldest,
max_batch=8,
flush=AsyncFlushPolicy::Batch,
),
)In this example, queue pressure and flush timing are both explicit.
Error Case
e.g.:
If the logger is closed, further enqueue attempts stop being normal active logging operations.
If queue drain fails internally, runtime state can reflect that through
has_failed()andlast_error().
Notes
async_logger(...)is the async counterpart toLogger::new(...).Use
state(),pending_count(), anddropped_count()for runtime diagnostics.Example entrypoint limitations such as
async fn mainsupport are separate from the library-level portability of this API.See target-verification.md for the current local verification matrix.
Pair this constructor with
run()andshutdown()when you need the full worker lifecycle rather than just a configured async logger value.Choose the facade name based on boundary intent: use
AsyncLogger[S]for the full surface,ApplicationAsyncLoggerorApplicationTextAsyncLoggerfor application-facing alias names, andLibraryAsyncLogger[S]when a package boundary should intentionally narrow what downstream code can call directly.