mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-07-24 00:42:18 +00:00
142 lines
7.1 KiB
Markdown
142 lines
7.1 KiB
Markdown
---
|
|
name: async-logger
|
|
group: api
|
|
category: async
|
|
update-time: 20260614
|
|
description: Create an async logger with bounded queueing, overflow policy, lifecycle helpers, background run control, and a raising flush callback.
|
|
key-word:
|
|
- async
|
|
- logger
|
|
- queue
|
|
- public
|
|
---
|
|
|
|
## 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
|
|
|
|
```moonbit
|
|
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 by `run()`.
|
|
- `async_logger(...)` returns the full `AsyncLogger[S]` surface directly. It is therefore the underlying constructor used by both application-facing async aliases and the narrower `LibraryAsyncLogger[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 as `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
|
|
- Unlike synchronous `Logger`, async `with_context_fields(...)` and `bind(...)` preserve the visible `AsyncLogger[S]` type because shared fields are stored directly on the async logger value instead of being modeled as a separate sink wrapper.
|
|
- `ApplicationAsyncLogger` and `ApplicationTextAsyncLogger` are only alias names over concrete `AsyncLogger[...]` shapes, so they keep the same lifecycle, queue, failure, and state helpers without adding a wrapper layer.
|
|
- `LibraryAsyncLogger[S]` wraps an `AsyncLogger[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 with `to_async_logger()`.
|
|
- In non-native targets, the implementation uses compatibility behavior while keeping the same public surface.
|
|
- `src-async` is designed for `native / llvm / js / wasm / wasm-gc`, but current release-facing local verification is stronger for `native / js / wasm / wasm-gc` than for `llvm`.
|
|
- `llvm` should currently be read as experimental and locally unverified in this environment rather than as a stable checked target.
|
|
- `flush` is used only when batch or shutdown policy wants explicit flushing.
|
|
- If the supplied flush callback raises, worker failure state is recorded through `has_failed()` and `last_error()`.
|
|
- `wait_idle()` is failure-aware rather than a pure backlog-to-zero guarantee. If a worker failure sets `has_failed=true`, waiting stops early and the logger can still report `pending_count() > 0` until later cleanup or restart work happens.
|
|
- A later `run()` attempt starts by clearing stale failure state back to `has_failed=false` and `last_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()` and `shutdown()` 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:
|
|
```moonbit
|
|
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:
|
|
```moonbit
|
|
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:
|
|
```moonbit
|
|
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:
|
|
```moonbit
|
|
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()` and `last_error()`.
|
|
|
|
### Notes
|
|
|
|
1. `async_logger(...)` is the async counterpart to `Logger::new(...)`.
|
|
|
|
2. Use `state()`, `pending_count()`, and `dropped_count()` for runtime diagnostics.
|
|
|
|
3. Example entrypoint limitations such as `async fn main` support are separate from the library-level portability of this API.
|
|
|
|
4. See [target-verification.md](./target-verification.md) for the current local verification matrix.
|
|
|
|
5. Pair this constructor with `run()` and `shutdown()` when you need the full worker lifecycle rather than just a configured async logger value.
|
|
|
|
6. Choose the facade name based on boundary intent: use `AsyncLogger[S]` for the full surface, `ApplicationAsyncLogger` or `ApplicationTextAsyncLogger` for application-facing alias names, and `LibraryAsyncLogger[S]` when a package boundary should intentionally narrow what downstream code can call directly.
|