📝 Add async logger composition API docs

This commit is contained in:
Nanaloveyuki
2026-05-12 13:51:08 +08:00
parent 80f15cd455
commit f172305453
7 changed files with 561 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
---
name: async-logger-child
group: api
category: async
update-time: 20260512
description: Derive a child async logger by composing the current target with a child target segment.
key-word:
- async
- logger
- target
- public
---
## Async-logger-child
Create a child async logger by composing the current target with another target segment. This is the standard API for hierarchical async logger naming such as `app.worker` or `service.http.client`.
### Interface
```moonbit
pub fn[S] AsyncLogger::child(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Parent async logger whose target should be extended.
- `target : String` - Child target segment or suffix.
#### output
- `AsyncLogger[S]` - A new async logger whose default target is the composed child path.
### Explanation
Detailed rules explaining key parameters and behaviors
- If the parent target is empty, the child target becomes the full target.
- If the child target is empty, the parent target is preserved.
- If both are non-empty, they are joined with `.`.
- Queue settings, sink wiring, and runtime behavior are preserved in the returned logger.
### How to Use
Here are some specific examples provided.
#### When Need Hierarchical Async Targets
When subsystem logs should stay grouped under one async namespace:
```moonbit
let logger = async_logger(console_sink(), target="service")
let worker = logger.child("worker")
```
In this example, `worker` emits under `service.worker` while keeping the same async queue behavior.
#### When Build Deep Async Scopes Step By Step
When deeper target composition should remain readable:
```moonbit
let http = async_logger(console_sink(), target="app")
.child("http")
.child("client")
```
In this example, the final logger target becomes `app.http.client`.
### Error Case
e.g.:
- If `target` is empty, the returned logger keeps the original parent target.
- If callers need complete replacement rather than composition, `with_target(...)` should be used instead.
### Notes
1. This is the preferred API for hierarchical async logger naming.
2. Composition changes the target only and does not rebuild the queue or sink.
@@ -0,0 +1,84 @@
---
name: async-logger-with-context-fields
group: api
category: async
update-time: 20260512
description: Attach reusable structured fields to an async logger so every queued record inherits them.
key-word:
- async
- logger
- fields
- public
---
## Async-logger-with-context-fields
Bind shared structured fields to an async logger. This is the standard way to attach stable metadata such as service name, component, region, or subsystem identity without repeating them for every async log call.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_context_fields(
self : AsyncLogger[S],
fields : Array[@bitlogger.Field],
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger that should gain shared fields.
- `fields : Array[Field]` - Structured fields that will be prepended to each emitted record.
#### output
- `AsyncLogger[S]` - A new async logger carrying the shared field set.
### Explanation
Detailed rules explaining key parameters and behaviors
- Context fields are merged during `log(...)` before enqueue.
- When a log call also passes per-record fields, the context fields are placed before those per-call fields.
- This API returns a new logger value; it does not mutate the original async logger.
- Unlike synchronous `Logger::with_context_fields(...)`, this async variant stores fields directly on `AsyncLogger` instead of changing the visible sink type.
### How to Use
Here are some specific examples provided.
#### When Need Stable Async Service Metadata
When every queued record should carry service-level metadata:
```moonbit
let logger = async_logger(console_sink(), target="billing")
.with_context_fields([
@bitlogger.field("service", "billing"),
@bitlogger.field("region", "cn"),
])
```
In this example, both fields are attached before each record enters the queue.
#### When Build Child Async Loggers For Subsystems
When a subsystem has both a target and fixed fields:
```moonbit
let worker = async_logger(console_sink(), target="app")
.child("worker")
.with_context_fields([@bitlogger.field("component", "worker")])
```
In this example, target composition and field binding stay separate but work together cleanly.
### Error Case
e.g.:
- If `fields` is empty, the logger remains valid and just adds no extra metadata.
- If duplicate field keys are provided, all fields are still emitted; conflict handling is left to the consumer side.
### Notes
1. Use this for stable metadata, not highly dynamic event-specific values.
2. This async variant preserves the visible `AsyncLogger[S]` type while still injecting shared fields.
+82
View File
@@ -0,0 +1,82 @@
---
name: async-logger-with-filter
group: api
category: async
update-time: 20260512
description: Attach predicate-based filtering to an async logger so only matching records reach the queue.
key-word:
- async
- logger
- filter
- public
---
## Async-logger-with-filter
Attach a record predicate to an async logger so only matching records are enqueued. This is the main API for async routing-by-predicate without rewriting sink implementations.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_filter(
self : AsyncLogger[S],
predicate : (@bitlogger.Record) -> Bool,
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger to constrain.
- `predicate : (Record) -> Bool` - Record predicate that decides whether a record should be enqueued.
#### output
- `AsyncLogger[S]` - A new async logger that only enqueues matching records.
### Explanation
Detailed rules explaining key parameters and behaviors
- Filtering happens after record construction and patch application but before enqueue.
- Existing filter logic is preserved and combined with the new predicate using logical `and`.
- The original async logger is not mutated.
- Filtering avoids unnecessary queue pressure for records that should never be delivered.
### How to Use
Here are some specific examples provided.
#### When Keep Only One Async Target Namespace
When an async logger should only enqueue records from a specific subsystem:
```moonbit
let logger = async_logger(console_sink(), target="service")
.with_filter(@bitlogger.target_has_prefix("service.api"))
```
In this example, non-matching records are dropped before they reach the async queue.
#### When Combine Several Async Filter Rules
When filtering depends on multiple conditions:
```moonbit
let logger = async_logger(console_sink(), target="api")
.with_filter(fn(rec) {
rec.level.enabled(@bitlogger.Level::Warn) && rec.message.contains("timeout")
})
```
In this example, only warning-or-higher timeout records are enqueued.
### Error Case
e.g.:
- If the predicate always returns `false`, the logger silently drops every record before enqueue.
- If the predicate always returns `true`, the wrapper behaves like a pass-through filter.
### Notes
1. Use this API for selection logic, not record mutation.
2. Async filtering is especially useful when queue capacity should be reserved for high-value records.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-logger-with-min-level
group: api
category: async
update-time: 20260512
description: Replace the async logger minimum enabled level so lower-severity records are skipped before enqueue.
key-word:
- async
- logger
- level
- public
---
## Async-logger-with-min-level
Replace the async logger's minimum enabled level. This API controls the first gate checked before record creation and queue insertion.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_min_level(
self : AsyncLogger[S],
min_level : @bitlogger.Level,
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger whose level threshold should change.
- `min_level : Level` - New minimum enabled level.
#### output
- `AsyncLogger[S]` - A new async logger value carrying the updated threshold.
### Explanation
Detailed rules explaining key parameters and behaviors
- `log(...)` checks `is_enabled(level)` before creating a record or touching the queue.
- Lower-severity records below `min_level` are skipped before enqueue.
- This API replaces the logger threshold and does not alter queue configuration.
- The returned logger keeps the same sink, target, and timestamp settings.
### How to Use
Here are some specific examples provided.
#### When Raise Async Noise Floor In Production
When only warning and error records should reach the async queue:
```moonbit
let logger = async_logger(console_sink())
.with_min_level(@bitlogger.Level::Warn)
```
In this example, lower-severity records are skipped before queue pressure increases.
#### When Derive A More Verbose Async Branch
When one branch of code should keep a different threshold:
```moonbit
let base = async_logger(console_sink(), min_level=@bitlogger.Level::Info)
let debug_logger = base.with_min_level(@bitlogger.Level::Debug)
```
In this example, the async sink and queue settings are reused while the threshold changes per logger value.
### Error Case
e.g.:
- If `min_level` is set too high, expected lower-severity diagnostics may disappear before they ever enter the queue.
- If callers need richer predicate logic than a simple threshold, `with_filter(...)` should be used instead.
### Notes
1. This API reduces async queue pressure by dropping disabled levels before enqueue.
2. Use it before adding more complex async filtering rules.
+82
View File
@@ -0,0 +1,82 @@
---
name: async-logger-with-patch
group: api
category: async
update-time: 20260512
description: Attach record transformation logic to an async logger before records reach the queue.
key-word:
- async
- logger
- patch
- public
---
## Async-logger-with-patch
Attach a record patch to an async logger so each record is transformed before filtering and enqueue. This is the main API for async record rewriting without changing the sink implementation.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_patch(
self : AsyncLogger[S],
patch : @bitlogger.RecordPatch,
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger to wrap.
- `patch : RecordPatch` - Transformation applied to each record before enqueue.
#### output
- `AsyncLogger[S]` - A new async logger that rewrites each record before filtering and queue insertion.
### Explanation
Detailed rules explaining key parameters and behaviors
- Patch logic runs after record creation and field merging but before filtering and enqueue.
- Existing patch logic is preserved and composed so the new patch wraps the current one.
- The original async logger is not mutated.
- Patching can normalize, redact, or enrich records before they consume queue capacity.
### How to Use
Here are some specific examples provided.
#### When Need Async Record Enrichment
When records should gain stable extra data before enqueue:
```moonbit
let logger = async_logger(console_sink())
.with_patch(@bitlogger.append_fields([
@bitlogger.field("channel", "async"),
]))
```
In this example, the added fields are part of the record before filtering and queue insertion.
#### When Need Redaction Before Queueing
When sensitive fields should be removed early:
```moonbit
let logger = async_logger(console_sink())
.with_patch(@bitlogger.redact_fields(["token", "password"]))
```
In this example, sensitive values are rewritten before they enter the async pipeline.
### Error Case
e.g.:
- If the patch removes or rewrites important data incorrectly, later filters and sinks will only see the patched version.
- If callers need selection logic rather than transformation, `with_filter(...)` should be used instead.
### Notes
1. Use patches for transformation, not filtering decisions.
2. Redaction before enqueue helps keep sensitive data out of the queued pipeline.
+78
View File
@@ -0,0 +1,78 @@
---
name: async-logger-with-target
group: api
category: async
update-time: 20260512
description: Replace the default target carried by an async logger so later records inherit a new target namespace.
key-word:
- async
- logger
- target
- public
---
## Async-logger-with-target
Replace the async logger's default target. This API retargets later enqueue operations without changing the queue, overflow policy, or sink wiring.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_target(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger whose default target should be replaced.
- `target : String` - New default target namespace.
#### output
- `AsyncLogger[S]` - A new async logger value carrying the updated target.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned logger keeps the same sink, queue state, overflow policy, flush policy, and lifecycle flags.
- This API replaces the default target instead of composing it.
- Per-call `target?` arguments on `log(...)` can still override the default target.
- The original logger value is not mutated.
### How to Use
Here are some specific examples provided.
#### When Need A Stable Async Target Namespace
When one async logger should always emit under a fixed target:
```moonbit
let logger = async_logger(console_sink())
.with_target("service.worker")
```
In this example, later async log calls inherit `service.worker` unless a call overrides the target explicitly.
#### When Reuse One Async Setup Across Namespaces
When multiple subsystem loggers should share the same async queue behavior:
```moonbit
let base = async_logger(console_sink(), config=AsyncLoggerConfig::new(max_pending=64))
let api = base.with_target("api")
let jobs = base.with_target("jobs")
```
In this example, target routing changes without rebuilding the async runtime configuration.
### Error Case
e.g.:
- If `target` is empty, the logger remains valid and later records default to an empty target.
- If callers need hierarchical target composition rather than replacement, `child(...)` is the better API.
### Notes
1. Use this API for replacement, not parent-child target composition.
2. It is useful when several subsystems should share one async queue policy.
+77
View File
@@ -0,0 +1,77 @@
---
name: async-logger-with-timestamp
group: api
category: async
update-time: 20260512
description: Enable or disable automatic timestamp capture for records emitted by an async logger.
key-word:
- async
- logger
- timestamp
- public
---
## Async-logger-with-timestamp
Enable or disable automatic timestamp capture on async log emission. This API controls whether `AsyncLogger::log(...)` records the current time before enqueue or leaves the timestamp at `0UL`.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_timestamp(self : AsyncLogger[S], enabled~ : Bool = true) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger whose timestamp behavior should change.
- `enabled : Bool` - Whether emitted records should capture current time automatically.
#### output
- `AsyncLogger[S]` - A new async logger value with updated timestamp behavior.
### Explanation
Detailed rules explaining key parameters and behaviors
- When enabled, `log(...)` captures `@env.now()` before placing the record into the queue.
- When disabled, emitted records use `0UL` as the timestamp value.
- This setting affects later emitted records only.
- Queue, batching, and flush behavior are unchanged.
### How to Use
Here are some specific examples provided.
#### When Need Real Event Time Before Queueing
When downstream formatting or JSON output should include event time:
```moonbit
let logger = async_logger(console_sink())
.with_timestamp()
```
In this example, each record captures its timestamp before entering the async queue.
#### When Need Deterministic Async Records
When timestamps should be disabled for tests or reduced output:
```moonbit
let logger = async_logger(console_sink())
.with_timestamp(enabled=false)
```
In this example, queued records are emitted without runtime time capture.
### Error Case
e.g.:
- If timestamps are disabled, formatters that expect meaningful time values may show empty or zero-like timestamp output.
- If callers need timestamps only for one record, a separate logger value is usually clearer than toggling behavior repeatedly.
### Notes
1. This API controls record creation before enqueue, not formatter display policy.
2. It is useful for tests, deterministic snapshots, and production timing.