104 Commits

Author SHA1 Message Date
Nanaloveyuki c6962fe71a 📝 add generated package interface snapshots 2026-07-17 15:53:21 +08:00
Nanaloveyuki 74b9815bab 🧹 apply moon fmt and async mbti refresh 2026-07-17 15:53:21 +08:00
Nanaloveyuki 6ee0d97aac 💚 enhance CI matrix and coverage checks 2026-07-17 15:53:21 +08:00
Nanaloveyuki 465e5ecd5d fix async js shutdown test expectation 2026-07-17 15:53:21 +08:00
Nanaloveyuki 52b26b31f2 🔖 prepare 1.1.0(0.7.0) sync release notes 2026-07-17 15:53:21 +08:00
Nanaloveyuki 9405a04c13 📝 align sync API docs with owner packages 2026-07-17 15:53:21 +08:00
Nanaloveyuki e5a048090f split sync tests by behavior boundary 2026-07-17 15:53:21 +08:00
Nanaloveyuki e3097ba4fc ♻️ sync owner migration and facade thinning 2026-07-17 15:53:21 +08:00
Nanaloveyuki 5d9924026e 🐛 修复异常 Runtime Queue 2026-07-17 15:53:20 +08:00
Nanaloveyuki bdbb161865 添加 Logger build测试 2026-07-17 15:53:20 +08:00
Nanaloveyuki 83371bb4d5 📝 补充runtime文档 2026-07-17 15:53:20 +08:00
Nanaloveyuki 0e02f3d2cf 📝 Fix policy test and doc 2026-07-17 15:53:20 +08:00
Nanaloveyuki 46e87403bf 🔖 bump version to 0.6.1 2026-07-17 15:53:20 +08:00
Nanaloveyuki 3bdab60412 📝 Add 1.0.1(0.6.1) change notes for wide file rotation 2026-07-17 15:53:20 +08:00
Nanaloveyuki e001315319 Add Int64 file rotation path and JSON roundtrip 2026-07-17 15:53:20 +08:00
Nanaloveyuki 93e9bd966d 📝 添加Wiki并修改1.0.0文件名 2026-07-17 15:53:20 +08:00
Nanaloveyuki e8a2c76652 🩹 Fix the major version must be 0 2026-07-17 15:53:20 +08:00
Nanaloveyuki c8023b0ded 🔊 Update 1.0.0 2026-07-17 15:53:20 +08:00
Nanaloveyuki 7557e37cc8 ⬆️ Update Async and Mbt Version 2026-07-17 15:53:19 +08:00
Nanaloveyuki 637962a934 📝 add VitePress site and deployment workflow 2026-06-14 16:00:00 +08:00
Nanaloveyuki 265cd69ea9 📝 consolidate logger API and async lifecycle guidance 2026-06-14 14:00:00 +08:00
Nanaloveyuki 4f2ad097af consolidate async and native sink coverage 2026-06-14 10:00:00 +08:00
Nanaloveyuki 81385d5c0b consolidate async and native runtime updates 2026-06-14 09:00:00 +08:00
Nanaloveyuki 7d2d20c31e 📝 refine configured runtime docs 2026-06-13 23:58:00 +08:00
Nanaloveyuki 1e568f55cd 📝 refine configured file setters docs 2026-06-13 23:53:41 +08:00
Nanaloveyuki 4e00c5eac5 📝 refine configured file status docs 2026-06-13 23:51:37 +08:00
Nanaloveyuki 01eb5c6f8d 📝 refine configured file control docs 2026-06-13 23:49:45 +08:00
Nanaloveyuki dddad3d3d0 📝 refine configured file policy docs 2026-06-13 23:47:23 +08:00
Nanaloveyuki 8427cc6cf3 📝 refine configured runtime file state docs 2026-06-13 23:45:46 +08:00
Nanaloveyuki ce3eb06c1c 📝 clarify runtime file export examples 2026-06-13 23:44:05 +08:00
Nanaloveyuki 61094732a8 📝 align runtime file examples 2026-06-13 23:42:09 +08:00
Nanaloveyuki 342d08377f 📝 refresh runtime file constructors docs 2026-06-13 23:39:24 +08:00
Nanaloveyuki 56ba583680 📝 refresh runtime file export docs 2026-06-13 23:37:55 +08:00
Nanaloveyuki 06541a578c 📝 refresh runtime file model docs 2026-06-13 23:36:05 +08:00
Nanaloveyuki d47109aa54 📝 remove duplicate constructor docs 2026-06-13 23:31:41 +08:00
Nanaloveyuki bb39091830 📝 document formatter config constructor 2026-06-13 23:25:56 +08:00
Nanaloveyuki c4ebf09a7b 📝 document config constructors 2026-06-13 23:23:58 +08:00
Nanaloveyuki 78fd2124da 📝 document runtime sink policy setter 2026-06-13 23:21:24 +08:00
Nanaloveyuki 10555b3580 📝 document runtime sink file state 2026-06-13 23:19:03 +08:00
Nanaloveyuki 793e9bfc83 📝 document runtime sink file policies 2026-06-13 23:16:37 +08:00
Nanaloveyuki 419b79ac50 📝 document runtime sink failure counters 2026-06-13 23:14:22 +08:00
Nanaloveyuki c8e4cee9d6 📝 document runtime sink file flush methods 2026-06-13 23:11:39 +08:00
Nanaloveyuki a3ecb6676d 📝 document runtime sink rotation methods 2026-06-13 23:09:40 +08:00
Nanaloveyuki f1b7a86cf6 📝 document runtime sink reopen methods 2026-06-13 23:05:08 +08:00
Nanaloveyuki 65605f6b59 📝 document runtime sink append path methods 2026-06-13 22:59:12 +08:00
Nanaloveyuki bf7512d113 📝 document runtime sink auto flush methods 2026-06-13 22:56:51 +08:00
Nanaloveyuki 6b59398bc6 📝 document text formatter config conversion 2026-06-13 22:54:34 +08:00
Nanaloveyuki 1c83cf2ba1 📝 document runtime file state constructor 2026-06-13 22:52:29 +08:00
Nanaloveyuki c5f7dda2aa 📝 document file sink policy constructor 2026-06-13 22:50:45 +08:00
Nanaloveyuki 18bc3dbdb0 📝 document file sink state constructor 2026-06-13 22:48:43 +08:00
Nanaloveyuki e1ba2fb835 📝 document async state constructors 2026-06-13 22:46:47 +08:00
Nanaloveyuki 45a596cce1 📝 document async logger build config constructor 2026-06-13 22:44:36 +08:00
Nanaloveyuki 0df565d454 📝 document queued sink methods 2026-06-13 22:42:41 +08:00
Nanaloveyuki 86b99fe004 📝 document buffered sink methods 2026-06-13 22:40:26 +08:00
Nanaloveyuki 1ebf31945c 📝 document runtime sink state surface 2026-06-13 22:38:14 +08:00
Nanaloveyuki a17f50db88 📝 document runtime sink control methods 2026-06-13 22:35:36 +08:00
Nanaloveyuki 78ca9e60a9 📝 document file sink policy rotation methods 2026-06-13 22:32:45 +08:00
Nanaloveyuki 138461871a 📝 document file sink counters and resets 2026-06-13 22:29:11 +08:00
Nanaloveyuki afce0c7ce2 📝 document file sink reopen methods 2026-06-13 22:26:05 +08:00
Nanaloveyuki e34065441f 📝 document file sink policy state methods 2026-06-13 22:23:35 +08:00
Nanaloveyuki 78010b2524 📝 document file sink policy mutators 2026-06-13 22:18:35 +08:00
Nanaloveyuki 8373c9c6c1 📝 document file sink policy accessors 2026-06-13 22:16:32 +08:00
Nanaloveyuki 9a3fbcc793 📝 document file sink lifecycle methods 2026-06-13 22:14:13 +08:00
Nanaloveyuki 038c37992f 📝 document formatter color and registry methods 2026-06-13 22:11:32 +08:00
Nanaloveyuki 3dc5759a73 📝 document formatter target and field markup methods 2026-06-13 22:09:02 +08:00
Nanaloveyuki 1c8a8c24a9 📝 document formatter message markup methods 2026-06-13 22:06:30 +08:00
Nanaloveyuki 0f0b4c4321 📝 document style tag registry methods 2026-06-13 22:02:04 +08:00
Nanaloveyuki 248406ce75 🐛 hide internal file backend wrappers 2026-06-13 21:58:38 +08:00
Nanaloveyuki 212bb76b56 📝 document formatter rendering helpers 2026-06-13 21:55:22 +08:00
Nanaloveyuki 0dd4c6b080 📝 document remaining sink root types 2026-06-13 21:49:54 +08:00
Nanaloveyuki 27886e1eba 📝 document wrapper sink types 2026-06-13 21:47:40 +08:00
Nanaloveyuki fd7defbb93 📝 document routing sink types 2026-06-13 21:45:17 +08:00
Nanaloveyuki 1a30562a28 📝 document callback sink types 2026-06-13 21:42:30 +08:00
Nanaloveyuki 0418fcab60 📝 document console sink types 2026-06-13 21:40:40 +08:00
Nanaloveyuki 6ab828c4ce 📝 document formatted sink constructors 2026-06-13 21:37:24 +08:00
Nanaloveyuki a9725a8d33 📝 document root logger types 2026-06-13 21:34:56 +08:00
Nanaloveyuki b60df648ff 📝 document library logger facade types 2026-06-13 21:32:37 +08:00
Nanaloveyuki a983af2e0b 📝 document configured logger alias 2026-06-13 21:29:39 +08:00
Nanaloveyuki 041385712b 📝 document file capability and rotation json helpers 2026-06-13 21:27:21 +08:00
Nanaloveyuki 0566ebbabd 📝 document field and file rotation aliases 2026-06-13 21:24:34 +08:00
Nanaloveyuki 839c03ec57 📝 document sync config model aliases 2026-06-13 21:21:20 +08:00
Nanaloveyuki 9c00ee9341 📝 document formatter model aliases 2026-06-13 21:18:25 +08:00
Nanaloveyuki dea63b1ed6 📝 document async config model aliases 2026-06-13 21:16:18 +08:00
Nanaloveyuki d4ec733b64 📝 document async state model aliases 2026-06-13 21:13:59 +08:00
Nanaloveyuki b47f95c918 📝 document remaining record helpers 2026-06-13 21:10:58 +08:00
Nanaloveyuki e01eed5c44 📝 document record transformation apis 2026-06-13 21:08:25 +08:00
Nanaloveyuki 17573b9f9e 📝 document record alias 2026-06-13 21:05:49 +08:00
Nanaloveyuki 3321ebef8b 📝 document file runtime model aliases 2026-06-13 21:03:32 +08:00
Nanaloveyuki 12f6ce4c2b 📝 document formatter mode aliases 2026-06-13 21:00:34 +08:00
Nanaloveyuki 17c1b95a61 📝 document public config and level aliases 2026-06-13 20:56:46 +08:00
Nanaloveyuki 3881ca505a 📝 document public function type aliases 2026-06-13 20:51:03 +08:00
Nanaloveyuki 554318a7b0 📝 document application logger aliases 2026-06-13 20:48:22 +08:00
Nanaloveyuki 00e06c302f 📝 document async library facade write apis 2026-06-13 20:45:41 +08:00
Nanaloveyuki cff282db2f 📝 document sync library facade write apis 2026-06-13 20:42:12 +08:00
Nanaloveyuki 13f0a24336 📝 document async library facade composition apis 2026-06-13 20:39:24 +08:00
Nanaloveyuki 4e32a7350a 📝 document sync library facade composition apis 2026-06-13 20:36:45 +08:00
Nanaloveyuki 76dbc421eb 📝 document library facade conversion apis 2026-06-13 20:05:22 +08:00
Nanaloveyuki fbd63b5e4a 🔖 bump version to 0.5.2 2026-06-07 20:49:10 +08:00
Nanaloveyuki a3d0a695de 🔖 prepare 0.5.2 release notes 2026-06-07 20:34:25 +08:00
Nanaloveyuki c0fe7999c4 🔖 prepare 0.5.1 release notes 2026-05-21 14:56:04 +08:00
Nanaloveyuki 5c8067d009 📝 simplify bilingual readme copy 2026-05-21 14:22:49 +08:00
Nanaloveyuki 6c19708d7b 📝 clarify api target verification status 2026-05-21 14:14:15 +08:00
Nanaloveyuki 3eee5893f5 tighten cross-target capability contracts 2026-05-21 14:13:17 +08:00
Nanaloveyuki 3e8d4c50a9 📝 align portable examples and release messaging 2026-05-21 14:12:56 +08:00
477 changed files with 41318 additions and 5622 deletions
+151 -31
View File
@@ -7,70 +7,190 @@ on:
pull_request:
jobs:
moonbit:
quality-matrix:
name: Quality (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup MoonBit (Linux/macOS)
if: runner.os != 'Windows'
shell: bash
run: |
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"
- name: Setup MoonBit (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
irm https://cli.moonbitlang.com/install/powershell.ps1 | iex
"$HOME/.moon/bin" | Out-File -FilePath $env:GITHUB_PATH -Append
- name: Setup MSYS2 toolchain (Windows)
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: >-
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-openssl
- name: Show tool versions
shell: bash
run: |
moon version --all
- name: Update Moon registry
shell: bash
run: |
moon update
- name: Check formatting and exported interfaces
shell: bash
run: |
moon fmt --check
moon info
git diff --exit-code
- name: Root checks and tests
shell: bash
run: |
moon check --deny-warn
moon test --deny-warn
moon check --target native --deny-warn
moon check --target wasm-gc --deny-warn
moon check --target js --deny-warn
- name: Run sync example
shell: bash
run: |
moon run examples/basic
- name: Check async example native
if: runner.os != 'Windows'
shell: bash
run: |
moon check examples/async_basic --target native --deny-warn
async-cross-targets:
name: Async cross-targets (Ubuntu)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install MoonBit
- name: Setup MoonBit
shell: bash
run: |
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"
- name: Show tool versions
shell: bash
run: |
moon version
moon version --all
- name: Update Moon registry
shell: bash
run: |
moon update
- name: Check bitlogger
- name: Check async package on wasm-gc and js
shell: bash
run: |
moon check
moon check src-async --target wasm-gc --deny-warn
moon check src-async --target js --deny-warn
- name: Test bitlogger
- name: Test async package on wasm-gc and js
shell: bash
run: |
moon test
moon test src-async --target wasm-gc --deny-warn
moon test src-async --target js --deny-warn
- name: Check bitlogger native
run: |
moon check --target native
async-native:
name: Async native (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
- name: Check bitlogger wasm-gc
run: |
moon check --target wasm-gc
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check bitlogger js
- name: Setup MoonBit
shell: bash
run: |
moon check --target js
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"
- name: Check bitlogger_async native
- name: Show tool versions
shell: bash
run: |
moon check src-async --target native
moon version --all
- name: Check bitlogger_async wasm-gc
- name: Update Moon registry
shell: bash
run: |
moon check src-async --target wasm-gc
moon update
- name: Check bitlogger_async js
- name: Check async native path
shell: bash
run: |
moon check src-async --target js
moon check src-async --target native --deny-warn
- name: Test bitlogger_async wasm-gc
- name: Test async native path
shell: bash
run: |
moon test src-async --target wasm-gc
moon test src-async --target native --deny-warn
- name: Test bitlogger_async js
run: |
moon test src-async --target js
coverage:
name: Coverage (Ubuntu)
runs-on: ubuntu-latest
- name: Run basic example
run: |
moon run examples/basic
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check async example native
- name: Setup MoonBit
shell: bash
run: |
moon check examples/async_basic --target native
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"
- name: Show tool versions
shell: bash
run: |
moon version --all
- name: Update Moon registry
shell: bash
run: |
moon update
- name: Root coverage (default target)
shell: bash
run: |
moon coverage clean
moon test --deny-warn --enable-coverage
moon coverage report -f summary
moon coverage analyze
- name: Root coverage (native target)
shell: bash
run: |
moon coverage clean
moon test --deny-warn --target native --enable-coverage
moon coverage report -f summary
moon coverage analyze
+61
View File
@@ -0,0 +1,61 @@
name: Docs
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: docs-pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Build docs site
env:
DOCS_BASE: /
run: npm run docs:build
- name: Configure Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/configure-pages@v5
- name: Upload Pages artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+7 -1
View File
@@ -16,9 +16,15 @@ desktop.ini
*.log
*.tmp
*.bak
node_modules/
# Dev Documentations
docs/dev/*
# Docs site build artifacts
docs/.vitepress/cache/
docs/.vitepress/dist/
docs/.vitepress/generated/
# vibecoding
AGENTS/
AGENTS/
+42 -67
View File
@@ -1,49 +1,23 @@
<html>
<div style="display: flex; justify-content: center; align-items: center; height: 15vh;">
<h3 title="https://moonbitlang.github.io/OSC2026/index.html#top">2026 MoonBit 国产基础软件生态开源大赛参赛作品</h3>
</div>
<div style="display: flex; justify-content: center; align-items: center; height: 8vh;">
<a href="https://mooncakes.io/docs/Nanaloveyuki/BitLogger" title="点击前往 Mooncake 页面"><b>Mooncake@Nanaloveyuki/BitLogger</b></a>
</div>
<div style="display: flex; justify-content: center; align-items: center; height: 2vh;">
<b>中文 | <a href="./docs/README-en.md">English</a></b>
</div>
</html>
# BitLogger
## 📖 介绍
BitLogger 是一个使用 MoonBit 编写的结构化日志库,适合命令行工具、服务和需要统一日志输出的项目。
BitLogger 是一个使用 MoonBit 编写的结构化日志库,目标是提供可组合、可配置、可跨端编译的日志基础设施。
- [Mooncake 文档页](https://mooncakes.io/docs/Nanaloveyuki/BitLogger)
- [English README](./docs/README-en.md)
- [Wiki](https://bitlogger.naloveyuki.top)
## 🧭 后端兼容
## 介绍
| 模块 / 能力 | native / llvm | js / wasm / wasm-gc |
| --- | --- | --- |
| `src` 主包 | 支持 | 支持 |
| `file_sink(...)` | 支持 | 不支持, `native_files_supported()` 返回 `false` |
| `src-async` | 支持原生 worker 语义 | 支持兼容实现 |
| `examples/async_basic` | 支持 | 受 `async fn main` 入口限制, 当前不提供 |
BitLogger 提供统一的日志级别、目标名、结构化字段和可定制格式,既可以直接输出到控制台,也可以在 native 环境写入文件,并提供异步日志版本。
## ❇️ 关键特性
- 结构化日志基础能力:level、record、formatter、sink、target、context field。
- 组合式设计:支持 filter、patch、fanout、split、callback、queued sink 等组合能力。
- 配置驱动:支持 JSON 配置解析、导出与 `build_logger(...)` / `build_async_logger(...)` 运行时组装。
- 文本格式化:支持 `text_formatter(...)`、template、style tag、`color_mode` / `style_markup`
- Native 文件输出:支持 file sink、基础 size rotation、reopen、failure counter 与运行时状态读取。
- 异步层:提供独立 `src-async` package,支持 queue、worker lifecycle、runtime state 和跨端兼容实现。
## 🚀 快速开始
## 快速开始
```moonbit
let logger = build_logger(
with_queue(
text_console(
min_level=Level::Info,
target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
max_pending=32,
overflow=QueueOverflowPolicy::DropOldest,
text_console(
min_level=Level::Info,
target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
)
@@ -51,40 +25,41 @@ logger.info("starting", fields=[field("port", "8080")])
ignore(logger.flush())
```
推荐`presets + build_logger(...)` 路径开始:`console(...)` / `json_console(...)` / `text_console(...)` / `file(...)` 负责拼装 `LoggerConfig``with_queue(...)` / `with_file_rotation(...)` 负责做小范围组合,然后再交给 `build_logger(...)` 构建运行时 logger
推荐从 `console(...)``json_console(...)``text_console(...)``file(...)` 这几个入口开始,再按需配合 `with_queue(...)` `with_file_rotation(...)`
你需要 `fanout``split``callback``patch` 这类自定义 sink 图组合时,再优先使用 `Logger::new(...)` 直接进行运行时拼装
如果你需要自己组合 sink,比如 `fanout``split``callback`,再使用 `Logger::new(...)`
异步入口示例:
## 支持情况
```moonbit
let logger = async_logger(console_sink(), target="async.demo")
@async.with_task_group(group => {
group.spawn_bg(() => logger.run())
logger.info("started")
logger.shutdown()
})
```
- `BitLogger` 当前在 CI 中检查/验证的目标是 `native``js``wasm-gc`
- `bitlogger_async` 当前在 CI 中检查 `native``js``wasm-gc`,测试覆盖 `native``js``wasm-gc`
- `wasm` 目标在源码 `moon.pkg` 中保留声明,但当前未纳入 CI 验证口径
- `llvm` 目前按实验性目标处理,当前环境未完成验证
- 文件输出是 native 能力;跨端代码里建议先判断 `native_files_supported()`
- `src-async` 可用,但示例 `examples/async_basic` 目前仍按 native 入口提供
## 📂 仓库结构
## 主要能力
- `src/`: 主日志库 package。
- `src-async/`: 基于 `moonbitlang/async` 的异步日志层。
- `docs/api/`: 单接口粒度 API 文档。
- `examples/basic/`: breadth 示例;文件开头就是推荐的 presets + `build_logger(...)` 同步入口,后续继续覆盖更广能力面。
- `examples/console_basic/`: console 与 json console 最小输出示例。
- `examples/text_formatter/`: text formatter / template 示例。
- `examples/style_tags/`: style tag / colored formatter 示例。
- `examples/file_rotation/`: file sink / rotation 示例。
- `examples/config_build/`: `build_logger(...)` 配置驱动示例。
- `examples/presets/`: presets + `build_logger(...)` 示例。
- `examples/async_basic/`: 异步 logger 示例。
- 结构化日志:level、target、message、fields
- 多种输出方式:console、json console、text console、file
- 可定制文本格式:模板、style tag、颜色控制
- 配置驱动构建:`build_logger(...)``build_async_logger(...)`
- 组合能力:queue、filter、patch、fanout、split、callback
- 异步日志:独立 `src-async` package
## 🔗 文档入口
## 示例
- `examples/console_basic/`:最小 console / json console 示例
- `examples/text_formatter/`:文本格式与模板示例
- `examples/style_tags/`style tag 与彩色输出示例
- `examples/config_build/`:配置构建示例
- `examples/presets/`:常用预设组合示例
- `examples/file_rotation/`:文件输出与轮转示例,仅适用于 native
- `examples/async_basic/`:异步日志示例
## 文档
- [Mooncake 文档页](https://mooncakes.io/docs/Nanaloveyuki/BitLogger)
- [English README](./docs/README-en.md)
- [src package README](./src/README.mbt.md)
- [API 索引](./docs/api/index.md)
- 推荐起步 API: `text_console(...)` / `file(...)` / `with_queue(...)` / `build_logger(...)`
- facade API: `build_application_logger(...)` / `build_library_logger(...)` / `build_application_async_logger(...)`
- [src package README](./src/README.mbt.md)
常用入口:`text_console(...)``file(...)``with_queue(...)``build_logger(...)``build_async_logger(...)`
+142
View File
@@ -0,0 +1,142 @@
import { basename } from 'node:path'
import { createMoonbitLanguageRegistration } from 'moonbit-syntax-highlighter'
import { defineConfig, type DefaultTheme } from 'vitepress'
import { docsData } from './generated/docs-data.mjs'
type ApiDocEntry = {
file: string
text: string
group: string
category: string
}
const repoSlug = process.env.GITHUB_REPOSITORY?.split('/')[1] ?? 'BitLogger'
const docsBase = process.env.DOCS_BASE ?? (process.env.GITHUB_ACTIONS ? `/${repoSlug}/` : '/')
const repository = 'https://github.com/Nanaloveyuki/BitLogger'
function splitWords(input: string): string[] {
return input
.split(/[^A-Za-z0-9]+/)
.map(part => part.trim())
.filter(Boolean)
}
function formatWord(word: string): string {
const lower = word.toLowerCase()
if (lower === 'api') return 'API'
if (lower === 'json') return 'JSON'
if (lower === 'js') return 'JS'
if (lower === 'wasm') return 'WASM'
if (lower === 'llvm') return 'LLVM'
return lower.charAt(0).toUpperCase() + lower.slice(1)
}
function humanize(input: string): string {
const words = splitWords(input)
if (words.length === 0) return input
return words.map(formatWord).join(' ')
}
function readApiDocs(): ApiDocEntry[] {
return docsData.api.entries.map(entry => ({
file: entry.file,
text: entry.title,
group: entry.group,
category: entry.category,
}))
}
function buildApiSidebar(): DefaultTheme.SidebarItem[] {
const entries = readApiDocs()
const sidebar: DefaultTheme.SidebarItem[] = [{ text: 'Overview', link: '/api/' }]
const groups = new Map<string, Map<string, DefaultTheme.SidebarItem[]>>()
for (const entry of entries) {
if (entry.file === 'index.md') continue
const group = groups.get(entry.group) ?? new Map<string, DefaultTheme.SidebarItem[]>()
const categoryItems = group.get(entry.category) ?? []
categoryItems.push({
text: entry.text,
link: `/api/${basename(entry.file, '.md')}`,
})
group.set(entry.category, categoryItems)
groups.set(entry.group, group)
}
if (groups.size === 1 && groups.has('api')) {
const categories = groups.get('api')!
for (const category of [...categories.keys()].sort((a, b) => humanize(a).localeCompare(humanize(b)))) {
sidebar.push({
text: humanize(category),
collapsed: true,
items: categories.get(category)!,
})
}
return sidebar
}
for (const group of [...groups.keys()].sort((a, b) => humanize(a).localeCompare(humanize(b)))) {
const categories = groups.get(group)!
sidebar.push({
text: humanize(group),
collapsed: true,
items: [...categories.keys()]
.sort((a, b) => humanize(a).localeCompare(humanize(b)))
.map(category => ({
text: humanize(category),
collapsed: true,
items: categories.get(category)!,
})),
})
}
return sidebar
}
function buildChangesSidebar(): DefaultTheme.SidebarItem[] {
return [
{ text: 'Overview', link: '/changes/' },
{
text: 'Versions',
items: docsData.changes.map(item => ({ text: item.version, link: item.link })),
},
]
}
export default defineConfig({
title: 'BitLogger',
description: 'Structured logging library docs for MoonBit.',
base: docsBase,
cleanUrls: true,
srcExclude: ['dev/**'],
lastUpdated: true,
markdown: {
languages: [createMoonbitLanguageRegistration()],
},
themeConfig: {
siteTitle: 'BitLogger',
nav: [
{ text: 'Home', link: '/' },
{ text: 'API', link: '/api/' },
{ text: 'Changes', link: '/changes/' },
{ text: 'Mooncake', link: 'https://mooncakes.io/docs/Nanaloveyuki/BitLogger' },
],
search: {
provider: 'local',
},
socialLinks: [{ icon: 'github', link: repository }],
editLink: {
pattern: `${repository}/edit/main/docs/:path`,
text: 'Edit this page on GitHub',
},
sidebar: {
'/api/': buildApiSidebar(),
'/changes/': buildChangesSidebar(),
},
footer: {
message: 'Published from the repository docs folder with VitePress.',
copyright: 'MIT',
},
},
})
@@ -0,0 +1,219 @@
<script setup lang="ts">
import { computed, shallowRef } from 'vue'
import { withBase } from 'vitepress'
import { docsData } from '../../generated/docs-data.mjs'
const selectedGroup = shallowRef('all')
const query = shallowRef('')
const groups = computed(() => docsData.api.groups)
const filteredGroups = computed(() => {
const keyword = query.value.trim().toLowerCase()
return groups.value
.filter(group => selectedGroup.value === 'all' || group.id === selectedGroup.value)
.map(group => ({
...group,
categories: group.categories
.map(category => ({
...category,
entries: category.entries.filter(entry => {
if (!keyword) return true
const haystack = [entry.title, entry.description, entry.name, ...entry.keywords]
.join(' ')
.toLowerCase()
return haystack.includes(keyword)
}),
}))
.filter(category => category.entries.length > 0),
}))
.filter(group => group.categories.length > 0)
})
const resultCount = computed(() =>
filteredGroups.value.reduce(
(total, group) => total + group.categories.reduce((sum, category) => sum + category.entries.length, 0),
0,
),
)
</script>
<template>
<section class="api-shell">
<div class="api-toolbar">
<label class="api-search">
<span>Find API</span>
<input v-model="query" type="search" placeholder="Search logger, sink, config, async...">
</label>
<label class="api-filter">
<span>Doc Group</span>
<select v-model="selectedGroup">
<option value="all">All groups</option>
<option v-for="group in groups" :key="group.id" :value="group.id">
{{ group.label }}
</option>
</select>
</label>
</div>
<div class="api-summary">
<strong>{{ resultCount }}</strong>
<span>matching API pages across {{ filteredGroups.length }} group views</span>
</div>
<div class="api-groups">
<article v-for="group in filteredGroups" :key="group.id" class="api-group-card">
<header class="api-group-header">
<div>
<p class="api-group-kicker">{{ group.label }}</p>
<h3>{{ group.entryCount }} APIs in this group</h3>
</div>
</header>
<div class="api-category-grid">
<section v-for="category in group.categories" :key="category.id" class="api-category-card">
<h4>{{ category.label }}</h4>
<p>{{ category.entries.length }} entries</p>
<ul>
<li v-for="entry in category.entries.slice(0, 8)" :key="entry.slug">
<a :href="withBase(entry.link)">{{ entry.title }}</a>
</li>
</ul>
<a v-if="category.entries.length > 8" class="api-more" :href="withBase('/api/')">
Browse {{ category.entries.length - 8 }} more in sidebar
</a>
</section>
</div>
</article>
</div>
</section>
</template>
<style scoped>
.api-shell {
margin: 1.5rem 0 2rem;
}
.api-toolbar {
display: grid;
gap: 0.9rem;
margin-bottom: 1rem;
}
.api-search,
.api-filter {
display: grid;
gap: 0.4rem;
}
.api-search span,
.api-filter span {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #9b4d28;
font-weight: 700;
}
.api-search input,
.api-filter select {
width: 100%;
border-radius: 14px;
border: 1px solid var(--vp-c-divider);
background: rgba(255, 255, 255, 0.9);
padding: 0.85rem 0.95rem;
font: inherit;
}
.api-summary {
display: flex;
align-items: baseline;
gap: 0.65rem;
margin-bottom: 1rem;
}
.api-summary strong {
font-size: 1.8rem;
}
.api-summary span {
color: var(--vp-c-text-2);
}
.api-groups {
display: grid;
gap: 1rem;
}
.api-group-card {
border: 1px solid var(--vp-c-divider);
border-radius: 28px;
padding: 1rem;
background: linear-gradient(180deg, rgba(255, 251, 247, 0.96), rgba(246, 242, 236, 0.96));
}
.api-group-header h3,
.api-category-card h4,
.api-category-card p {
margin: 0;
}
.api-group-kicker {
margin: 0 0 0.25rem;
color: #9b4d28;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.75rem;
font-weight: 700;
}
.api-category-grid {
display: grid;
gap: 0.85rem;
margin-top: 0.9rem;
}
.api-category-card {
border-radius: 20px;
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(155, 77, 40, 0.12);
padding: 0.95rem;
}
.api-category-card p {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.api-category-card ul {
list-style: none;
padding: 0;
margin: 0.85rem 0 0;
display: grid;
gap: 0.45rem;
}
.api-category-card a {
text-decoration: none;
}
.api-more {
display: inline-block;
margin-top: 0.8rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
@media (min-width: 820px) {
.api-toolbar {
grid-template-columns: minmax(0, 2fr) minmax(220px, 0.8fr);
}
.api-category-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
@@ -0,0 +1,191 @@
<script setup lang="ts">
import { computed } from 'vue'
import { withBase } from 'vitepress'
import { docsData } from '../../generated/docs-data.mjs'
const topCategories = computed(() => docsData.api.categories.slice(0, 6))
const recentChanges = computed(() => docsData.changes.slice(0, 4))
const apiCount = computed(() => docsData.api.entries.filter(entry => entry.slug !== 'index').length)
const groupCount = computed(() => docsData.api.groups.length)
</script>
<template>
<section class="hub-grid">
<article class="hub-panel hub-panel-accent">
<p class="hub-eyebrow">Docs Hub</p>
<h2 class="hub-title">Start from use case, not from filenames.</h2>
<p class="hub-copy">
Browse builders, runtime helpers, presets, sync logging, and async logging from one place.
</p>
<div class="hub-stats">
<div>
<strong>{{ apiCount }}</strong>
<span>API pages</span>
</div>
<div>
<strong>{{ groupCount }}</strong>
<span>doc groups</span>
</div>
<div>
<strong>{{ docsData.changes.length }}</strong>
<span>release notes</span>
</div>
</div>
</article>
<article class="hub-panel">
<p class="hub-eyebrow">Popular Areas</p>
<ul class="hub-chip-list">
<li v-for="category in topCategories" :key="category.id">
<a class="hub-chip" :href="withBase('/api/')">
<span>{{ category.label }}</span>
<small>{{ category.entryCount }}</small>
</a>
</li>
</ul>
</article>
<article class="hub-panel">
<p class="hub-eyebrow">Release Trail</p>
<ul class="hub-link-list">
<li v-for="item in recentChanges" :key="item.version">
<a :href="withBase(item.link)">Version {{ item.version }}</a>
</li>
</ul>
</article>
</section>
</template>
<style scoped>
.hub-grid {
display: grid;
gap: 1rem;
margin: 1.5rem 0 2rem;
}
.hub-panel {
border: 1px solid var(--vp-c-divider);
border-radius: 24px;
padding: 1.25rem;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(247, 245, 239, 0.95));
box-shadow: 0 16px 40px rgba(62, 46, 31, 0.08);
}
.hub-panel-accent {
background:
radial-gradient(circle at top right, rgba(191, 68, 32, 0.16), transparent 34%),
linear-gradient(180deg, rgba(255, 250, 244, 0.98), rgba(247, 241, 232, 0.98));
}
.hub-eyebrow {
margin: 0 0 0.5rem;
color: #9b4d28;
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.75rem;
font-weight: 700;
}
.hub-title {
margin: 0;
font-size: 1.55rem;
line-height: 1.15;
}
.hub-copy {
margin: 0.85rem 0 0;
color: var(--vp-c-text-2);
}
.hub-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.75rem;
margin-top: 1.15rem;
}
.hub-stats strong,
.hub-stats span {
display: block;
}
.hub-stats strong {
font-size: 1.6rem;
}
.hub-stats span {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.hub-chip-list,
.hub-link-list {
list-style: none;
padding: 0;
margin: 0;
}
.hub-chip-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 0.7rem;
}
.hub-chip {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: 56px;
gap: 0.8rem;
padding: 0.7rem 0.9rem;
border-radius: 999px;
text-decoration: none;
color: inherit;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(155, 77, 40, 0.14);
box-sizing: border-box;
}
.hub-chip span {
min-width: 0;
}
.hub-chip small {
flex: 0 0 auto;
color: var(--vp-c-text-2);
}
@media (max-width: 640px) {
.hub-chip-list {
grid-template-columns: 1fr;
}
}
.hub-link-list {
display: grid;
gap: 0.5rem;
}
.hub-link-list a {
color: var(--vp-c-brand-1);
text-decoration: none;
}
@media (min-width: 860px) {
.hub-grid {
grid-template-columns: 1.35fr 1fr;
}
.hub-panel-accent {
grid-row: span 2;
}
}
@media (max-width: 640px) {
.hub-stats {
grid-template-columns: 1fr;
}
}
</style>
+37
View File
@@ -0,0 +1,37 @@
:root {
--vp-c-brand-1: #b34a24;
--vp-c-brand-2: #92381a;
--vp-c-brand-3: #d06d39;
--vp-c-brand-soft: rgba(176, 74, 36, 0.14);
--vp-home-hero-name-color: #6b2f17;
--vp-home-hero-text-color: #27170f;
--vp-home-hero-tagline-color: #5f5249;
--vp-font-family-base: "Segoe UI", "PingFang SC", "Hiragino Sans GB", sans-serif;
--vp-font-family-mono: "Cascadia Mono", "JetBrains Mono", monospace;
}
.VPContent {
background:
radial-gradient(circle at top right, rgba(208, 109, 57, 0.08), transparent 28%),
radial-gradient(circle at left 20%, rgba(179, 74, 36, 0.06), transparent 22%);
}
.VPHomeHero .name,
.VPDoc h1,
.VPDoc h2,
.VPDoc h3 {
letter-spacing: -0.02em;
}
.VPHomeHero .image-container {
display: none;
}
.vp-doc a {
text-underline-offset: 0.18em;
}
.vp-doc .custom-block,
.vp-doc div[class*='language-'] {
border-radius: 18px;
}
+17
View File
@@ -0,0 +1,17 @@
import DefaultTheme from 'vitepress/theme'
import type { Theme } from 'vitepress'
import ApiOverview from './components/ApiOverview.vue'
import HomeDocHub from './components/HomeDocHub.vue'
import './custom.css'
const theme: Theme = {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('ApiOverview', ApiOverview)
app.component('HomeDocHub', HomeDocHub)
},
}
export default theme
+38 -69
View File
@@ -1,43 +1,22 @@
# BitLogger
BitLogger is a structured logging library written in MoonBit.
BitLogger is a structured logging library for MoonBit projects.
This README focuses on project positioning, core capabilities, and entry points. Detailed API coverage now lives in `docs/api/`.
- [Mooncake package page](https://mooncakes.io/docs/Nanaloveyuki/BitLogger)
- [Chinese README](https://github.com/Nanaloveyuki/BitLogger/blob/main/README.md)
## Overview
BitLogger is designed to provide composable, configurable, and cross-target logging infrastructure for MoonBit projects.
## Backend Compatibility
| Module / capability | native / llvm | js / wasm / wasm-gc |
| --- | --- | --- |
| `src` core package | Supported | Supported |
| `file_sink(...)` | Supported | Not available, `native_files_supported()` returns `false` |
| `src-async` | Native worker semantics | Compatibility implementation |
| `examples/async_basic` | Supported | Not shipped currently because `async fn main` entry support is still limited |
## Key Features
- Structured logging with levels, targets, and key-value fields.
- Composable sinks, filters, patches, fanout, routing, and queue wrappers.
- Config-driven runtime assembly through JSON parse/export helpers and `build_logger(...)` / `build_async_logger(...)`.
- Configurable text formatting with templates, style tags, and color control.
- Native file sink support with basic rotation, reopen helpers, and runtime observability.
- Separate async layer with bounded queueing, worker lifecycle control, runtime state, and cross-target compatibility.
BitLogger gives you consistent levels, targets, structured fields, configurable text output, native file logging, and an async logging layer.
## Quick Start
```moonbit
let logger = build_logger(
with_queue(
text_console(
min_level=Level::Info,
target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
max_pending=32,
overflow=QueueOverflowPolicy::DropOldest,
text_console(
min_level=Level::Info,
target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
)
@@ -45,52 +24,42 @@ logger.info("starting", fields=[field("port", "8080")])
ignore(logger.flush())
```
Start new synchronous code with `presets + build_logger(...)`: use `console(...)`, `json_console(...)`, `text_console(...)`, or `file(...)` to assemble `LoggerConfig`, optionally extend it with `with_queue(...)` or `with_file_rotation(...)`, then call `build_logger(...)`.
Start with `console(...)`, `json_console(...)`, `text_console(...)`, or `file(...)`, then add `with_queue(...)` or `with_file_rotation(...)` only when needed.
Switch to `Logger::new(...)` when you need direct runtime sink composition such as `fanout`, `split`, `callback`, or custom patch/filter graphs.
Use `Logger::new(...)` when you want to assemble custom sink graphs directly.
Async entry example:
## Support Status
```moonbit
let logger = async_logger(console_sink(), target="async.demo")
@async.with_task_group(group => {
group.spawn_bg(() => logger.run())
logger.info("started")
logger.shutdown()
})
```
- Current local verification covers `native`, `js`, `wasm`, and `wasm-gc` for the main `src` package and `src-async`
- `llvm` is still treated as experimental in the current release context and was not locally re-verified in the current environment
- The source packages still declare `wasm` support alongside `native`, `llvm`, `js`, and `wasm-gc`; see [`target-verification.md`](./api/target-verification.md) for the current release-facing verification boundary
- File output is a native capability; check `native_files_supported()` in cross-target code
- `src-async` is available, while `examples/async_basic` is still shipped as a native entry example
## Repository Layout
## Main Features
- `src/`: core logging package.
- `src-async/`: async logging layer built on `moonbitlang/async`.
- `docs/api/`: one-file-per-interface API documentation.
- `examples/basic/`: breadth example; starts with the recommended presets-based sync path and then sweeps broader capabilities.
- `examples/console_basic/`: minimal console and JSON console output.
- `examples/text_formatter/`: text formatter and template example.
- `examples/style_tags/`: style tag and colored formatter example.
- `examples/file_rotation/`: file sink and rotation example.
- `examples/config_build/`: config-driven `build_logger(...)` example.
- `examples/presets/`: presets-based construction example.
- `examples/async_basic/`: async logger example.
- Structured logs with levels, targets, messages, and fields
- Multiple outputs: console, JSON console, text console, and file
- Custom text formatting with templates, style tags, and color control
- Config-based builders: `build_logger(...)` and `build_async_logger(...)`
- Composition helpers: queue, filter, patch, fanout, split, callback
- Separate async package under `src-async`
## Documentation Entry Points
## Examples
- [Mooncake package page](https://mooncakes.io/docs/Nanaloveyuki/BitLogger)
- [Chinese README](../README.md)
- [src package README](../src/README.mbt.md)
- Recommended sync starting APIs: `text_console(...)`, `file(...)`, `with_queue(...)`, `build_logger(...)`
- Selected API docs in `docs/api/`:
- [logger-new.md](./api/logger-new.md)
- [async-logger.md](./api/async-logger.md)
- [build-logger.md](./api/build-logger.md)
- [build-async-logger.md](./api/build-async-logger.md)
- [build-application-logger.md](./api/build-application-logger.md)
- [build-library-logger.md](./api/build-library-logger.md)
- [build-application-async-logger.md](./api/build-application-async-logger.md)
- `examples/console_basic/`: minimal console and JSON console example
- `examples/text_formatter/`: text formatting and template example
- `examples/style_tags/`: style tags and colored output example
- `examples/config_build/`: config-based build example
- `examples/presets/`: common preset combinations
- `examples/file_rotation/`: native file logging and rotation example
- `examples/async_basic/`: async logging example
## Notes
## Documentation
- `docs/README-en.md` no longer acts as an API catalog.
- Detailed API references, config fields, runtime control helpers, and lifecycle surfaces now live under `docs/api/`.
- For concrete runnable flows, prefer `examples/`.
- [API index](./api/index.md): canonical API reference, organized as one public API per file
- [src package README](https://github.com/Nanaloveyuki/BitLogger/blob/main/src/README.mbt.md): package-level usage notes and target reminders
- [`docs/changes/`](./changes/): versioned release notes and publish-facing change summaries
- `docs/dev/`: developer reference material kept in the repository, intentionally excluded from the public static docs site
Common entry points: `text_console(...)`, `file(...)`, `with_queue(...)`, `build_logger(...)`, `build_async_logger(...)`
+127
View File
@@ -0,0 +1,127 @@
---
name: application-async-logger
group: api
category: facade
update-time: 20260614
description: Application-facing alias for the runtime-sink async logger surface, preserving the same async calling semantics as AsyncLogger.
key-word:
- application
- async
- alias
- public
---
## Application-async-logger
`ApplicationAsyncLogger` is the application-facing async logger alias. It currently maps directly to `AsyncLogger[@bitlogger.RuntimeSink]` and keeps the same async lifecycle, state, and queue helper surface.
### Interface
```moonbit
pub type ApplicationAsyncLogger = AsyncLogger[@bitlogger.RuntimeSink]
```
#### output
- `ApplicationAsyncLogger` - Application-facing name for the runtime-sink async logger shape.
### Explanation
Detailed rules explaining key parameters and behaviors
- This alias does not introduce a new runtime type or wrapper layer.
- It preserves the same async lifecycle helpers such as `run()`, `shutdown()`, `pending_count()`, and `state()`.
- Because it is `AsyncLogger[@bitlogger.RuntimeSink]`, the alias also keeps ordinary async logger composition and target behavior such as `with_target(...)`, `child(...)`, and per-call `log(..., target=...)` overrides.
- In particular, `log(..., target=...)` can override the target for one call, while severity 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 the synchronous application alias, async `with_context_fields(...)` and `bind(...)` preserve the visible `ApplicationAsyncLogger` shape because shared fields are stored directly on the async logger value instead of being modeled as a separate sink wrapper.
- Because this is only an alias, methods that are async on `AsyncLogger[@bitlogger.RuntimeSink]` remain async here as well.
- The alias therefore keeps the same runtime-sink lifecycle, queue, failure-state, and runtime-dependent post-close semantics already documented on `AsyncLogger[@bitlogger.RuntimeSink]`.
- In the current direct alias coverage, values built through `build_application_async_logger(...)` keep the same serialized state snapshot shape, queue counters, lifecycle flags, failure fields, and runtime-sink helper surface that the underlying runtime-sink async logger exposes directly.
- When the value is built through `build_application_async_logger(...)`, the sync-first builder route also stays visible through the alias: any optional `LoggerConfig.queue` was already applied before async wrapping, and `logger.sink.kind` had already selected the concrete `RuntimeSink` variant before the application alias reused that value.
- That includes queued runtime-sink behavior and file-backed runtime helpers when the configured sink path supports them.
- Those helpers remain directly callable on `ApplicationAsyncLogger` itself; callers do not need an unwrap step to reach queue counters, lifecycle state, or `RuntimeSink` file helpers.
- The alias exists to give application boot code a clearer public type name for the standard runtime-sink async logger.
- Builders such as `build_application_async_logger(...)` and `parse_and_build_application_async_logger(...)` return this alias.
### How to Use
Here are some specific examples provided.
#### When Need An App-level Name For The Standard Async Runtime Logger
When application code wants a stable public type name for the runtime-sink async logger:
```moonbit
let logger : ApplicationAsyncLogger = build_application_async_logger(
AsyncLoggerBuildConfig::new(logger=LoggerConfig::new(target="app.async")),
)
```
In this example, the application alias keeps the same underlying async logger behavior while presenting an app-facing type name.
#### When Pass The Async Logger Through App-level APIs
When top-level boot code or services should expose an application-oriented async logger type:
```moonbit
async fn start_async(logger : ApplicationAsyncLogger) -> Unit {
logger.run()
}
```
In this example, callers see the app-facing alias instead of the more explicit generic async logger spelling, while `run()` keeps its async calling contract.
And the inherited async logger target rules stay the same: `log(..., target=...)` can override the target per call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Direct Async Runtime Helpers On The Application Alias
When app code should inspect queue or file-backed runtime state without leaving the alias surface:
```moonbit
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, lifecycle and queue helpers are called directly on `ApplicationAsyncLogger`.
And unlike `LibraryAsyncLogger[@bitlogger.RuntimeSink]`, no `to_async_logger()` unwrap is required first.
#### When Need A One-call Target Override Without Rebuilding The Alias
When app-level async code should keep the same alias value but emit one 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 alias value's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Shared Context On The Async Application Alias
When app-level 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 `ApplicationAsyncLogger`.
And that shape preservation is intentional because async context binding updates stored logger metadata instead of changing the exposed sink type.
### Error Case
e.g.:
- Because this is only an alias, any runtime-sink limitations or target-specific async behavior still apply unchanged.
- If the value came from `build_application_async_logger(...)` or `parse_and_build_application_async_logger(...)`, the alias also does not undo the underlying sync-first sink selection; queued and file-backed runtime behavior still depend on the already-built `RuntimeSink` variant.
- If code needs a narrower public surface than the full async logger API, `LibraryAsyncLogger` is the better facade.
- If callers need queued runtime-sink helpers or file-backed runtime helpers, they remain directly available on this alias because no wrapper layer strips them away.
### Notes
1. This alias is about naming and public intent, not a different async implementation.
2. Inherited `AsyncLogger` behavior stays unchanged on this alias, including target overrides on `log(...)` and derived target composition through `with_target(...)` and `child(...)`.
3. Use `build_application_async_logger(...)` or `parse_and_build_application_async_logger(...)` for the usual construction paths.
4. Use `ApplicationTextAsyncLogger` or `build_application_text_async_logger(...)` when application code should keep the narrower text-console sink type instead of the broader runtime-sink alias.
+119
View File
@@ -0,0 +1,119 @@
---
name: application-logger
group: api
category: facade
update-time: 20260613
description: Application-facing alias for the configured sync runtime logger surface, preserving the full ConfiguredLogger helper set.
key-word:
- application
- facade
- alias
- public
---
## Application-logger
`ApplicationLogger` is the application-facing sync logger alias. It currently maps directly to `ConfiguredLogger` and keeps the same runtime helper surface for sync logging, queue inspection, and file controls.
### Interface
```moonbit
pub type ApplicationLogger = ConfiguredLogger
```
#### output
- `ApplicationLogger` - Application-facing name for the configured sync runtime logger shape.
### Explanation
Detailed rules explaining key parameters and behaviors
- This alias does not introduce a new runtime type or wrapper layer.
- It preserves the same logging, queue, and file helper APIs exposed by `ConfiguredLogger`.
- Because `ConfiguredLogger` is itself `Logger[RuntimeSink]`, the alias also keeps ordinary logger composition and write behavior such as `with_target(...)`, `child(...)`, and `log(..., target=...)`.
- In particular, `log(..., target=...)` can override the target for one call, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- Like the underlying synchronous logger line, `with_context_fields(...)` and `bind(...)` do not preserve the alias spelling. They return a `Logger[ContextSink[RuntimeSink]]` shape because sync shared-field binding extends the sink pipeline instead of storing extra alias-level context metadata.
- Because this is only an alias, the application-facing type does not hide any configured-runtime helpers or broader logger surface.
- That means queue, drain, flush, and file runtime helpers remain directly callable on `ApplicationLogger` itself; callers do not need an unwrap step to reach the underlying configured runtime logger behavior.
- The alias exists to give application boot code a clearer public entry name.
- Builders such as `build_application_logger(...)` and `parse_and_build_application_logger(...)` return this alias.
### How to Use
Here are some specific examples provided.
#### When Need An App-level Name For The Configured Runtime Logger
When application code wants a stable public type name for the configured sync logger:
```moonbit
let logger : ApplicationLogger = build_application_logger(LoggerConfig::new(target="app"))
```
In this example, the application alias keeps the same underlying runtime logger behavior while presenting an app-facing type name.
#### When Pass The Configured Logger Through App-level APIs
When top-level boot code or services should expose an application-oriented logger type:
```moonbit
fn start(logger : ApplicationLogger) -> Unit {
logger.info("started")
}
```
In this example, callers see the app-facing alias instead of the lower-level `ConfiguredLogger` name.
And the same queue/file/runtime helpers remain directly callable because no narrowing wrapper is added.
#### When Need Direct Runtime Helpers On The Application Alias
When app code should inspect queue state or file controls without leaving the alias surface:
```moonbit
ignore(logger.pending_count())
ignore(logger.flush())
```
In this example, the runtime helpers are called directly on `ApplicationLogger`.
And unlike `LibraryLogger[RuntimeSink]`, no `to_logger()` unwrap is required first.
And the inherited logger target rules stay the same: `log(..., target=...)` can override the target per call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need A One-call Target Override Without Rebuilding The Alias
When app-level sync code should keep the same alias value but emit one record under a different target:
```moonbit
logger.log(Level::Error, "boom", target="app.audit")
```
In this example, the emitted record uses `app.audit` only for that one call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the alias value's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Shared Context On The Sync Application Alias
When app-level sync code should attach stable metadata to later records:
```moonbit
let contextual = logger.with_context_fields([field("service", "billing")])
```
In this example, the returned value has the visible type `Logger[ContextSink[RuntimeSink]]` rather than the alias name `ApplicationLogger`.
And that type-shape change is expected because sync context binding extends the sink pipeline.
### Error Case
e.g.:
- Because this is only an alias, any backend limitations of `ConfiguredLogger` still apply unchanged.
- If code needs a narrower public surface than the full configured runtime logger, `LibraryLogger` is the better facade.
### Notes
1. This alias is about naming and public intent, not a different runtime implementation.
2. Inherited `Logger` behavior stays unchanged on this alias, including target overrides on `log(...)` and derived target composition through `with_target(...)` and `child(...)`.
3. Use `build_application_logger(...)` or `parse_and_build_application_logger(...)` for the usual construction paths.
4. Use `LibraryLogger` instead when a library boundary should intentionally hide configured-runtime helper methods behind a narrower facade.
+120
View File
@@ -0,0 +1,120 @@
---
name: application-text-async-logger
group: api
category: facade
update-time: 20260614
description: Application-facing alias for the text-console async logger surface, preserving the full AsyncLogger helper set for the concrete text sink shape.
key-word:
- application
- async
- text
- public
---
## Application-text-async-logger
`ApplicationTextAsyncLogger` is the application-facing async logger alias for text-console output. It currently maps directly to `AsyncLogger[@bitlogger.FormattedConsoleSink]` and keeps the same async lifecycle and queue helper surface while preserving the concrete text sink shape.
### Interface
```moonbit
pub type ApplicationTextAsyncLogger = AsyncLogger[@bitlogger.FormattedConsoleSink]
```
#### output
- `ApplicationTextAsyncLogger` - Application-facing name for the text-console async logger shape.
### Explanation
Detailed rules explaining key parameters and behaviors
- This alias does not introduce a new runtime type or wrapper layer.
- It preserves the same async lifecycle helpers as other async logger aliases.
- Because it is `AsyncLogger[@bitlogger.FormattedConsoleSink]`, the alias also keeps ordinary async logger composition and target behavior such as `with_target(...)`, `child(...)`, and per-call `log(..., target=...)` overrides.
- In particular, `log(..., target=...)` can override the target for one call, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- Like the broader runtime-sink async alias, `with_context_fields(...)` and `bind(...)` preserve the visible `ApplicationTextAsyncLogger` shape because shared fields are stored directly on the async logger value instead of being modeled as a separate sink wrapper.
- Because this is only an alias, methods that are async on `AsyncLogger[@bitlogger.FormattedConsoleSink]` remain async here as well.
- The alias therefore keeps the same text-console-specific builder and lifecycle semantics already documented on `AsyncLogger[@bitlogger.FormattedConsoleSink]`, including the concrete sink shape plus the same close, queue, and failure-state behavior.
- The application-facing type does not hide any async state or lifecycle helpers; queue/backlog/failure inspection remains directly available on this alias just as it is on the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`.
- In the current direct alias coverage, values built through `build_application_text_async_logger(...)` keep the same serialized state snapshot shape, formatter behavior, queue counters, lifecycle flags, and failure fields that the underlying text-console async logger exposes directly.
- When the value is built through `build_application_text_async_logger(...)`, the direct async counters come from the outer async logger only; any optional sync queue configured on `LoggerConfig.queue` is not carried into this text-specific build path.
- When the value is built through `build_application_text_async_logger(...)`, `logger.sink.kind` also does not decide the runtime sink shape. The builder still constructs `FormattedConsoleSink` from `logger.sink.text_formatter`, even if the config said `Console`, `JsonConsole`, or `File`.
- When the value is built through `build_application_text_async_logger(...)`, `flush_policy()` still reports the configured async policy, but the text-specific build path keeps the default no-op async flush callback instead of wiring an explicit sink flush step.
- The alias exists to give application code a clearer public name when it wants the concrete text-console sink shape explicitly.
- `build_application_text_async_logger(...)` returns this alias.
### How to Use
Here are some specific examples provided.
#### When Need An App-level Name For Text-console Async Output
When application code wants a stable type name for a text-console async logger:
```moonbit
let logger : ApplicationTextAsyncLogger = build_application_text_async_logger(
AsyncLoggerBuildConfig::new(logger=text_console(target="app.text.async")),
)
```
In this example, the application alias keeps the same underlying async logger behavior while preserving the text sink shape explicitly.
#### When Pass A Text-console Async Logger Through App-level APIs
When caller code should know it is working with the text-console variant:
```moonbit
async fn start_text_async(logger : ApplicationTextAsyncLogger) -> Unit {
logger.run()
}
```
In this example, the app-facing alias communicates the concrete text-console async shape directly, while `run()` keeps its async calling contract.
And the same pending-count, state, and failure helpers remain directly available because no narrowing wrapper is added.
And the inherited async logger target rules stay the same: `log(..., target=...)` can override the target per call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need A One-call Target Override On The Text-console Alias
When app-level text-console async code should keep the same alias value but emit one record under a different target:
```moonbit
logger.log(@bitlogger.Level::Error, "boom", target="app.text.async.audit")
```
In this example, the emitted record uses `app.text.async.audit` only for that one call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the alias value's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Shared Context On The Text-console Async Alias
When app-level text-console 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 `ApplicationTextAsyncLogger`.
And that shape preservation is intentional because async context binding updates stored logger metadata instead of changing the exposed sink type.
### Error Case
e.g.:
- Because this is only an alias, any async target limitations or runtime behavior still apply unchanged.
- If code does not need the concrete text-console sink shape, `ApplicationAsyncLogger` is the broader runtime-sink async alias.
- If the value came from `build_application_text_async_logger(...)`, carrying `logger.sink.kind=File` in the config still does not make this alias file-backed; that builder keeps the formatter-driven text-console path.
- If callers depend on the concrete formatter or direct text-console helper surface, they remain available on this alias because no wrapper layer narrows them away.
### Notes
1. This alias is about naming and public intent, not a different async implementation.
2. Inherited `AsyncLogger` behavior stays unchanged on this alias, including target overrides on `log(...)` and derived target composition through `with_target(...)` and `child(...)`.
3. Use `build_application_text_async_logger(...)` when callers want the `FormattedConsoleSink`-backed async type explicitly.
4. Use `ApplicationAsyncLogger` when application code should keep the broader runtime-sink shape instead of the text-console-specific one.
5. Use `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]` instead when a library boundary should intentionally narrow the exposed async surface while still preserving the concrete text sink type.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-flush-policy
group: api
category: async
update-time: 20260614
description: Public flush policy alias used by AsyncLoggerConfig, async parser labels, and async worker flushing.
key-word:
- async
- flush
- alias
- public
---
## Async-flush-policy
`AsyncFlushPolicy` is the public enum that defines when an async logger should call its flush function. It is a direct alias to the async model enum used by `AsyncLoggerConfig`, worker execution, and async logger state reporting.
### Interface
```moonbit
pub type AsyncFlushPolicy = @utils.AsyncFlushPolicy
```
#### output
- `AsyncFlushPolicy` - Public async flush enum with the variants `Never`, `Batch`, and `Shutdown`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a separate lifecycle wrapper.
- `AsyncFlushPolicy::Never` skips explicit flush calls from the async worker.
- `AsyncFlushPolicy::Batch` calls the configured flush function after each processed batch.
- `AsyncFlushPolicy::Shutdown` calls the configured flush function once after the worker loop exits.
- The current flush policy is also exposed through `AsyncLogger::flush_policy()` and included in `AsyncLoggerState`.
- The canonical labels `Never`, `Batch`, and `Shutdown` are also the labels emitted by async config and logger-state serializers, so parsing and diagnostics share one public vocabulary.
- Async config parsing accepts the canonical label `Never` and also the compatibility alias `None`, both mapping to the same public enum variant.
- `Batch` flushing happens after the worker finishes one drained batch, not after every individual record write.
### How to Use
Here are some specific examples provided.
#### When Need Explicit Flush After Every Processed Batch
When buffered sinks should flush incrementally during worker execution:
```moonbit
let config = AsyncLoggerConfig::new(flush=AsyncFlushPolicy::Batch)
```
In this example, each batch run triggers the provided flush callback.
#### When Need One Final Flush During Shutdown
When sink flushing should be deferred until the worker finishes:
```moonbit
let config = AsyncLoggerConfig::new(flush=AsyncFlushPolicy::Shutdown)
```
In this example, flushing happens when the worker loop exits instead of after each batch.
### Error Case
e.g.:
- If async config text uses unsupported flush policy text, async config parsing raises a failure.
- The parser error path for unsupported flush text is the same `Failure` surface used by the async config utilities.
- If a sink never needs explicit flushing, `Batch` or `Shutdown` can add unnecessary work without changing output.
- If the configured flush callback raises, the worker records failure state and stops instead of silently hiding the error.
### Notes
1. This policy only affects the async logger path and only matters when the configured sink has a meaningful flush function.
2. `AsyncFlushPolicy::Never` is the default in `AsyncLoggerConfig::new(...)`.
3. Serialized config uses the canonical `Never` label even though the parser also accepts `None`.
+30 -2
View File
@@ -2,8 +2,8 @@
name: async-logger-build-config-to-json
group: api
category: async
update-time: 20260512
description: Convert AsyncLoggerBuildConfig into a JSON value for exporting complete async logger build settings.
update-time: 20260614
description: Convert AsyncLoggerBuildConfig into a JSON value for exporting the full shared async build shape that can later feed either async builder path.
key-word:
- async
- build
@@ -38,7 +38,13 @@ Detailed rules explaining key parameters and behaviors
- The output always includes `logger` and `async_config`.
- Logger export is delegated to `@bitlogger.logger_config_to_json(...)`.
- Async export is delegated to `async_logger_config_to_json(...)`.
- Because both sections are always materialized, parsed defaults that were originally omitted in JSON input become explicit again in the exported build-config shape.
- This helper is useful when generated setup should preserve both sink/logger behavior and async runtime behavior together.
- The exported `logger` section keeps the full `LoggerConfig` shape, including fields that only matter on the full sync-first builder path such as the optional sync queue layer.
- That means the JSON shape is broader than the consumption pattern of `build_async_text_logger(...)`, which only uses selected text-oriented logger fields when building the sink.
- In particular, the exported `logger.sink.kind` remains whatever the config currently says, but a later `build_async_text_logger(...)` call still ignores that sink-kind branch and constructs `FormattedConsoleSink` from `logger.sink.text_formatter`.
- The same exported object is also the shared handoff shape for the application and library facade routes after parse. `parse_async_logger_build_config_text(...)` can read this JSON back into `AsyncLoggerBuildConfig`, and that parsed value can then flow unchanged into `build_application_async_logger(...)`, `build_application_text_async_logger(...)`, `build_library_async_logger(...)`, or `build_library_async_text_logger(...)`.
- Because of that, the exported structure is descriptive config data rather than a commitment to one public async type. The later builder or facade API still decides whether the runtime-sink line or the text-console line is taken.
### How to Use
@@ -58,6 +64,12 @@ let payload = async_logger_build_config_to_json(
In this example, both layers of configuration are exported together.
And later consumers can still choose whether to rebuild through `build_async_logger(...)` or the narrower `build_async_text_logger(...)` path.
And that later text-specific builder choice still matters more than the serialized `logger.sink.kind` value, because only the formatter-backed text path is consumed there.
And the same exported object can just as well be parsed and then routed into the application or library facade builders when the next consumer wants a narrower public async type.
#### When Need Roundtrip-friendly Build Config Data
When generated build config should later be parsed again:
@@ -74,3 +86,19 @@ e.g.:
- If callers want direct text output, they should use `stringify_async_logger_build_config(...)` instead.
- Exporting the full `logger` section does not imply that every async builder will later consume every logger field equally.
- Exporting `logger.sink.kind="file"` or `"console"` also does not force the later text-specific builder path to branch that way; only `build_async_logger(...)` follows sink kind when constructing the runtime sink.
- Choosing an application or library facade builder later does not change the meaning of the exported config by itself; those facade APIs inherit the same runtime-sink-versus-text-console split from the direct builder they delegate to.
### Notes
1. Use this helper when tools or tests need a structured JSON object instead of text.
2. Use `stringify_async_logger_build_config(...)` when the same build shape should be emitted as JSON text directly.
3. The resulting object round-trips through `parse_async_logger_build_config_text(...)`, even though different async builders later consume different parts of the embedded `LoggerConfig`.
4. After parsing, that same object can also feed the application or library facade builders; export preserves one shared build-config shape, not a direct-builder-only route.
@@ -0,0 +1,99 @@
---
name: async-logger-build-config-type
group: api
category: async
update-time: 20260614
description: Public async build config alias combining the base logger config and async runtime config for both the general async builder path and the specialized text-console builder path.
key-word:
- async
- build
- config
- public
---
## Async-logger-build-config-type
`AsyncLoggerBuildConfig` is the public config object that combines the base synchronous `LoggerConfig` with the async runtime `AsyncLoggerConfig`. It is a direct alias to the build-config model used by async builder APIs, parsers, and serializers, even though the available builders consume different parts of the embedded sync config.
### Interface
```moonbit
pub type AsyncLoggerBuildConfig = @utils.AsyncLoggerBuildConfig
```
#### output
- `AsyncLoggerBuildConfig` - Public async build config object containing `logger` and `async_config`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a built logger instance.
- The current fields are `logger : LoggerConfig` and `async_config : AsyncLoggerConfig`.
- The public `src-async` surface forwards this alias directly from `@utils.AsyncLoggerBuildConfig`, so constructor, parser, export, and stringify helpers all operate on one shared underlying build-config model.
- `AsyncLoggerBuildConfig::new(...)` constructs this type as the main handoff object for async build flows.
- `build_async_logger(...)`, `build_async_text_logger(...)`, `parse_async_logger_build_config_text(...)`, `async_logger_build_config_to_json(...)`, and `stringify_async_logger_build_config(...)` all consume or produce this same public shape.
- The same typed object also feeds the application and library facade builders. `build_application_async_logger(...)`, `build_application_text_async_logger(...)`, `build_library_async_logger(...)`, and `build_library_async_text_logger(...)` all accept this exact config type and then delegate to one of the two direct builder lines.
- The parse-and-build facade helpers use the same model as well. `parse_and_build_application_async_logger(...)` and `parse_and_build_library_async_logger(...)` first parse JSON into this build-config shape and then forward into their respective facade builders.
- `build_async_logger(...)` consumes the full sync build path by calling `build_logger(config.logger)` first, so `LoggerConfig.sink`, `LoggerConfig.queue`, and the resulting runtime sink behavior all participate before the async layer is added.
- `build_async_text_logger(...)` is narrower: it builds a text console sink directly from `config.logger.sink.text_formatter` and the top-level `min_level`, `target`, and `timestamp` fields, without applying `LoggerConfig.queue`.
- On that text-specific path, `config.logger.sink.kind` also does not decide the runtime sink shape. `build_async_text_logger(...)` still constructs `FormattedConsoleSink` from `config.logger.sink.text_formatter` even if the config says `Console`, `JsonConsole`, or `File`.
- In practice, the builder function you choose is what decides how the embedded `LoggerConfig` is interpreted. The runtime-sink line (`build_async_logger(...)` and the application/library facades layered on it) preserves the full sync build path, while the text-console line (`build_async_text_logger(...)` and the facades layered on it) ignores the optional sync queue and does not branch on `logger.sink.kind`.
### How to Use
Here are some specific examples provided.
#### When Need One Typed Object For Full Async Logger Setup
When sync sink setup and async runtime policy should move together through application boot code:
```moonbit
let config : AsyncLoggerBuildConfig = AsyncLoggerBuildConfig::new(
logger=@bitlogger.LoggerConfig::new(target="svc"),
async_config=AsyncLoggerConfig::new(max_pending=64),
)
```
In this example, both layers of logger setup are kept in one typed value.
And downstream code can still choose between the full sync-first builder path and the narrower text-console builder path.
And if downstream code chooses `build_async_text_logger(...)`, that builder choice still matters more than `logger.sink.kind` because only the formatter-backed text path is consumed there.
And the same typed object can be handed unchanged to `build_application_async_logger(...)`, `build_application_text_async_logger(...)`, `build_library_async_logger(...)`, or `build_library_async_text_logger(...)` when the caller wants a different public facade over the same underlying build decision.
#### When Need To Export Or Inspect The Full Build Shape
When application code should inspect the combined async build configuration before constructing the logger:
```moonbit
let config = AsyncLoggerBuildConfig::new(async_config=AsyncLoggerConfig::new(max_batch=4))
println(stringify_async_logger_build_config(config, pretty=true))
```
In this example, the same public config object supports both review and later build steps.
### Error Case
e.g.:
- `AsyncLoggerBuildConfig` itself does not have a runtime failure mode.
- If only async runtime policy is needed and the base sync logger config is irrelevant, this type may be broader than necessary and `AsyncLoggerConfig` is the smaller fit.
- If callers expect every `LoggerConfig` field to affect every async builder in the same way, that assumption is too broad: the text-specific builder intentionally ignores the optional sync queue layer.
- In particular, carrying `logger.sink.kind=File` inside this config type does not force the later text-specific builder path to create a file-backed async logger; only `build_async_logger(...)` branches on sink kind.
- Likewise, choosing an application or library facade builder does not change these config semantics by itself. Those facade APIs inherit the same runtime-sink-versus-text-console split from the direct builder they delegate to.
### Notes
1. Use `AsyncLoggerBuildConfig::new(...)` when one object should carry both sync and async logger setup.
2. Use `parse_async_logger_build_config_text(...)` when the same shape should come from JSON text instead of handwritten code.
3. Pick `build_async_logger(...)` when the full synchronous config path, including `LoggerConfig.queue`, should be preserved before async wrapping.
4. Pick `build_async_text_logger(...)` when the goal is specifically a concrete text console sink and only the selected text-oriented `LoggerConfig` fields should apply.
5. Pick the application or library facade builders when the same config should drive one of those narrower public surfaces; the chosen facade changes the exposed type, but the direct builder line underneath still determines whether queue and sink-kind settings are preserved or ignored.
+97
View File
@@ -0,0 +1,97 @@
---
name: async-logger-build-config
group: api
category: async
update-time: 20260614
description: Create the combined sync-and-async build config used by async logger builder APIs, whether callers later choose the full sync-first builder path or the specialized text-console builder path.
key-word:
- async
- build
- config
- public
---
## Async-logger-build-config
Create an `AsyncLoggerBuildConfig` value that combines the base synchronous `LoggerConfig` with the async runtime `AsyncLoggerConfig`. This is the constructor used when async builder APIs should receive one typed object carrying both layers of setup, even though different builders later consume different parts of the embedded sync config.
### Interface
```moonbit
pub fn AsyncLoggerBuildConfig::new(
logger~ : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(),
async_config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
) -> AsyncLoggerBuildConfig {
```
#### input
- `logger : LoggerConfig` - Base synchronous logger config describing the sink, level, target, related sync logger settings, and any optional synchronous queue wrapper.
- `async_config : AsyncLoggerConfig` - Async runtime config describing queue, batching, linger, and flush behavior.
#### output
- `AsyncLoggerBuildConfig` - Combined build config used by async logger build and parse helpers.
### Explanation
Detailed rules explaining key parameters and behaviors
- Omitting `logger` uses `default_logger_config()`.
- Omitting `async_config` uses `AsyncLoggerConfig::new()`.
- The constructor simply packages both config objects into one public build shape.
- The constructor does not normalize or reinterpret either embedded config beyond those defaults; any normalization has already happened inside the `LoggerConfig` or `AsyncLoggerConfig` values passed in.
- When passed to `build_async_logger(...)`, the `logger` portion is built first through the normal synchronous config path before the outer async queue layer is applied.
- When passed to `build_async_text_logger(...)`, the same `logger` portion is consumed more narrowly: `text_formatter`, `min_level`, `target`, and `timestamp` are used directly to build a text console sink, while `LoggerConfig.queue` is not applied.
- On that text-specific path, `logger.sink.kind` also does not decide the runtime sink shape. `build_async_text_logger(...)` still constructs `FormattedConsoleSink` from `logger.sink.text_formatter` even if the config says `Console`, `JsonConsole`, or `File`.
- This helper is the main code-side counterpart to `parse_async_logger_build_config_text(...)`.
### How to Use
Here are some specific examples provided.
#### When Need One Typed Object For Async Builder Input
When sync sink setup and async runtime policy should travel together through build code:
```moonbit
let config = AsyncLoggerBuildConfig::new(
logger=@bitlogger.LoggerConfig::new(target="svc.async"),
async_config=AsyncLoggerConfig::new(max_pending=64, max_batch=8),
)
```
In this example, the builder input keeps both configuration layers in one typed value.
And later code can still decide whether that shared config should flow into the full sync-first builder or the narrower text-console builder.
And if later code chooses `build_async_text_logger(...)`, that builder choice still matters more than `logger.sink.kind` because only the formatter-backed text path is consumed there.
#### When Need Defaulted Async Build Settings
When code only wants the standard combined config shape with few overrides:
```moonbit
let config = AsyncLoggerBuildConfig::new(async_config=AsyncLoggerConfig::new(max_batch=4))
```
In this example, the base sync logger config falls back to its default value automatically.
### Error Case
e.g.:
- This constructor itself does not have a normal failure mode; it only packages configuration values.
- If callers only need async runtime policy and not the full builder input shape, `AsyncLoggerConfig::new(...)` is the smaller API.
- If callers expect every field inside `LoggerConfig` to affect every async builder equally, that assumption is too broad: `build_async_text_logger(...)` intentionally skips the optional sync queue layer.
- In particular, carrying `logger.sink.kind=File` inside this config does not force the later text-specific builder path to create a file-backed async logger; only `build_async_logger(...)` branches on sink kind.
### Notes
1. Use this helper when async builder APIs should receive one combined config object.
2. Pair it with `build_async_logger(...)`, `build_async_text_logger(...)`, or `parse_async_logger_build_config_text(...)` depending on whether the source is code or JSON text.
3. Prefer `build_async_logger(...)` after constructing this value when configured sync sink behavior, including `LoggerConfig.queue`, should be preserved before async wrapping.
4. Prefer `build_async_text_logger(...)` after constructing this value when the goal is specifically config-driven text console output with a concrete `FormattedConsoleSink`.
+10 -2
View File
@@ -28,16 +28,18 @@ pub fn[S] AsyncLogger::child(self : AsyncLogger[S], target : String) -> AsyncLog
#### output
- `AsyncLogger[S]` - A new async logger whose default target is the composed child path.
- `AsyncLogger[S]` - A new async logger value whose default target is the composed child path.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned logger is derived from `self`; the original async logger value is not mutated.
- 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.
- Only the stored target changes. Queue settings, sink wiring, flush policy, level gating, and lifecycle state remain shared with the same underlying async logger setup.
- In the current direct async coverage, `timestamp` is also preserved on the derived child logger while the source logger keeps its previous parent target.
### How to Use
@@ -53,6 +55,8 @@ let worker = logger.child("worker")
In this example, `worker` emits under `service.worker` while keeping the same async queue behavior.
And `logger` still keeps its original stored target, because `child(...)` returns a derived async logger value instead of mutating the parent.
#### When Build Deep Async Scopes Step By Step
When deeper target composition should remain readable:
@@ -76,3 +80,7 @@ e.g.:
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.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` still operate on the same async logger state after derivation.
4. Use `child("")` when code should keep the current target while still following a target-composition code path.
+13 -2
View File
@@ -2,8 +2,8 @@
name: async-logger-close
group: api
category: async
update-time: 20260512
description: Close the async logger queue and optionally clear pending records immediately.
update-time: 20260707
description: Close the async logger queue immediately and optionally convert current pending backlog into dropped records before queue closure.
key-word:
- async
- logger
@@ -35,9 +35,14 @@ pub fn[S] AsyncLogger::close(self : AsyncLogger[S], clear? : Bool = false) -> Un
Detailed rules explaining key parameters and behaviors
- `close(...)` marks the logger as closed immediately.
- If a worker is still active when `close(...)` runs, the lifecycle phase moves into the closing path first and only settles into a terminal closed phase after worker exit.
- `clear=false` closes the queue without explicitly abandoning pending records in the helper itself.
- `clear=true` counts pending records as dropped and resets `pending_count` to `0` before closing the queue.
- This helper does not itself wait for the worker to finish.
- `close(...)` also does not change `has_failed()` or `last_error()`; it is a closure primitive, not a failure reset API.
- Because this is a low-level close primitive, it does not first run `wait_idle()` or apply the runtime-dependent fallback logic used by `shutdown()`.
- After closure, later log attempts are rejected in the shared async log path before record build, patch, or filter work starts.
- That means post-close late logs no longer add pending or dropped counts, and they no longer trigger patch/filter side effects on one backend but not another.
### How to Use
@@ -66,6 +71,10 @@ In this example, queued backlog is counted as dropped instead of waiting for fur
e.g.:
- If `clear=true`, pending records are intentionally discarded and contribute to `dropped_count()`.
- If `clear=false`, pending records may still exist after closure until worker drain or later cleanup resolves them.
- If callers perform late log attempts after closure, backlog counters still stay unchanged and patch/filter side effects stay skipped.
- If callers need graceful waiting for drain completion, `shutdown()` is usually the better API.
### Notes
@@ -73,3 +82,5 @@ e.g.:
1. This is a low-level lifecycle helper; prefer `shutdown()` for normal graceful teardown.
2. Use `clear=true` only when backlog loss is an acceptable shutdown tradeoff.
3. Pair it with `pending_count()`, `dropped_count()`, or `state()` when you need to observe what happened to existing backlog after closure.
+11 -2
View File
@@ -2,8 +2,8 @@
name: async-logger-config-to-json
group: api
category: async
update-time: 20260512
description: Convert AsyncLoggerConfig into a JSON value for export, persistence, or generated async config output.
update-time: 20260614
description: Convert AsyncLoggerConfig into a JSON value for export, persistence, or generated async config output using the stable serialized policy labels.
key-word:
- async
- config
@@ -36,6 +36,7 @@ Detailed rules explaining key parameters and behaviors
- The output includes `max_pending`, `max_batch`, `linger_ms`, `overflow`, and `flush`.
- Policy fields are serialized using the stable labels accepted by the config parser.
- This helper exports effective typed config after constructor normalization has already happened.
- If `max_pending` is negative in the config object, the exported JSON preserves that negative value because runtime queue clamping happens later, not during serialization.
- The JSON shape matches the `async_config` section used by async build config parsing.
### How to Use
@@ -67,5 +68,13 @@ In this example, the exported JSON stays aligned with parser expectations.
e.g.:
- If `max_batch` or `linger_ms` were normalized during construction, the exported JSON reflects the normalized values rather than the original invalid inputs.
- If callers need to understand runtime queue behavior for negative `max_pending`, they should document that separately because serialization preserves the config value rather than the later queue-kind clamp.
- If callers want direct text output instead of a JSON value, they should use `stringify_async_logger_config(...)` instead.
### Notes
1. Serialized policy labels round-trip through `parse_async_logger_config_text(...)`.
2. The serializer emits canonical labels like `DropNewest` and `Never`, even though the parser also accepts compatibility aliases such as `DropLatest` and `None`.
+81
View File
@@ -0,0 +1,81 @@
---
name: async-logger-config-type
group: api
category: async
update-time: 20260614
description: Public async logger config alias used for async queue interpretation, batching, linger, and flush settings.
key-word:
- async
- config
- alias
- public
---
## Async-logger-config-type
`AsyncLoggerConfig` is the public config object used to describe async queue capacity, overflow behavior, batching, linger timing, and flush policy. It is a direct alias to the async config model used by `async_logger(...)`, config parsers, and async config serializers.
### Interface
```moonbit
pub type AsyncLoggerConfig = @utils.AsyncLoggerConfig
```
#### output
- `AsyncLoggerConfig` - Public async config object containing `max_pending`, `overflow`, `max_batch`, `linger_ms`, and `flush`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a runtime logger handle.
- The current fields are `max_pending : Int`, `overflow : AsyncOverflowPolicy`, `max_batch : Int`, `linger_ms : Int`, and `flush : AsyncFlushPolicy`.
- The public `src-async` surface forwards this alias directly from `@utils.AsyncLoggerConfig`, so constructor, parser, export, and stringify helpers all operate on one shared underlying config model.
- `AsyncLoggerConfig::new(...)` normalizes `max_batch` and `linger_ms`, but it preserves the provided `max_pending` value.
- Runtime queue creation later interprets negative `max_pending` as `0` when choosing the internal queue kind.
- `parse_async_logger_config_text(...)`, `async_logger_config_to_json(...)`, and `stringify_async_logger_config(...)` all operate on this same public config shape.
- The parser accepts the stable serialized labels such as `DropNewest` and `Never`, plus compatibility aliases such as `DropLatest` and `None`.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Async Runtime Policy Value
When async queue and flush behavior should be passed around as structured config:
```moonbit
let config : AsyncLoggerConfig = AsyncLoggerConfig::new(
max_pending=64,
overflow=AsyncOverflowPolicy::DropOldest,
)
```
In this example, async policy remains a typed object instead of immediately becoming JSON text or a logger instance.
#### When Need To Inspect The Config Before Building
When one layer should read or adjust async policy before logger construction:
```moonbit
let config = AsyncLoggerConfig::new(max_batch=4, linger_ms=20)
println(stringify_async_logger_config(config, pretty=true))
```
In this example, the same public config object supports both inspection and later build steps.
### Error Case
e.g.:
- `AsyncLoggerConfig` itself does not have a runtime failure mode.
- Constructor normalization still applies when the value is created through `AsyncLoggerConfig::new(...)`, so very small batch sizes and negative linger values may be adjusted before later serialization or use.
- Negative `max_pending` is not rewritten inside the config object itself; it is only clamped later when the async queue kind is derived at runtime.
### Notes
1. Use `AsyncLoggerConfig::new(...)` when you need a value of this type in code.
2. Use `AsyncLoggerBuildConfig` when the async config should travel together with the base synchronous `LoggerConfig`.
3. Use `parse_async_logger_config_text(...)` when the same shape should come from JSON text, including accepted aliases like `DropLatest` and `None`.
+9 -5
View File
@@ -2,8 +2,8 @@
name: async-logger-config
group: api
category: async
update-time: 20260512
description: Create the queue, batching, linger, and flush policy config used by async loggers.
update-time: 20260614
description: Create the async queue, batching, linger, and flush policy config used by async loggers.
key-word:
- async
- config
@@ -29,7 +29,7 @@ pub fn AsyncLoggerConfig::new(
#### input
- `max_pending : Int` - Maximum queued records.
- `max_pending : Int` - Requested maximum queued records before overflow policy matters; negative values are preserved in the config object and later treated as `0` by runtime queue creation.
- `overflow : AsyncOverflowPolicy` - Queue overflow strategy.
- `max_batch : Int` - Maximum records drained per batch.
- `linger_ms : Int` - Optional wait window for batch accumulation.
@@ -45,6 +45,8 @@ Detailed rules explaining key parameters and behaviors
- `max_batch <= 1` is normalized to `1`.
- `linger_ms < 0` is normalized to `0`.
- `max_pending` is stored as provided by the constructor.
- When the async logger runtime later builds its internal queue, negative `max_pending` is interpreted as `0`.
- `overflow` and `flush` define the most important queue/runtime behavior tradeoffs.
- This type is used directly by `async_logger(...)` and embedded in `AsyncLoggerBuildConfig`.
@@ -79,12 +81,14 @@ In this example, the config becomes a stable JSON payload.
### Error Case
e.g.:
- If `max_batch` is set to `0` or below, runtime config normalizes it to `1`.
- If `max_batch` is set to `0` or below, constructor normalization changes it to `1`.
- If `linger_ms` is negative, it is normalized to `0`.
- If `linger_ms` is negative, constructor normalization changes it to `0`.
### Notes
1. This type controls async runtime behavior, not synchronous queue wrapping.
2. Prefer explicit values for production services so overflow and flush semantics are visible.
3. If queue limit semantics for negative values matter, document that behavior explicitly in calling code because the config object preserves the negative value while the runtime queue later clamps it to `0`.
+7 -6
View File
@@ -2,8 +2,8 @@
name: async-logger-debug
group: api
category: async
update-time: 20260512
description: Enqueue a debug-level record through the async logger using the built-in severity shortcut.
update-time: 20260614
description: Enqueue a debug-level record through the async logger using the built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
@@ -39,8 +39,9 @@ pub async fn[S] AsyncLogger::debug(
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Debug, ...)`.
- This helper delegates to `log(Level::Debug, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Debug records are useful for development and targeted diagnostics.
- Use this helper when a named debug call is clearer than a raw `log(...)` call.
@@ -52,7 +53,7 @@ Here are some specific examples provided.
When intermediate async flow details should be visible during debugging:
```moonbit
await logger.debug("loaded worker config")
logger.debug("loaded worker config")
```
In this example, the call site communicates its intended diagnostic level directly.
@@ -61,7 +62,7 @@ In this example, the call site communicates its intended diagnostic level direct
When a debug event should include extra fields:
```moonbit
await logger.debug(
logger.debug(
"dispatch start",
fields=[@bitlogger.field("job_id", "42")],
)
@@ -80,4 +81,4 @@ e.g.:
1. Prefer this helper when the event is semantically debug-level.
2. Use `log(...)` when the level must be chosen dynamically.
2. Use `log(...)` when the level must be chosen dynamically or one call needs a target override.
+6
View File
@@ -35,6 +35,10 @@ Detailed rules explaining key parameters and behaviors
- The counter increases when overflow policy discards records.
- The counter can also increase when `close(clear=true)` or `shutdown(clear=true)` abandons queued records.
- It can also increase when shutdown fallback logic converts remaining pending records into dropped records during a clear-close path.
- After a worker failure short-circuits `wait_idle()`, that shutdown-fallback increase is runtime-dependent in the current implementation: native-worker shutdown can convert leftover pending records into additional dropped records, while compatibility shutdown can leave that closed-queue remainder visible in `pending_count()` instead.
- That also means `dropped_count()` can still stay unchanged even after `is_closed=true` and retained failure state on compatibility-style shutdown paths, because closure there does not necessarily convert the leftover failed backlog into dropped records.
- Later log attempts against an already closed queue do not add to this counter by themselves.
- This is a cumulative counter for the lifetime of the logger value.
- Use this helper when you need a focused loss metric rather than a full `state()` snapshot.
@@ -70,6 +74,8 @@ e.g.:
- If callers need to know why records were lost, they must interpret this metric together with overflow policy and shutdown behavior.
- If `wait_idle()` returned early because the worker failed, `dropped_count()` may still stay unchanged on compatibility shutdown paths even though one leftover closed-queue item remains visible in `pending_count()`.
### Notes
1. This helper reports record loss only; it does not explain the full logger state.
+14 -7
View File
@@ -2,8 +2,8 @@
name: async-logger-error
group: api
category: async
update-time: 20260512
description: Enqueue an error-level record through the async logger using the highest built-in severity shortcut.
update-time: 20260614
description: Enqueue an error-level record through the async logger using the highest built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
@@ -39,8 +39,9 @@ pub async fn[S] AsyncLogger::error(
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Error, ...)`.
- The record is still subject to patching, filtering, and overflow policy.
- This helper delegates to `log(Level::Error, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Error records represent the highest built-in severity in this logger API.
- Use this helper when a named error call is clearer than a raw `log(...)` call.
@@ -52,7 +53,7 @@ Here are some specific examples provided.
When an operation should emit a high-severity failure event:
```moonbit
await logger.error("worker execution failed")
logger.error("worker execution failed")
```
In this example, failure intent is explicit at the call site.
@@ -61,7 +62,7 @@ In this example, failure intent is explicit at the call site.
When an error event should include diagnostic fields:
```moonbit
await logger.error(
logger.error(
"dispatch failed",
fields=[@bitlogger.field("job_id", "42")],
)
@@ -69,6 +70,10 @@ await logger.error(
In this example, the error record carries structured context without falling back to the generic `log(...)` form.
And any shared context already carried by the logger still participates ahead of these per-call fields when the record is built.
The write still uses the logger's stored target because this shortcut does not take a one-off `target=` override.
### Error Case
e.g.:
@@ -80,4 +85,6 @@ e.g.:
1. Use this helper for high-severity async application failures.
2. Emitting an error record is separate from the logger worker itself entering failure state.
2. Use `log(...)` instead when an error call needs a one-off target override.
3. Emitting an error record is separate from the logger worker itself entering failure state.
+9 -5
View File
@@ -2,8 +2,8 @@
name: async-logger-flush-policy
group: api
category: async
update-time: 20260512
description: Read the async logger flush policy currently governing batch and shutdown flushing behavior.
update-time: 20260707
description: Read the async logger flush policy currently governing batch-end and shutdown-end flush-callback timing.
key-word:
- async
- logger
@@ -34,9 +34,13 @@ pub fn[S] AsyncLogger::flush_policy(self : AsyncLogger[S]) -> AsyncFlushPolicy {
Detailed rules explaining key parameters and behaviors
- The returned value reflects the policy captured when the async logger was created.
- `Batch` causes explicit flush calls after worker batch processing.
- `Shutdown` causes explicit flush calls at worker shutdown.
- `Never` leaves flushing entirely to sink behavior or external control.
- `Batch` means the worker invokes the stored async flush callback after each completed drained batch, not after every individual record write.
- `Shutdown` means the worker invokes the stored async flush callback once after the worker loop exits.
- `Never` leaves that explicit callback path unused and relies entirely on sink behavior or external control.
- This helper reports callback timing policy, not a progress count contract.
- The callback is now treated as an attempted flush action with optional failure, not as a meaningful returned item count.
- Builder routes that wrap `RuntimeSink` align with the sync/runtime facade by calling `RuntimeSink::flush_progress()` inside that callback and discarding the returned progress details.
- Text-specific async builder paths still keep the default no-op flush callback even when the visible policy is `Batch` or `Shutdown`, so the reported policy can describe when the callback would run without implying extra sink work actually happens on that path.
### How to Use
+15 -3
View File
@@ -2,8 +2,8 @@
name: async-logger-has-failed
group: api
category: async
update-time: 20260512
description: Read whether the async logger worker has encountered a failure during queue drain.
update-time: 20260707
description: Read whether the async logger worker has recorded a runtime failure after run() startup reset and drain execution.
key-word:
- async
- logger
@@ -34,7 +34,10 @@ pub fn[S] AsyncLogger::has_failed(self : AsyncLogger[S]) -> Bool {}
Detailed rules explaining key parameters and behaviors
- `run()` clears previous failure state at startup.
- If the worker loop raises an error, the logger records that failure and exposes it through this flag.
- `run()` also clears the stored `last_error()` string at startup before drain work begins.
- If the worker loop raises an error, the logger transitions its lifecycle phase to `failed` or `closed_failed` and exposes that failure through this flag.
- Once set by a failed run, the flag stays `true` until a later `run()` invocation actually starts and resets it.
- Failure-driven shutdown does not clear this flag by itself, so `has_failed()` can remain `true` even after the logger is already closed.
- This helper is intentionally compact and should usually be paired with `last_error()` for details.
- Failure state is about runtime drain execution, not whether records were dropped due to overflow policy.
@@ -67,10 +70,19 @@ In this example, the helper exposes a simple pass-fail runtime indicator.
e.g.:
- If `has_failed()` is `false`, queue pressure or dropped records may still exist for non-failure reasons.
- If `has_failed()` becomes `true`, `wait_idle()` may stop early while pending records still remain until a later close or clear path handles them.
- In the current regression coverage, that later handling can be an explicit `close(clear=true)` that resets pending backlog immediately or a `shutdown(...)` path that clears retained pending backlog into dropped records before returning.
- If `has_failed()` is still `true` after shutdown, that does not by itself mean cleanup failed; the logger may already be `phase=closed_failed` and `terminal=true` while the retained failure record is still intentionally visible.
- If `has_failed()` is `true`, callers should inspect `last_error()` or `state()` for more context.
- `close()` or `shutdown()` do not clear this flag by themselves; only a later `run()` that has already started resets it.
### Notes
1. This helper reports worker failure, not general queue stress.
2. Pair it with `last_error()` when you need actionable detail.
3. Pair it with `is_running()` or `state()` when you also need to know whether the worker has already exited and whether backlog remains.
+11 -6
View File
@@ -2,8 +2,8 @@
name: async-logger-info
group: api
category: async
update-time: 20260512
description: Enqueue an info-level record through the async logger using the most common built-in severity shortcut.
update-time: 20260614
description: Enqueue an info-level record through the async logger using the most common built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
@@ -39,8 +39,9 @@ pub async fn[S] AsyncLogger::info(
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Info, ...)`.
- This helper delegates to `log(Level::Info, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Info is often the default operational logging level for async application events.
- Use this helper when explicit info intent is clearer than a raw `log(...)` call.
@@ -52,7 +53,7 @@ Here are some specific examples provided.
When async code should report routine progress or lifecycle events:
```moonbit
await logger.info("worker started")
logger.info("worker started")
```
In this example, the event is expressed at the most common operational logging level.
@@ -61,7 +62,7 @@ In this example, the event is expressed at the most common operational logging l
When an info event should include stable structured detail:
```moonbit
await logger.info(
logger.info(
"job queued",
fields=[@bitlogger.field("queue", "sync")],
)
@@ -69,6 +70,10 @@ await logger.info(
In this example, the record remains concise while still carrying useful metadata.
And any shared context already carried by the logger still participates ahead of these per-call fields when the record is built.
The write still uses the logger's stored target because this shortcut does not take a one-off `target=` override.
### Error Case
e.g.:
@@ -80,4 +85,4 @@ e.g.:
1. This is often the most common convenience method for normal async application events.
2. Use `log(...)` when the call site needs a dynamic level or target override.
2. Use `log(...)` when the call site needs a dynamic level or a one-off target override.
+14 -2
View File
@@ -2,8 +2,8 @@
name: async-logger-is-closed
group: api
category: async
update-time: 20260512
description: Read whether the async logger has been closed and should no longer accept normal new queue traffic.
update-time: 20260707
description: Read whether the async logger has entered closed lifecycle state and should no longer be treated as a normal active enqueue target.
key-word:
- async
- logger
@@ -36,7 +36,11 @@ Detailed rules explaining key parameters and behaviors
- `close(...)` sets the closed state immediately.
- `shutdown(...)` also results in a closed logger by the end of its lifecycle flow.
- A closed logger should no longer be treated as a normal active enqueue target.
- This helper is derived from the current lifecycle phase rather than from an independent closed flag ref.
- This helper reflects lifecycle state only and does not indicate whether the worker is still draining.
- `is_closed()` becoming `true` does not imply the logger reached a clean success state. Failure flags, retained `last_error()`, and remaining backlog-related counters can still reflect the earlier worker outcome.
- After shutdown on a worker-failure path, `is_closed()` can therefore be `true` while retained failure state still remains visible under `phase=closed_failed`.
- Late log attempts are rejected in the shared async log path after closure, so `is_closed()` now aligns with a stable post-close enqueue rejection contract.
### How to Use
@@ -70,8 +74,16 @@ e.g.:
- If callers need to know whether the worker is still active, they should also inspect `is_running()`.
- If callers need to know whether the logger is also terminal or rerunnable, they should inspect `state().terminal` or `state().can_rerun`.
- Closing a logger does not by itself reset `has_failed()` or `last_error()`.
- A closed logger can still report leftover `pending_count()` or `dropped_count()` values from the shutdown path, so `is_closed()` alone is not enough to infer whether backlog was fully drained or explicitly abandoned.
### Notes
1. Closed state and running state are related but not identical.
2. Use this helper when lifecycle control matters more than queue counters.
3. Pair it with `pending_count()`, `is_running()`, or `state()` when you need to understand what closure means for remaining backlog on a live logger instance.
+14 -2
View File
@@ -2,8 +2,8 @@
name: async-logger-is-running
group: api
category: async
update-time: 20260512
description: Read whether the async logger worker is currently running and draining the queue.
update-time: 20260707
description: Read whether the async logger worker loop is currently running, regardless of queue backlog or recorded failure state.
key-word:
- async
- logger
@@ -36,7 +36,11 @@ Detailed rules explaining key parameters and behaviors
- `run()` sets the running state while the worker loop is active.
- The flag is cleared when the worker exits normally or after failure handling finishes.
- A logger may be closed while still running briefly during final drain or shutdown processing.
- After a worker failure, the logger can already be `is_running=false` while still retaining `has_failed=true`, the recorded `last_error()`, and leftover dropped-count cleanup.
- This helper is derived from the current lifecycle phase rather than from an independent running flag ref; it does not wait, synchronize, or infer why the value is what it is.
- This helper focuses on worker activity rather than queue size or failure details.
- `is_running()` can be `false` even when `pending_count()` is still nonzero, for example if the worker was never started or if it exited after a recorded failure.
- A later `run()` attempt can flip `is_running()` back to `true` before that retained failure/backlog state has been fully drained, because the restarted worker clears stale failure state only once the new run has actually started.
### How to Use
@@ -67,10 +71,18 @@ In this example, callers watch the worker lifecycle directly.
e.g.:
- If `is_running()` is `false`, pending records may still exist if the worker was never started or has already failed.
- If `is_running()` is `false`, the logger may also already be closed while still retaining the previous failure record and the cleanup result from shutdown.
- If callers need a one-shot lifecycle flow, `shutdown()` is usually better than manual polling.
- If `is_running()` is `true`, that still does not guarantee healthy drain progress; callers may need `has_failed()` or `state()` for failure context.
- Under concurrent activity, the returned value may change immediately after it is read.
### Notes
1. Use this helper for worker activity checks, not as a complete health signal.
2. Pair it with `has_failed()` or `state()` when diagnosing stalled pipelines.
3. Pair it with `pending_count()` when you need to distinguish an idle worker from a stopped logger that still has backlog.
+14 -3
View File
@@ -2,8 +2,8 @@
name: async-logger-last-error
group: api
category: async
update-time: 20260512
description: Read the last error recorded by the async logger worker during runtime failure handling.
update-time: 20260707
description: Read the last error string recorded by the async logger worker after run() resets and runtime failure capture.
key-word:
- async
- logger
@@ -33,10 +33,13 @@ pub fn[S] AsyncLogger::last_error(self : AsyncLogger[S]) -> String {}
Detailed rules explaining key parameters and behaviors
- `run()` resets the stored error string when the worker starts.
- A later `run()` clears the stored error string only after that run has actually started.
- If the worker loop fails, the error text is captured from the raised exception.
- An empty string normally means no failure has been recorded.
- Once a failure string is recorded, it stays in place until a later `run()` invocation actually starts and clears it.
- Failure-driven shutdown does not clear the stored error string by itself, so the same `last_error()` text can remain visible even after the logger is already closed.
- This helper reports worker execution errors, not ordinary overflow or backpressure conditions.
- That reset happens before the restarted worker resumes drain work.
### How to Use
@@ -67,10 +70,18 @@ In this example, the helper provides the textual failure detail without building
e.g.:
- If no runtime failure has occurred, the method returns an empty string.
- An empty string does not prove the queue is empty or the worker is idle; it only means no failure string is currently recorded.
- If callers need broader context than just the error text, they should use `state()`.
- `close()` or `shutdown()` do not clear a previously recorded error string by themselves; the reset happens only after a later `run()` has already started.
- If the same error string is still present after shutdown, that does not by itself mean cleanup was skipped; the logger may already be `phase=closed_failed` and `terminal=true` while the retained failure record is still intentionally visible.
### Notes
1. Read this helper together with `has_failed()` when interpreting worker health.
2. The stored value is a diagnostic string, not a typed error object.
3. Pair it with `is_running()` or `pending_count()` when you need to know whether failure left the logger with unfinished backlog, because the previous error string can coexist with remaining pending records until later cleanup or restart.
+26 -7
View File
@@ -2,7 +2,7 @@
name: async-logger-log
group: api
category: async
update-time: 20260512
update-time: 20260614
description: Enqueue a record into the async logger with an explicit level, message, optional fields, and optional target override.
key-word:
- async
@@ -13,7 +13,7 @@ key-word:
## Async-logger-log
Enqueue a record into the async logger with an explicit level and message. This is the core write API behind all async level-specific convenience methods.
Enqueue a record into the async logger with an explicit level and message. This is the core write API behind all async level-specific convenience methods and the only built-in async write API that accepts a per-call target override.
### Interface
@@ -43,10 +43,14 @@ pub async fn[S] AsyncLogger::log(
Detailed rules explaining key parameters and behaviors
- The logger checks `is_enabled(level)` before building a record.
- Compatibility runtimes that guard closed writes first can return immediately when `is_closed()` is already `true`, before level checks, record construction, patch logic, filter logic, or queue work.
- Otherwise the logger checks `is_enabled(level)` before building a record.
- If `target` is empty, the logger uses its stored default target. If `target` is non-empty, that value overrides the stored target for this call only.
- Context fields, patch logic, and filter logic are applied before enqueue.
- If timestamping is enabled, `@env.now()` is captured before the record enters the queue.
- Overflow behavior depends on the configured `AsyncOverflowPolicy`.
- Closed-on-log behavior is runtime-dependent: compatibility runtimes short-circuit before record-building work, while native-worker runtimes may still build, patch, and filter the record before queue operations treat a closed queue as a non-accepted write.
- In the tested blocking-policy path, a late write against an already closed logger does not become a newly accepted pending record and does not add another dropped record; it simply fails to enter the queue after any runtime-specific pre-queue work finishes.
### How to Use
@@ -56,7 +60,7 @@ Here are some specific examples provided.
When code should choose level, fields, and target per event:
```moonbit
await logger.log(
logger.log(
@bitlogger.Level::Info,
"worker started",
fields=[@bitlogger.field("job", "sync")],
@@ -64,13 +68,22 @@ await logger.log(
)
```
In this example, all per-record inputs are supplied explicitly.
In this example, the record overrides the logger's stored target only for this call.
#### When Reuse The Stored Async Target
When a call should keep the logger's existing target:
```moonbit
logger.log(@bitlogger.Level::Warn, "slow request")
```
In this example, the logger falls back to its stored target because no `target=` override is provided.
#### When Build Higher-level Async Logging Helpers
When application code wants a custom wrapper around the base API:
```moonbit
await logger.log(@bitlogger.Level::Warn, "slow request")
logger.log(@bitlogger.Level::Warn, "slow request")
```
In this example, `log(...)` acts as the common primitive under custom wrappers or convenience methods.
@@ -82,8 +95,14 @@ e.g.:
- If the logger is closed or overflow policy rejects the record, enqueue may not proceed as a normal accepted write.
- A closed queue does not count as a newly accepted pending record.
- On compatibility runtimes, a late call after close can be skipped before patch or filter logic runs at all.
- On native-worker runtimes, a late call after close can still execute patch and filter logic before the queue rejects the write.
### Notes
1. Use this API when the call site needs full control instead of a fixed severity helper.
2. Prefer `info()`, `warn()`, and the other shortcuts when only the level differs.
2. Prefer `info()`, `warn()`, and the other shortcuts when only the level differs and no per-call target override is needed.
+12 -2
View File
@@ -2,8 +2,8 @@
name: async-logger-pending-count
group: api
category: async
update-time: 20260512
description: Read the current number of queued records that have not yet been drained by the async logger worker.
update-time: 20260614
description: Read the current number of queued records that have not yet been drained or cleared from the async logger pipeline.
key-word:
- async
- logger
@@ -35,6 +35,8 @@ Detailed rules explaining key parameters and behaviors
- The count increases when records are accepted into the queue.
- The count decreases as the worker drains records.
- The count is also reset to `0` when queued records are abandoned through clear-close paths such as `close(clear=true)`.
- Log attempts against a closed queue do not create new pending backlog.
- This is a point-in-time metric and may change immediately after it is read.
- Use this helper when a single backlog number is enough and a full `state()` snapshot is unnecessary.
@@ -67,6 +69,12 @@ In this example, the queue backlog is checked directly.
e.g.:
- If the worker is not running, `pending_count()` may stay above `0` until records are drained or cleared.
- If `wait_idle()` returns early because `has_failed()` became `true`, `pending_count()` may still be above `0` until later cleanup or clear-close handling runs.
- In the current tested split, that later cleanup can either force the leftover pending item into `dropped_count()` on native-worker shutdown paths or leave the closed-queue remainder visible in `pending_count()` on compatibility shutdown paths.
- That means `pending_count()` can still stay above `0` even after `is_closed=true` on compatibility-style shutdown paths, because closure there does not necessarily convert the leftover failed backlog into dropped records.
- A later restarted `run()` can still drain that retained backlog after failure-reset startup, so a nonzero pending count after failure is not necessarily a permanent terminal state.
- If the queue is empty, the method simply returns `0`.
### Notes
@@ -74,3 +82,5 @@ e.g.:
1. Use `state()` when you also need dropped counts, failure state, or runtime mode.
2. This helper is useful for lightweight health checks and tests.
3. Pair it with `is_running()` or `has_failed()` when backlog alone is not enough to explain whether the worker is actively draining, stopped, or failed.
+26 -8
View File
@@ -2,8 +2,8 @@
name: async-logger-run
group: api
category: async
update-time: 20260512
description: Start the async logger worker loop so queued records are drained to the underlying sink.
update-time: 20260707
description: Start the async logger worker loop so queued records are drained to the underlying sink while lifecycle and failure state are reset and updated around execution.
key-word:
- async
- logger
@@ -13,7 +13,7 @@ key-word:
## Async-logger-run
Start the async logger worker loop. This is the core runtime API that drains queued records to the underlying sink and updates worker lifecycle state.
Start the async logger worker loop. This is the core runtime API that drains queued records to the underlying sink and updates worker lifecycle state around that drain loop.
### Interface
@@ -33,10 +33,16 @@ pub async fn[S : @bitlogger.Sink] AsyncLogger::run(self : AsyncLogger[S]) -> Uni
Detailed rules explaining key parameters and behaviors
- `run()` sets `is_running` to `true` while the worker loop is active.
- It clears previous failure state before worker execution begins.
- On failure, the logger records `has_failed=true` and stores the error text in `last_error`.
- The worker exits when the queue is closed or when a failure aborts processing.
- `run()` sets `is_running` to `true` before worker execution begins.
- `run()` moves the logger lifecycle phase from `ready` or `failed` into `running`.
- `run()` now enforces a single-worker contract. If the same logger already has a running worker, the later call raises `AsyncLoggerAlreadyRunning` instead of silently starting another drain loop.
- If the logger is already closed, `run()` raises `AsyncLoggerClosed` instead of trying to restart a terminal logger.
- Every invocation clears previous failure state first by setting `has_failed=false` and `last_error()` to an empty string once that `run()` call has actually started executing.
- The method then keeps draining records until `queue.get()` stops with `AsyncLoggerClosed` or a worker error escapes.
- On a normal queue-close exit, `run()` leaves the logger in `closed` if shutdown/close had already started, otherwise it returns to `ready`.
- On failure, the logger records `last_error`, transitions to `failed` or `closed_failed`, and then raises the error back out of `run()`.
- A worker failure does not guarantee the async backlog was fully drained first. If the failure happens after some records were already written, later queued records can remain pending when `run()` exits.
- A later `run()` invocation can resume draining that retained backlog, but the stale failure flag and stale `last_error()` value are only cleared after the new worker call actually begins running.
### How to Use
@@ -68,12 +74,24 @@ In this example, the application decides when the worker begins instead of hidin
### Error Case
e.g.:
- If the worker loop fails, `has_failed()` becomes `true` and `last_error()` stores the error text.
- If the worker loop fails, `has_failed()` becomes `true`, `last_error()` stores the error text, and `run()` raises that failure to the caller.
- After a failed run, `pending_count()` can still be greater than zero until later shutdown or restart logic finishes handling the retained backlog.
- If `run()` is never started, accepted records may remain queued and not reach the sink.
- A later `run()` attempt starts from a fresh failure flag and empty `last_error()` string once that retrying worker has actually started, even if an earlier run failed.
- Starting more than one `run()` task for the same logger raises `AsyncLoggerAlreadyRunning` on the later attempt.
- Starting `run()` after a terminal close path raises `AsyncLoggerClosed`.
### Notes
1. `async_logger(...)` only constructs the logger; `run()` is what activates queue draining.
2. Pair this API with `shutdown()` for a complete worker lifecycle.
3. Pair it with `has_failed()`, `last_error()`, or `state()` when tests need to inspect how a worker exit affected logger health.
4. Start one deliberate worker task per logger. A concurrent second startup now fails explicitly with `AsyncLoggerAlreadyRunning`.
+23 -8
View File
@@ -2,8 +2,8 @@
name: async-logger-shutdown
group: api
category: async
update-time: 20260512
description: Gracefully stop an async logger by waiting for idle or clearing queued work, then waiting for the worker to finish.
update-time: 20260707
description: Gracefully stop an async logger by waiting for idle or clearing queued work, then waiting for any active worker to finish.
key-word:
- async
- logger
@@ -35,9 +35,12 @@ pub async fn[S] AsyncLogger::shutdown(self : AsyncLogger[S], clear? : Bool = fal
Detailed rules explaining key parameters and behaviors
- `clear=false` first waits for idle, then closes the logger.
- If backlog still remains after waiting, shutdown falls back to `close(clear=true)`.
- `clear=true` immediately closes and abandons pending records.
- The method waits until `is_running()` becomes `false` before returning.
- If `wait_idle()` returned early because a worker failure left backlog behind, shutdown now converts that retained pending backlog into dropped records with `close(clear=true)` before returning.
- `clear=true` immediately closes and abandons pending records, even if no worker was ever started for that logger.
- After either shutdown branch starts closing, the helper waits until `is_running()` becomes `false` before returning.
- In lifecycle-phase terms, shutdown drives the logger into `closing` while a worker is still active, then settles into `closed` or `closed_failed` once that worker has exited.
- Shutdown itself does not clear retained worker failure state. If a previous `run()` already recorded `has_failed=true` and a non-empty `last_error()`, those diagnostics can remain visible after shutdown completes.
- Because `clear=false` delegates to `wait_idle()` first, shutdown can also wait indefinitely when pending records exist but no worker is making progress and no failure flag is raised.
### How to Use
@@ -47,7 +50,7 @@ Here are some specific examples provided.
When a service should stop logging only after queued records are drained:
```moonbit
await logger.shutdown()
logger.shutdown()
```
In this example, the logger waits for normal drain behavior before final closure.
@@ -56,7 +59,7 @@ In this example, the logger waits for normal drain behavior before final closure
When teardown should prefer speed over preserving backlog:
```moonbit
await logger.shutdown(clear=true)
logger.shutdown(clear=true)
```
In this example, pending work is abandoned intentionally so shutdown can complete sooner.
@@ -66,10 +69,22 @@ In this example, pending work is abandoned intentionally so shutdown can complet
e.g.:
- If `clear=true`, pending records are intentionally dropped rather than drained.
- If `wait_idle()` returns early because the worker failed, shutdown still finishes by converting retained pending backlog into dropped records before it returns.
- Even after shutdown finishes with `is_closed=true`, callers can still observe retained `has_failed()` and `last_error()` from an earlier worker failure.
- If pending work exists but no worker was started, `shutdown(clear=false)` may never reach its later close step because it is still waiting inside `wait_idle()`.
- If callers skip `shutdown()` and only inspect flags manually, it is easier to leave the worker lifecycle in an unclear state.
### Notes
1. Prefer this API over raw `close()` in normal application shutdown paths.
2. Choose `clear=true` only when loss of queued records is acceptable.
2. `shutdown()` now always waits for an already-running worker to leave `is_running=true` before returning.
3. Choose `clear=true` only when loss of queued records is acceptable.
4. Pair it with `state()` or focused counters when tests need to assert whether shutdown drained backlog or converted it into dropped records.
5. Prefer `shutdown(clear=true)` when teardown must not depend on a still-running drain worker.
+114
View File
@@ -0,0 +1,114 @@
---
name: async-logger-state-new
group: api
category: async
update-time: 20260707
description: Construct an AsyncLoggerState snapshot from explicit runtime, lifecycle phase, queue, failure text, and flush-policy values without probing a live logger.
key-word:
- async
- logger
- state
- public
---
## Async-logger-state-new
Construct an `AsyncLoggerState` snapshot from explicit runtime, queue, lifecycle, and failure values. This is the low-level constructor behind the public async logger state shape used in diagnostics.
### Interface
```moonbit
pub fn AsyncLoggerState::new(
runtime : AsyncRuntimeState,
phase : AsyncLifecyclePhase,
pending_count : Int,
dropped_count : Int,
last_error : String,
flush_policy : AsyncFlushPolicy,
) -> AsyncLoggerState {
```
#### input
- `runtime : AsyncRuntimeState` - Embedded backend-level runtime snapshot.
- `phase : AsyncLifecyclePhase` - Primary lifecycle conclusion for the snapshot.
- `pending_count : Int` - Current async queue backlog.
- `dropped_count : Int` - Current dropped-record count.
- `last_error : String` - Latest error text, or an empty string when no failure has been recorded.
- `flush_policy : AsyncFlushPolicy` - Active async flush policy for the logger.
#### output
- `AsyncLoggerState` - Full async logger snapshot containing the supplied runtime, queue, lifecycle, and failure values.
### Explanation
Detailed rules explaining key parameters and behaviors
- This constructor packages the supplied runtime, phase, counters, last error, and flush policy into one public snapshot value.
- It does not inspect a live logger instance by itself.
- `AsyncLogger::state()` is the higher-level API that reads these values from a concrete logger.
- It also does not validate whether the supplied fields represent a combination that could come from one real logger instant.
- The supplied `runtime : AsyncRuntimeState` is stored exactly as provided; this constructor does not recompute `mode` or `background_worker` from the active backend.
- The constructor derives `is_closed`, `is_running`, `has_failed`, `backlog_retained`, `can_rerun`, and `terminal` from the supplied `phase` and `pending_count`.
- The constructed value matches the same public shape used by async logger serializers.
- Because `AsyncLoggerState` is only a data snapshot type, this constructor is mainly useful for tests, adapters, and synthetic diagnostics rather than ordinary logger inspection.
- Serialization helpers accept any `AsyncLoggerState` value, including hand-built ones from this constructor.
- That includes combinations such as `has_failed=true` together with non-zero `pending_count` or a retained `last_error`, and even a manually chosen runtime snapshot that does not match the current backend, all of which are valid for diagnostic snapshots and test fixtures.
### How to Use
Here are some specific examples provided.
#### When Need A Hand-built Async Logger Snapshot
When tests or adapters should construct a full async logger state explicitly:
```moonbit
let state = AsyncLoggerState::new(
runtime=AsyncRuntimeState::new(AsyncRuntimeMode::Compatibility, false),
phase=AsyncLifecyclePhase::Running,
pending_count=0,
dropped_count=0,
last_error="",
flush_policy=AsyncFlushPolicy::Never,
)
```
In this example, a complete async logger snapshot is assembled directly without querying a live logger instance.
#### When Need Structured Diagnostics Input Before Serialization
When code should prepare a typed logger state value for later export:
```moonbit
let state = AsyncLoggerState::new(
runtime=async_runtime_state(),
phase=logger.phase(),
pending_count=logger.pending_count(),
dropped_count=logger.dropped_count(),
last_error=logger.last_error(),
flush_policy=logger.flush_policy(),
)
```
In this example, callers still use the direct constructor while making each diagnostic input explicit.
### Error Case
e.g.:
- This constructor itself does not have a normal failure mode; it only packages the provided values.
- If callers want a snapshot directly from one live logger instance, `AsyncLogger::state()` is the simpler API.
- If callers manually combine a runtime snapshot, counters, or flush policy that do not actually belong together, the constructor still accepts that synthetic snapshot.
- If callers want the current backend-derived runtime pair instead of a synthetic one, they must pass `async_runtime_state()` explicitly or use `AsyncLogger::state()`.
- This constructor does not apply cleanup semantics such as clearing `last_error` on restart or draining pending records; callers must provide the phase and counters exactly as they want them represented.
### Notes
1. Use this helper when code should construct an `AsyncLoggerState` value explicitly.
2. Pair it with `async_logger_state_to_json(...)` or `stringify_async_logger_state(...)` when the snapshot should be exported.
3. Prefer `AsyncLogger::state()` when the goal is to report the actual current state of one live logger instance.
+22 -8
View File
@@ -2,8 +2,8 @@
name: async-logger-state-to-json
group: api
category: async
update-time: 20260512
description: Convert an AsyncLoggerState snapshot into a JSON value for diagnostics and transport.
update-time: 20260707
description: Convert an AsyncLoggerState snapshot into a JSON value for diagnostics and transport using the canonical nested runtime shape and flush-policy labels.
key-word:
- async
- state
@@ -13,7 +13,7 @@ key-word:
## Async-logger-state-to-json
Convert `AsyncLoggerState` into a `JsonValue`. This helper is the structured export path for async logger runtime snapshots when callers want machine-readable diagnostics instead of a plain string.
Convert `AsyncLoggerState` into a `JsonValue`. This helper is the structured export path for async logger runtime snapshots or manually constructed state values when callers want machine-readable diagnostics instead of a plain string.
### Interface
@@ -23,7 +23,7 @@ pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.Json
#### input
- `state : AsyncLoggerState` - Snapshot produced by `AsyncLogger::state()`.
- `state : AsyncLoggerState` - Snapshot produced by `AsyncLogger::state()` or any manually constructed `AsyncLoggerState` value.
#### output
@@ -33,10 +33,16 @@ pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.Json
Detailed rules explaining key parameters and behaviors
- The JSON includes runtime mode, worker support, queue counters, lifecycle flags, last error, and flush policy.
- The JSON includes runtime mode, lifecycle phase, derived lifecycle conclusions, queue counters, last error, and flush policy.
- The top-level fields are `runtime`, `phase`, `pending_count`, `dropped_count`, `is_closed`, `is_running`, `has_failed`, `backlog_retained`, `can_rerun`, `terminal`, `last_error`, and `flush_policy`.
- The nested `runtime` field reuses `async_runtime_state_to_json(...)`, and `flush_policy` is serialized with the canonical labels `Never`, `Batch`, or `Shutdown`.
- The public helper returns the same internal JSON snapshot shape used by `stringify_async_logger_state(...)`, so both export paths stay aligned without duplicate field assembly logic.
- This helper is suitable for health endpoints, diagnostics payloads, and custom serialization flows.
- It shares the same stable field names used by `stringify_async_logger_state(...)`.
- The state must already have been captured before serialization.
- The state must already have been captured or constructed before serialization.
- Serialization preserves whatever snapshot combination it receives, including lifecycle phase and failure/backlog combinations together with remaining backlog counts.
- This helper never rereads a logger instance by itself. If callers pass an older or manually constructed `AsyncLoggerState`, the JSON reflects that provided value exactly rather than refreshing fields from live runtime state.
- It also does not normalize mixed diagnostic combinations. If the provided snapshot says `is_closed=true` together with retained `has_failed=true`, `last_error`, or non-zero backlog counters, those exact combinations are emitted unchanged.
### How to Use
@@ -68,9 +74,17 @@ e.g.:
- If the queue is empty, `pending_count` and `dropped_count` are still serialized normally as numeric values.
- If `has_failed` is `true`, serialization does not force `pending_count` to `0` or clear `last_error`; it reports the snapshot exactly as provided.
- If callers need newer logger data, they must capture a fresh `AsyncLogger::state()` first instead of expecting JSON conversion itself to refresh stale fields.
### Notes
1. Use this API when downstream code wants a JSON value rather than a ready-made string.
1. This helper preserves the nested runtime snapshot instead of flattening `mode` and `background_worker` onto the top level.
2. Pair it with `AsyncLogger::state()` to capture the snapshot first.
2. The resulting object matches the compact string form produced by `stringify_async_logger_state(...)` after JSON stringification.
3. Use this API when downstream code wants a JSON value rather than a ready-made string.
4. Pair it with `AsyncLogger::state()` when you want current logger data, or with `AsyncLoggerState::new(...)` when tests or adapters are exporting a synthetic snapshot.
+87
View File
@@ -0,0 +1,87 @@
---
name: async-logger-state-type
group: api
category: async
update-time: 20260707
description: Public async logger state alias used for queue, lifecycle phase, derived lifecycle conclusions, runtime, and flush-policy diagnostics.
key-word:
- async
- logger
- state
- public
---
## Async-logger-state-type
`AsyncLoggerState` is the public snapshot type used to describe an async logger's runtime status. It is a direct alias to the async logger state model returned by `AsyncLogger::state()` and used by async diagnostics serializers.
### Interface
```moonbit
pub type AsyncLoggerState = @utils.AsyncLoggerState
```
#### output
- `AsyncLoggerState` - Public async logger snapshot containing `runtime`, `phase`, queue counters, derived lifecycle conclusions, `last_error`, and `flush_policy`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a live logger handle.
- The `runtime` field embeds an `AsyncRuntimeState` snapshot.
- The remaining fields capture queue counts, lifecycle phase, derived lifecycle conclusions, failure state, last error text, and active flush policy.
- `AsyncLogger::state()` returns this type directly, while `async_logger_state_to_json(...)` and `stringify_async_logger_state(...)` export the same data shape for diagnostics.
- `AsyncLogger::state()` currently builds this snapshot from `async_runtime_state()` plus the logger's current counters, lifecycle phase, last error, and flush policy.
- When the value comes from `AsyncLogger::state()`, the fields are read one by one rather than through a transactional snapshot primitive.
- `AsyncLoggerState::new(...)` can also construct this type manually, but manual construction is synthetic snapshot data and does not read one live logger instant by itself.
- The type itself does not distinguish live logger snapshots from hand-built ones; callers must track whether a given `AsyncLoggerState` value came from `AsyncLogger::state()` or from manual construction.
- `phase` is the strongest lifecycle conclusion in the snapshot, while `is_closed`, `is_running`, `has_failed`, `backlog_retained`, `can_rerun`, and `terminal` are the main derived convenience reads.
- When a live snapshot is taken after worker failure, `phase=failed`, a retained `last_error`, `backlog_retained=true`, and non-zero `pending_count` may legitimately coexist until later cleanup or restart.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Snapshot For Async Diagnostics
When application code should inspect async logger state before deciding how to report it:
```moonbit
let state : AsyncLoggerState = logger.state()
```
In this example, queue, lifecycle, and runtime information stay available as structured data.
#### When Need To Branch On Failure Or Backlog
When code should react to the current async logger condition:
```moonbit
let state = logger.state()
if state.has_failed || state.pending_count > 0 {
println(stringify_async_logger_state(state, pretty=true))
}
```
In this example, the typed snapshot supports both logic and later export.
### Error Case
e.g.:
- `AsyncLoggerState` itself does not have a runtime failure mode.
- `last_error` may be an empty string when no failure has occurred, which is normal and not a special error condition by itself.
- Because this is just a data shape, manual construction can represent combinations that do not come from a live logger at one exact instant.
- Receiving an `AsyncLoggerState` value alone does not prove it came from one current logger read rather than from a synthetic constructor path.
- This type does not imply cleanup semantics by itself; values only report the supplied or captured fields and do not drain backlog or clear recorded failure state.
### Notes
1. Use `AsyncLogger::state()` when you need a value of this type from one logger instance.
2. Use `AsyncRuntimeState` when only backend-level capability information is needed and logger-instance state is unnecessary.
3. Use `async_logger_state_to_json(...)` or `stringify_async_logger_state(...)` when this snapshot should leave typed space and become stable diagnostic output.
+33 -6
View File
@@ -2,8 +2,8 @@
name: async-logger-state
group: api
category: async
update-time: 20260512
description: Read a full async logger runtime snapshot including queue counters, lifecycle flags, and runtime mode.
update-time: 20260707
description: Read a full async logger runtime snapshot including runtime, lifecycle phase, derived lifecycle conclusions, queue counters, last error, and flush policy.
key-word:
- async
- state
@@ -27,16 +27,27 @@ pub fn[S] AsyncLogger::state(self : AsyncLogger[S]) -> AsyncLoggerState {}
#### output
- `AsyncLoggerState` - A snapshot containing runtime mode, worker support, queue counts, lifecycle flags, last error, and flush policy.
- `AsyncLoggerState` - A snapshot containing runtime mode, lifecycle phase, derived lifecycle conclusions, queue counts, last error, and flush policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- `AsyncLoggerState` includes `runtime`, `pending_count`, `dropped_count`, `is_closed`, `is_running`, `has_failed`, `last_error`, and `flush_policy`.
- `state()` returns a point-in-time snapshot rather than a live handle.
- `AsyncLoggerState` includes `runtime`, `phase`, `pending_count`, `dropped_count`, `is_closed`, `is_running`, `has_failed`, `backlog_retained`, `can_rerun`, `terminal`, `last_error`, and `flush_policy`.
- `state()` returns a value snapshot rather than a live handle.
- This helper is equivalent to `AsyncLoggerState::new(async_runtime_state(), self.phase(), self.pending_count(), self.dropped_count(), self.last_error(), self.flush_policy())`.
- `async_logger_state_to_json(...)` and `stringify_async_logger_state(...)` convert the snapshot to stable diagnostic output.
- `runtime` embeds the result of `async_runtime_state()` so callers do not need to join separate helpers manually.
- `runtime` embeds the result of `async_runtime_state()` from the moment `state()` is called, so callers do not need to join separate helpers manually.
- The runtime portion is therefore recomputed from the active backend helper on each call, while the remaining fields come from the logger's current counters, lifecycle phase, derived lifecycle conclusions, and flush policy at that same general moment.
- Because the snapshot is assembled field by field when `state()` is called, later logger changes require calling `state()` again rather than reusing an older `AsyncLoggerState` value as if it refreshed itself.
- That field-by-field assembly also means this helper is not an atomic freeze across all refs; under concurrent logger activity, neighboring fields can reflect slightly different instants.
- `phase` is the primary lifecycle conclusion. The current public phases are `ready`, `running`, `failed`, `closing`, `closed`, and `closed_failed`.
- `backlog_retained=true` means pending backlog still exists while the logger is no longer actively draining it.
- `can_rerun=true` means the current phase still allows a future `run()` call to restart drain work.
- `terminal=true` means the logger is closed, no worker is active, and no pending backlog remains.
- After a worker failure, snapshots can legitimately report `phase=failed`, `has_failed=true`, `backlog_retained=true`, a non-empty `last_error`, and `pending_count>0` together until later cleanup or restart changes them.
- After shutdown cleanup on an already failed logger, snapshots can also legitimately report `phase=closed_failed`, `is_closed=true`, `terminal=true`, retained `has_failed=true`, and the same `last_error()`.
- `state()` only reports the current field values; it does not clear failure state, drain backlog, or synchronize pending work by itself.
### How to Use
@@ -66,6 +77,8 @@ if state.has_failed {
In this example, the same snapshot object works for conditional diagnostics and serialization.
And the reported failure fields can still appear together with non-zero backlog when a worker stopped early.
### Error Case
e.g.:
@@ -73,8 +86,22 @@ e.g.:
- If the queue is empty, `pending_count` is `0`; this is normal and not a special error condition.
- `flush_policy` reports the logger's configured async flush mode, not whether a flush has already happened.
- The embedded `runtime` object is also just a snapshot of the current backend helper result at read time; `state()` does not cache it onto the logger for later reuse.
- If concurrent logger activity is still changing counters or flags while `state()` runs, the returned value is still useful for diagnostics but should not be treated as a transactional snapshot.
- A snapshot showing `has_failed=true` does not imply `pending_count` is already `0`; remaining queued records may still be visible until later cleanup or restart.
- A snapshot showing `is_closed=true` also does not imply failure state was cleared; after failure-driven shutdown, `phase=closed_failed`, `has_failed=true`, and the recorded `last_error` can still remain visible until a later `run()` actually restarts the logger.
### Notes
1. Prefer this API over manually combining `pending_count()`, `dropped_count()`, and runtime-mode helpers.
2. Use `pretty=true` when emitting logs for humans and the compact form for machine-oriented payloads.
3. Use `AsyncLoggerState::new(...)` only when tests or adapters need to construct a manual snapshot instead of reading one directly from a logger instance.
4. If consumers need stronger cross-field consistency than a diagnostic snapshot, they should not assume `state()` provides an atomic read barrier.
@@ -0,0 +1,98 @@
---
name: async-logger-to-library-async-logger
group: api
category: facade
update-time: 20260707
description: Convert a full async logger into the narrower library-facing async facade without rebuilding or detaching the underlying async state.
key-word:
- async
- library
- facade
- public
---
## Async-logger-to-library-async-logger
Convert `AsyncLogger[S]` into `LibraryAsyncLogger[S]`. This keeps the same async queue and sink behavior while projecting the value onto the smaller library-facing async surface.
### Interface
```moonbit
pub fn[S] AsyncLogger::to_library_async_logger(self : AsyncLogger[S]) -> LibraryAsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Full async logger to project into the library facade.
#### output
- `LibraryAsyncLogger[S]` - Narrower library-facing wrapper over the same async logger state.
### Explanation
Detailed rules explaining key parameters and behaviors
- This conversion does not rebuild the queue, sink, or runtime state.
- Target, min level, async config, flush behavior, pending counts, and failure state are preserved because the same underlying async logger value is wrapped.
- The original `AsyncLogger[S]` handle remains the same live logger. If caller code keeps that original value, later facade calls and later unwraps still observe the same shared queue, counters, sink helpers, and lifecycle mutations.
- The returned facade keeps library-facing async operations including `log(...)`, `run()`, and `shutdown(...)`.
- Async inspection helpers and broader composition APIs remain on the underlying `AsyncLogger[S]` and are intentionally hidden until `to_async_logger()` is used again.
- If later facade-level `run()` or `shutdown()` calls record worker failure or perform shutdown cleanup, unwrapping later still exposes that same post-call state instead of a translated facade copy.
- That includes states where delegated shutdown already finished with `is_closed=true` while retained `has_failed()` and `last_error()` remain on the wrapped logger, together with the same retained dropped-count cleanup outcome.
- When `S` itself exposes richer runtime helpers, projecting to the library facade does not strip those capabilities from the wrapped logger; they are still reachable after `to_async_logger()`.
- When `S` is `RuntimeSink`, projection also preserves queued runtime state and file-backed runtime helper behavior behind the facade instead of replacing them with a library-specific copy.
- Unwrapping later with `to_async_logger()` therefore exposes the same queue counters, failure snapshots, file state, and runtime file controls that the original async logger already carried.
- That also means runtime sink mutations still alias in both directions: changing file append mode, auto-flush, rotation, or other sink helper state through a later unwrapped logger changes the same live runtime sink that the original `AsyncLogger[S]` already held.
### How to Use
Here are some specific examples provided.
#### When Need To Expose A Narrower Async Type
When internal setup uses the full async logger API but public library code should return a smaller facade:
```moonbit
let logger = async_logger(console_sink(), target="lib.async")
let public_logger = logger.to_library_async_logger()
```
In this example, `public_logger` keeps the same async behavior but exposes the library-facing facade.
#### When Need To Narrow Surface Without Resetting Runtime State
When an already-used async logger should be projected to a library boundary without changing its current state:
```moonbit
let full = build_async_logger(config)
let public_logger = full.to_library_async_logger()
ignore(public_logger.is_enabled(@bitlogger.Level::Info))
```
In this example, the projection changes the exposed type only; it does not rebuild queue or lifecycle state.
If `full` is still kept elsewhere, it continues sharing that same live runtime state with `public_logger`.
### Error Case
e.g.:
- If callers later need APIs outside the library facade, they must unwrap with `to_async_logger()`.
- The conversion does not clear pending items or reset runtime state.
- If callers later need state helpers such as `pending_count()` or `state()`, they must unwrap again with `to_async_logger()`.
- Projection does not normalize failure snapshots; a later unwrap can still show combinations such as retained `last_error()` with remaining `pending_count()` when the wrapped async logger really ended up in that state.
- Projection also does not normalize delegated shutdown results; a later unwrap can still show `is_closed=true` together with retained `has_failed()`, the same `last_error()`, and the same retained dropped-count cleanup that accumulated behind the facade.
- Projection also does not normalize richer runtime sink state; if the original async logger already carried queued runtime data or file-backed helper state, a later unwrap still exposes that same live state.
- Projection does not create an isolated wrapper copy. If callers keep the original `AsyncLogger[S]`, then later facade-level writes, shutdown, or sink-helper mutations still affect that original handle too.
### Notes
1. Use this when package boundaries should avoid exposing the full async logger type.
2. This is a projection API, not a reconfiguration step.
3. Use `build_library_async_logger(...)` or `build_library_async_text_logger(...)` when construction and narrowing should happen together from config.
+9 -6
View File
@@ -2,8 +2,8 @@
name: async-logger-trace
group: api
category: async
update-time: 20260512
description: Enqueue a trace-level record through the async logger using the lowest built-in severity shortcut.
update-time: 20260614
description: Enqueue a trace-level record through the async logger using the lowest built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
@@ -39,8 +39,9 @@ pub async fn[S] AsyncLogger::trace(
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Trace, ...)`.
- This helper delegates to `log(Level::Trace, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Trace records are often skipped in production because they are the lowest built-in severity.
- Use this helper when explicit trace intent is clearer than a raw `log(...)` call.
@@ -52,7 +53,7 @@ Here are some specific examples provided.
When low-level execution flow should be observable during debugging:
```moonbit
await logger.trace("entered reconciliation step")
logger.trace("entered reconciliation step")
```
In this example, the call site makes trace intent explicit.
@@ -61,7 +62,7 @@ In this example, the call site makes trace intent explicit.
When a trace event should carry extra fields:
```moonbit
await logger.trace(
logger.trace(
"cache probe",
fields=[@bitlogger.field("key", "user:42")],
)
@@ -80,4 +81,6 @@ e.g.:
1. Prefer this helper when trace intent is more readable than `log(Level::Trace, ...)`.
2. Trace-level async logging can increase queue pressure quickly under verbose workloads.
2. Use `log(...)` instead when one trace call needs a one-off target override.
3. Trace-level async logging can increase queue pressure quickly under verbose workloads.
+100
View File
@@ -0,0 +1,100 @@
---
name: async-logger-type
group: api
category: async
update-time: 20260707
description: Public asynchronous logger root type used for queue-backed sink-preserving logging pipelines with explicit lifecycle, failure, and flush-callback state.
key-word:
- async
- logger
- type
- public
---
## Async-logger-type
`AsyncLogger[S]` is the public asynchronous root logger type. It stores a concrete sink type `S` together with queue policy, runtime state counters, and lifecycle state, then serves as the base value for async composition, worker control, and async write APIs.
### Interface
```moonbit
pub struct AsyncLogger[S] {
min_level : @bitlogger.Level
target : String
timestamp : Bool
overflow : AsyncOverflowPolicy
max_batch : Int
linger_ms : Int
flush_policy : AsyncFlushPolicy
sink : S
flush_callback : (S) -> Unit raise
context_fields : Array[@bitlogger.Field]
filter : (@bitlogger.Record) -> Bool
patch : @bitlogger.RecordPatch
queue : @async.Queue[@bitlogger.Record]
pending_count : Ref[Int]
dropped_count : Ref[Int]
phase : Ref[AsyncLifecyclePhase]
last_error : Ref[String]
}
```
#### output
- `AsyncLogger[S]` - Public queue-backed asynchronous logger value parameterized by the concrete sink type `S`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a public root struct, not a type alias.
- The current fields cover targeting, overflow policy, batching, linger timing, flush policy, the wrapped sink, context/filter/patch behavior, queue state, and the internal lifecycle phase model.
- `flush_callback : (S) -> Unit raise` stores the raising flush callback used by batch and shutdown flush policies.
- `pending_count`, `dropped_count`, `phase`, and `last_error` are the mutable runtime refs that power the higher-level lifecycle and diagnostics helpers.
- `phase` is the stronger internal lifecycle source of truth; helper reads such as `is_closed()`, `is_running()`, `has_failed()`, and the richer `state()` snapshot are derived from it.
- The sink type parameter is preserved across async composition, which is why helpers such as `with_target(...)`, `with_context_fields(...)`, `with_filter(...)`, and `with_patch(...)` keep returning `AsyncLogger[S]`.
- `async_logger(...)` constructs this type as the main asynchronous entry point.
- This root type is also what sits underneath both async facade families: `ApplicationAsyncLogger` is a direct alias over the runtime-sink line, while `LibraryAsyncLogger[S]` is a narrowing wrapper around an `AsyncLogger[S]` value.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Root Async Logger Value
When queue-backed logging should begin from a concrete sink-preserving root object:
```moonbit
let logger : AsyncLogger[ConsoleSink] = async_logger(console_sink(), target="app.async")
```
In this example, the root async logger keeps the concrete sink type visible for later typed composition.
#### When Need Worker Control Plus Runtime State
When code should both enqueue logs and inspect async runtime behavior:
```moonbit
let logger = async_logger(console_sink(), config=AsyncLoggerConfig::new(max_pending=32))
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, the root type combines queue-backed logging with explicit runtime observability.
### Error Case
e.g.:
- `AsyncLogger[S]` itself does not have a runtime failure mode.
- Actual enqueue, worker, and flush behavior still depends on the wrapped sink `S`, async runtime support, and the configured queue policy.
- Post-close logging behavior now follows the shared close contract rather than backend-specific divergence, but lifecycle interpretation should still prefer `state()` over manual field reasoning.
### Notes
1. Use `async_logger(...)`, `build_async_logger(...)`, or `build_async_text_logger(...)` when you need a value of this type.
2. Use `ApplicationAsyncLogger` when application code wants the same full async lifecycle and state helper surface under an app-facing alias.
3. Use `LibraryAsyncLogger[S]` when a library boundary should intentionally narrow the public async surface and expose broader lifecycle/state helpers only through `to_async_logger()`.
4. Prefer the method helpers such as `state()`, `has_failed()`, `last_error()`, `is_running()`, and `shutdown()` instead of reading these public fields conceptually as if they were a stable manual mutation surface.
+18 -6
View File
@@ -2,8 +2,8 @@
name: async-logger-wait-idle
group: api
category: async
update-time: 20260512
description: Wait until the async logger backlog drains to zero or a worker failure interrupts normal progress.
update-time: 20260707
description: Wait until the async logger backlog drains to zero or a worker failure interrupts normal progress, using the repo's direct async call style.
key-word:
- async
- logger
@@ -34,8 +34,13 @@ pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit {}
Detailed rules explaining key parameters and behaviors
- The helper keeps yielding while `pending_count() > 0`.
- If `has_failed()` becomes `true`, waiting stops early instead of looping forever.
- If `has_failed()` becomes `true`, waiting stops early instead of continuing to spin.
- This API does not close the logger or stop the worker.
- A return from `wait_idle()` therefore means either backlog reached `0` or failure interrupted normal drain progress.
- In the stronger lifecycle model, the common post-failure interpretation is `phase=failed` together with `backlog_retained=true` while `pending_count() > 0` remains visible.
- `wait_idle()` does not clear retained failure state by itself, so if pending records remain after a worker failure, later `wait_idle()` calls also short-circuit until another path changes that state.
- A later `run()` can make `wait_idle()` meaningful again for the retained backlog, but only after that new worker invocation has actually started and reset the stale failure flag.
- If no worker is draining the queue and no failure flag is raised, `wait_idle()` can wait indefinitely.
- It is narrower than `shutdown()` and is useful when the logger should continue to be used later.
### How to Use
@@ -46,7 +51,7 @@ Here are some specific examples provided.
When code should wait for queued work to flush before continuing:
```moonbit
await logger.wait_idle()
logger.wait_idle()
```
In this example, the caller waits for backlog drain but leaves the logger usable afterward.
@@ -55,7 +60,7 @@ In this example, the caller waits for backlog drain but leaves the logger usable
When a test wants to ensure earlier async logs were processed:
```moonbit
await logger.wait_idle()
logger.wait_idle()
println("phase complete")
```
@@ -66,10 +71,17 @@ In this example, the wait acts as a barrier between test phases.
e.g.:
- If the worker has failed, `wait_idle()` stops waiting even if pending records remain.
- If the worker was never started, pending records may not drain and callers should not expect idle progress automatically.
- If the worker was never started, or if nothing is making pending records decrease, `wait_idle()` can block indefinitely.
- If callers need backlog cleanup after a failure-short-circuit, they still need a later `close(clear=true)` or `shutdown(...)` path.
- In the current direct coverage, `wait_idle()` can return with `pending_count() > 0` after a worker failure, and a later `shutdown(...)` call will convert that retained backlog into dropped records before returning.
- If callers retry `wait_idle()` immediately after such a failure without restarting the worker or forcing cleanup first, the call can return again with the same retained pending backlog.
### Notes
1. Use this helper when you want a drain barrier without closing the logger.
2. Prefer `shutdown()` when lifecycle completion matters more than continued reuse.
3. Pair it with `pending_count()` or `state()` when you need to distinguish true idleness from an early stop caused by worker failure.
+11 -6
View File
@@ -2,8 +2,8 @@
name: async-logger-warn
group: api
category: async
update-time: 20260512
description: Enqueue a warning-level record through the async logger using the built-in severity shortcut.
update-time: 20260614
description: Enqueue a warning-level record through the async logger using the built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
@@ -39,8 +39,9 @@ pub async fn[S] AsyncLogger::warn(
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Warn, ...)`.
- This helper delegates to `log(Level::Warn, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Warning records are useful for degraded but non-fatal runtime conditions.
- Use this helper when a named warning call is clearer than a raw `log(...)` call.
@@ -52,7 +53,7 @@ Here are some specific examples provided.
When the system should report a non-fatal problem:
```moonbit
await logger.warn("retry budget running low")
logger.warn("retry budget running low")
```
In this example, the event is surfaced at warning severity without using the generic `log(...)` form.
@@ -61,7 +62,7 @@ In this example, the event is surfaced at warning severity without using the gen
When a warning event should include context:
```moonbit
await logger.warn(
logger.warn(
"queue near capacity",
fields=[@bitlogger.field("pending", logger.pending_count().to_string())],
)
@@ -69,6 +70,10 @@ await logger.warn(
In this example, the warning carries structured operational detail.
And any shared context already carried by the logger still participates ahead of these per-call fields when the record is built.
The write still uses the logger's stored target because this shortcut does not take a one-off `target=` override.
### Error Case
e.g.:
@@ -80,4 +85,4 @@ e.g.:
1. Use this helper for notable but non-fatal async runtime conditions.
2. Pair warnings with structured fields when operators need quick context.
2. Use `log(...)` instead when one warning call must override the target without deriving a new logger value.
+14 -1
View File
@@ -31,7 +31,7 @@ pub fn[S] AsyncLogger::with_context_fields(
#### output
- `AsyncLogger[S]` - A new async logger carrying the shared field set.
- `AsyncLogger[S]` - A new async logger value carrying the shared field set.
### Explanation
@@ -40,7 +40,10 @@ 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.
- The provided `fields` array replaces the previously stored shared field set on the returned async logger; it does not append onto whatever `context_fields` the source logger already had.
- Unlike synchronous `Logger::with_context_fields(...)`, this async variant stores fields directly on `AsyncLogger` instead of changing the visible sink type.
- Only the stored `context_fields` value changes. Target, minimum level, timestamp flag, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- In the current direct async coverage, the original logger keeps its previous `context_fields`, while the derived logger prepends the stored shared fields ahead of per-call fields exactly once when records are built.
### How to Use
@@ -59,6 +62,8 @@ let logger = async_logger(console_sink(), target="billing")
In this example, both fields are attached before each record enters the queue.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Build Child Async Loggers For Subsystems
When a subsystem has both a target and fixed fields:
@@ -75,6 +80,8 @@ In this example, target composition and field binding stay separate but work tog
e.g.:
- If `fields` is empty, the logger remains valid and just adds no extra metadata.
- If a derived async logger already had shared context fields, calling `with_context_fields(...)` again replaces that stored shared field set on the new derived logger rather than stacking both sets together.
- If duplicate field keys are provided, all fields are still emitted; conflict handling is left to the consumer side.
### Notes
@@ -82,3 +89,9 @@ e.g.:
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.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a fresh derived logger when one code path needs shared metadata and another should stay unchanged.
5. If you need to combine two shared field sets, combine them in the `fields` argument yourself instead of expecting repeated `with_context_fields(...)` calls to accumulate them.
+10 -2
View File
@@ -31,7 +31,7 @@ pub fn[S] AsyncLogger::with_filter(
#### output
- `AsyncLogger[S]` - A new async logger that only enqueues matching records.
- `AsyncLogger[S]` - A new async logger value that only enqueues matching records.
### Explanation
@@ -39,8 +39,10 @@ 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.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- Only the stored filter pipeline changes. Target, minimum level, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- Filtering avoids unnecessary queue pressure for records that should never be delivered.
- In the current direct async coverage, derived filters can compose target, level, and message predicates together while the original logger still accepts writes according to its previous filter state.
### How to Use
@@ -56,6 +58,8 @@ let logger = async_logger(console_sink(), target="service")
In this example, non-matching records are dropped before they reach the async queue.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Combine Several Async Filter Rules
When filtering depends on multiple conditions:
@@ -80,3 +84,7 @@ e.g.:
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.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a derived logger value when one branch should enforce extra filter rules and the base async logger should stay unchanged.
+10 -2
View File
@@ -39,8 +39,10 @@ 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.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- This API replaces the stored threshold and does not alter queue configuration.
- Only `min_level` changes. Sink, target, timestamp flag, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- In the current direct async coverage, the derived logger reports the new threshold through `is_enabled(...)`, while the original logger keeps its previous minimum level and still accepts records that remain enabled there.
### How to Use
@@ -56,6 +58,8 @@ let logger = async_logger(console_sink())
In this example, lower-severity records are skipped before queue pressure increases.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Derive A More Verbose Async Branch
When one branch of code should keep a different threshold:
@@ -78,3 +82,7 @@ e.g.:
1. This API reduces async queue pressure by dropping disabled levels before enqueue.
2. Use it before adding more complex async filtering rules.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a derived logger value when one branch should tighten the threshold and the base async logger should keep its broader level gate.
+10 -2
View File
@@ -31,7 +31,7 @@ pub fn[S] AsyncLogger::with_patch(
#### output
- `AsyncLogger[S]` - A new async logger that rewrites each record before filtering and queue insertion.
- `AsyncLogger[S]` - A new async logger value that rewrites each record before filtering and queue insertion.
### Explanation
@@ -39,8 +39,10 @@ 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.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- Only the stored patch pipeline changes. Target, minimum level, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- Patching can normalize, redact, or enrich records before they consume queue capacity.
- In the current direct async coverage, patched target/message/field changes are visible to later filter logic, and the original async logger still emits unpatched records when used separately.
### How to Use
@@ -58,6 +60,8 @@ let logger = async_logger(console_sink())
In this example, the added fields are part of the record before filtering and queue insertion.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Need Redaction Before Queueing
When sensitive fields should be removed early:
@@ -80,3 +84,7 @@ e.g.:
1. Use patches for transformation, not filtering decisions.
2. Redaction before enqueue helps keep sensitive data out of the queued pipeline.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Derive a patched logger when one path needs rewritten records and another should keep the original async record shape.
+3
View File
@@ -38,6 +38,7 @@ Detailed rules explaining key parameters and behaviors
- 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.
- In the current direct async coverage, derived loggers keep existing flags such as `timestamp`, while the original logger still retains its previous target.
### How to Use
@@ -76,3 +77,5 @@ e.g.:
1. Use this API for replacement, not parent-child target composition.
2. It is useful when several subsystems should share one async queue policy.
3. Use it when you want a derived logger value; the original async logger keeps its earlier default target.
+9 -1
View File
@@ -37,7 +37,9 @@ 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.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- Only the stored `timestamp` flag changes. Target, minimum level, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- In the current direct async coverage, a derived timestamp-enabled logger records non-zero timestamps while the original logger continues emitting `0UL` timestamps when it was left disabled.
### How to Use
@@ -53,6 +55,8 @@ let logger = async_logger(console_sink())
In this example, each record captures its timestamp before entering the async queue.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Need Deterministic Async Records
When timestamps should be disabled for tests or reduced output:
@@ -75,3 +79,7 @@ e.g.:
1. This API controls record creation before enqueue, not formatter display policy.
2. It is useful for tests, deterministic snapshots, and production timing.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a derived logger value when only one branch should capture timestamps and the base logger should remain deterministic.
+51 -4
View File
@@ -2,8 +2,8 @@
name: async-logger
group: api
category: async
update-time: 20260512
description: Create an async logger with bounded queueing, overflow policy, lifecycle helpers, and background run control.
update-time: 20260707
description: Create an async logger with bounded queueing, overflow policy, lifecycle helpers, background run control, and a raising flush callback.
key-word:
- async
- logger
@@ -23,7 +23,7 @@ pub fn[S] async_logger(
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
min_level~ : @bitlogger.Level = @bitlogger.Level::Info,
target~ : String = "",
flush~ : (S) -> Int = fn(_) { 0 },
flush~ : (S) -> Unit raise = fn(_) { () },
) -> AsyncLogger[S] {}
```
@@ -33,7 +33,7 @@ pub fn[S] async_logger(
- `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` - Flush callback used by batch/shutdown flush policies.
- `flush : (S) -> Unit raise` - Flush callback used by batch/shutdown flush policies and allowed to raise if sink flushing fails.
#### output
@@ -44,8 +44,25 @@ pub fn[S] async_logger(
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 in lifecycle phase `ready`, with `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.
- The callback expresses attempted flush timing and failure, not a returned progress count contract.
- 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.
- `state()` exposes the stronger lifecycle conclusion through `phase`, plus derived reads such as `backlog_retained`, `can_rerun`, and `terminal` so callers do not have to infer these states from raw flag combinations.
- Late log attempts after closure are rejected in the shared async log path before patch/filter work runs, so post-close side effects no longer vary by backend.
- `run()` now enforces a single-worker contract and raises `AsyncLoggerAlreadyRunning` when a second worker startup is attempted for the same live logger.
- `run()` also rejects already-closed loggers with `AsyncLoggerClosed`, which keeps rerun semantics aligned with the stronger lifecycle model: rerun is for retained `failed` backlog, not for terminal closed phases.
- `shutdown()` now always waits for any active worker to finish and converts retained post-failure backlog into dropped records before returning.
- Queue overflow behavior depends on `AsyncOverflowPolicy`.
### How to Use
@@ -68,6 +85,28 @@ In this example, the worker drains queued records in the background and `shutdow
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:
@@ -91,6 +130,7 @@ 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()`.
- If a second worker startup is attempted while `is_running()` is already true, `run()` raises `AsyncLoggerAlreadyRunning`.
### Notes
@@ -98,3 +138,10 @@ e.g.:
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.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-overflow-policy
group: api
category: async
update-time: 20260614
description: Public overflow policy alias used by AsyncLoggerConfig, async parser labels, and runtime queue behavior.
key-word:
- async
- overflow
- alias
- public
---
## Async-overflow-policy
`AsyncOverflowPolicy` is the public enum that controls what an `AsyncLogger` should do when its queue cannot accept a record immediately. It is a direct alias to the async model enum used by `AsyncLoggerConfig` and the runtime queue selection logic.
### Interface
```moonbit
pub type AsyncOverflowPolicy = @utils.AsyncOverflowPolicy
```
#### output
- `AsyncOverflowPolicy` - Public async queue overflow enum with the variants `Blocking`, `DropOldest`, and `DropNewest`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a separate runtime adapter.
- `AsyncOverflowPolicy::Blocking` waits for queue space when a non-blocking enqueue does not succeed.
- `AsyncOverflowPolicy::DropOldest` lets the underlying async queue discard older pending records when capacity is limited.
- `AsyncOverflowPolicy::DropNewest` discards the incoming record instead of blocking.
- The same enum is used by `AsyncLoggerConfig::new(...)`, async config parsing, and the queue-kind mapping inside `AsyncLogger`.
- The canonical labels `Blocking`, `DropOldest`, and `DropNewest` are also the labels emitted again by async config serializers, so export and parse stay aligned around one public vocabulary.
- Async config parsing accepts the canonical label `DropNewest` and also the compatibility alias `DropLatest`, both mapping to the same public enum variant.
- When `try_put(...)` reports that a record was not accepted under a drop policy, `dropped_count()` increases for that rejected enqueue.
### How to Use
Here are some specific examples provided.
#### When Need Backpressure Instead Of Silent Dropping
When producers should wait for queue space:
```moonbit
let config = AsyncLoggerConfig::new(max_pending=64, overflow=AsyncOverflowPolicy::Blocking)
```
In this example, logging pressure can slow producers instead of dropping data immediately.
#### When Need Bounded Async Logging With Drop Behavior
When async logging should keep moving under load without blocking callers:
```moonbit
let config = AsyncLoggerConfig::new(max_pending=64, overflow=AsyncOverflowPolicy::DropNewest)
```
In this example, the incoming record is dropped if the queue cannot accept it.
### Error Case
e.g.:
- If async config text uses unsupported overflow text, async config parsing raises a failure.
- The parser error path for unsupported overflow text is the same `Failure` surface used by the async config utilities.
- Under drop policies, sustained overload increases `dropped_count()` instead of guaranteeing delivery.
- The precise record discarded by the underlying queue behavior depends on the selected policy and queue implementation, so callers should not assume `dropped_count()` alone identifies which message was lost.
### Notes
1. This policy applies to `bitlogger_async`, not synchronous `QueuedSink`.
2. Choose `Blocking` only when producer-side waiting is acceptable for the caller.
3. Serialized config uses the canonical `DropNewest` label even though the parser also accepts `DropLatest`.
+13 -2
View File
@@ -2,8 +2,8 @@
name: async-runtime-mode-label
group: api
category: async
update-time: 20260512
description: Convert AsyncRuntimeMode into a stable string label for logs, JSON, and diagnostics.
update-time: 20260614
description: Convert AsyncRuntimeMode into its canonical stable string label for logs, JSON, and diagnostics.
key-word:
- async
- runtime
@@ -36,7 +36,10 @@ Detailed rules explaining key parameters and behaviors
- The returned value is intended for diagnostics and stable output, not just debugging prints.
- It keeps mode serialization logic in one place.
- This helper is used by async runtime JSON helpers.
- `async_runtime_state_to_json(...)` serializes the `mode` field through this helper, so runtime snapshots and direct label rendering share the same canonical text.
- Labels are more stable for telemetry and docs than ad hoc manual matching at call sites.
- The current canonical labels are exactly `native_worker` for `NativeWorker` and `compatibility` for `Compatibility`.
- This helper is only a pure enum-to-string mapping. It does not inspect the active backend or verify that the supplied enum still matches the current runtime environment.
### How to Use
@@ -65,5 +68,13 @@ In this example, code gets a stable string without duplicating enum matching log
e.g.:
- This API assumes a valid `AsyncRuntimeMode` input and does not expose a normal runtime error path.
- If callers need the current backend-derived mode rather than a previously stored enum value, they must call `async_runtime_mode()` first and then label that result.
- If callers need the whole runtime object rather than a string label, use `async_runtime_state()`.
### Notes
1. Use this helper when mode values should be rendered as stable text instead of manual enum matching.
2. `async_runtime_state_to_json(...)` uses these exact labels when serializing runtime snapshots.
+15 -2
View File
@@ -2,8 +2,8 @@
name: async-runtime-mode
group: api
category: async
update-time: 20260512
description: Read the current async runtime mode and distinguish native worker behavior from compatibility behavior.
update-time: 20260614
description: Read the current async runtime mode and distinguish native-worker behavior from compatibility behavior using the same mode contract exposed through async runtime snapshots.
key-word:
- async
- runtime
@@ -34,9 +34,15 @@ pub fn async_runtime_mode() -> AsyncRuntimeMode {}
Detailed rules explaining key parameters and behaviors
- The return value is determined by the active backend implementation.
- `NativeWorker` is the expected mode on native-style backends, while `Compatibility` is the expected mode on targets without native background-worker semantics.
- `async_runtime_mode_label(...)` converts the enum into a stable string value.
- `async_runtime_supports_background_worker()` is a narrower boolean probe built on the same idea.
- `async_runtime_state()` packages this mode together with the current background-worker capability into one `AsyncRuntimeState` snapshot.
- In the current backend implementations, `NativeWorker` pairs with `background_worker=true` and `Compatibility` pairs with `background_worker=false`.
- Concretely, the native runtime entrypoint returns `native_worker_async_runtime_mode()`, while the compatibility stub returns `compatibility_async_runtime_mode()`.
- The enum is therefore selected directly by the active backend helper on each call rather than read back out of a cached runtime snapshot object.
- This API is intentionally small and useful for lightweight branching.
- The mode result describes runtime behavior only; it should not be read as proof that every backend has been equally re-verified in the current release cycle.
### How to Use
@@ -68,6 +74,8 @@ In this example, the output becomes a stable string instead of an enum pattern-m
e.g.:
- This API does not have a normal runtime failure mode; it reflects the compiled backend behavior.
- If callers need the current mode paired with the matching worker-support flag, prefer a fresh `async_runtime_state()` call instead of combining `async_runtime_mode()` with an older saved runtime snapshot.
- If you need worker support as a direct boolean, use `async_runtime_supports_background_worker()` instead.
### Notes
@@ -76,3 +84,8 @@ e.g.:
2. Use `async_runtime_state()` when you also want worker support packaged into one object.
3. Use `async_runtime_mode_label(...)` when the result should leave enum space and become stable text such as `native_worker` or `compatibility`.
4. This mode distinction is about runtime behavior, not whether `src-async` itself is expected to compile for the target.
5. See [target-verification.md](./target-verification.md) for the current verification status of individual targets.
+89
View File
@@ -0,0 +1,89 @@
---
name: async-runtime-state-new
group: api
category: async
update-time: 20260614
description: Construct an AsyncRuntimeState snapshot from explicit runtime mode and worker-support values without probing or validating the live backend.
key-word:
- async
- runtime
- state
- public
---
## Async-runtime-state-new
Construct an `AsyncRuntimeState` snapshot from explicit runtime mode and worker-support values. This is the low-level constructor behind the public async runtime state shape used in diagnostics.
### Interface
```moonbit
pub fn AsyncRuntimeState::new(
mode : AsyncRuntimeMode,
background_worker : Bool,
) -> AsyncRuntimeState {
```
#### input
- `mode : AsyncRuntimeMode` - Backend-specific async runtime mode such as `NativeWorker` or `Compatibility`.
- `background_worker : Bool` - Whether native-style background-worker support is available.
#### output
- `AsyncRuntimeState` - Runtime state snapshot containing the supplied mode and worker-support flag.
### Explanation
Detailed rules explaining key parameters and behaviors
- This constructor simply packages `mode` and `background_worker` into one public snapshot value.
- It does not query the current backend automatically.
- `async_runtime_state()` is the higher-level API that reads these values from the live runtime environment.
- It also does not validate whether the supplied pair matches the current backend contract.
- The supplied `mode` and `background_worker` values are stored exactly as provided; this constructor does not recompute, normalize, or cross-check either field.
- The constructed value matches the same public shape used by async runtime serializers.
- Because `AsyncRuntimeState` is only a data snapshot type, this constructor is mainly useful for tests, adapters, and synthetic diagnostics rather than ordinary runtime probing.
### How to Use
Here are some specific examples provided.
#### When Need A Hand-built Runtime Snapshot
When tests or adapters should construct a runtime state explicitly:
```moonbit
let runtime = AsyncRuntimeState::new(
AsyncRuntimeMode::Compatibility,
false,
)
```
In this example, the runtime snapshot is built directly without probing the active backend.
#### When Need Structured Runtime Diagnostics Input
When code should prepare a runtime state value before serialization:
```moonbit
let runtime = AsyncRuntimeState::new(async_runtime_mode(), async_runtime_supports_background_worker())
```
In this example, callers still use the direct constructor while keeping the data source explicit.
### Error Case
e.g.:
- This constructor itself does not have a normal failure mode; it only packages the provided values.
- If callers want the current backend snapshot directly, `async_runtime_state()` is the simpler API.
- If callers manually pair `NativeWorker` with `false` or `Compatibility` with `true`, the constructor still accepts that snapshot because it does not enforce backend consistency.
- If callers want the currently probed runtime pair instead of a synthetic one, they must pass `async_runtime_mode()` plus `async_runtime_supports_background_worker()` explicitly or use `async_runtime_state()`.
### Notes
1. Use this helper when code should construct an `AsyncRuntimeState` value explicitly.
2. Pair it with `AsyncLoggerState::new(...)` when assembling a full async logger snapshot by hand.
3. Prefer `async_runtime_state()` when the goal is to report the actual current backend pair rather than an arbitrary constructed snapshot.
+13 -2
View File
@@ -2,8 +2,8 @@
name: async-runtime-state-to-json
group: api
category: async
update-time: 20260512
description: Convert AsyncRuntimeState into a JSON value for runtime capability and mode diagnostics.
update-time: 20260614
description: Convert AsyncRuntimeState into a JSON value for runtime capability and mode diagnostics using the canonical mode labels and snapshot field names.
key-word:
- async
- state
@@ -35,8 +35,11 @@ Detailed rules explaining key parameters and behaviors
- The output includes `mode` and `background_worker`.
- `mode` is serialized through `async_runtime_mode_label(...)`.
- The compact serialized shape is `{"mode":"native_worker|compatibility","background_worker":true|false}`.
- This helper focuses on runtime capabilities rather than queue counters or logger lifecycle flags.
- The exported JSON is suitable for diagnostics endpoints and startup environment checks.
- This helper serializes the provided `AsyncRuntimeState` exactly as given; it does not call `async_runtime_state()` or recheck backend capability by itself.
- That means manually constructed runtime snapshots are exported unchanged, with `mode` relabeled through `async_runtime_mode_label(...)` and `background_worker` kept exactly as supplied.
### How to Use
@@ -67,3 +70,11 @@ e.g.:
- If the runtime is in compatibility mode, the helper still serializes normally using the matching mode label.
- If callers need the current backend-derived runtime rather than an older or synthetic snapshot, they must capture a fresh `async_runtime_state()` first.
### Notes
1. The output field names are fixed as `mode` and `background_worker`.
2. The `mode` field always uses the canonical labels from `async_runtime_mode_label(...)`, not enum names like `NativeWorker` or `Compatibility`.
+82
View File
@@ -0,0 +1,82 @@
---
name: async-runtime-state-type
group: api
category: async
update-time: 20260614
description: Public async runtime state alias used for backend capability snapshots and diagnostics, pairing runtime mode with background-worker support.
key-word:
- async
- runtime
- state
- public
---
## Async-runtime-state-type
`AsyncRuntimeState` is the public snapshot type used to describe backend-level async runtime capability. It is a direct alias to the async runtime state model returned by `async_runtime_state()` and used by async diagnostics serializers.
### Interface
```moonbit
pub type AsyncRuntimeState = @utils.AsyncRuntimeState
```
#### output
- `AsyncRuntimeState` - Public runtime snapshot containing `mode` and `background_worker`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a live runtime controller.
- The current fields are `mode : AsyncRuntimeMode` and `background_worker : Bool`.
- `async_runtime_state()` returns this type directly as an environment-level snapshot.
- `async_runtime_state()` currently builds that snapshot from `async_runtime_mode()` and `async_runtime_supports_background_worker()`.
- `async_runtime_state_to_json(...)` and `stringify_async_runtime_state(...)` serialize the same snapshot shape for diagnostics.
- `AsyncRuntimeState::new(...)` can also construct this type manually, but manual construction is synthetic data and does not probe the current backend by itself.
- The type itself does not distinguish live backend snapshots from hand-built ones; callers must track whether a given `AsyncRuntimeState` value came from `async_runtime_state()` or from manual construction.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Backend Capability Snapshot
When startup or diagnostics code should keep the runtime state as structured data:
```moonbit
let runtime : AsyncRuntimeState = async_runtime_state()
```
In this example, the backend capability snapshot stays available as a typed value instead of only as printed text.
#### When Need To Read Runtime Mode And Worker Support Together
When code should inspect both async mode and worker availability from one object:
```moonbit
let runtime = async_runtime_state()
if runtime.background_worker {
println(async_runtime_mode_label(runtime.mode))
}
```
In this example, both fields are consumed from the same public snapshot type.
### Error Case
e.g.:
- `AsyncRuntimeState` itself does not have a runtime failure mode.
- The snapshot is point-in-time diagnostic data; it should not be treated as proof that every target was re-verified in the current release cycle.
- Because this is just a data shape, manual construction can represent combinations that do not come from the current backend probe.
- Receiving an `AsyncRuntimeState` value alone does not prove it came from the current backend rather than from a synthetic constructor path.
### Notes
1. Use `async_runtime_state()` when you need a value of this type from the current backend.
2. Use `AsyncLoggerState` when logger-instance queue and lifecycle information is also required.
3. Use `async_runtime_mode_label(...)` when the `mode` field should be rendered as stable text such as `native_worker` or `compatibility`.
+10 -2
View File
@@ -2,8 +2,8 @@
name: async-runtime-state
group: api
category: async
update-time: 20260512
description: Read the current backend-specific async runtime mode and worker capability.
update-time: 20260614
description: Read the current backend-specific async runtime snapshot as the paired result of runtime mode and background-worker capability.
key-word:
- async
- runtime
@@ -35,8 +35,12 @@ Detailed rules explaining key parameters and behaviors
- `mode` is derived from the active backend implementation.
- `background_worker` tells callers whether native worker semantics are available.
- This helper is equivalent to `AsyncRuntimeState::new(async_runtime_mode(), async_runtime_supports_background_worker())`.
- The returned pair is rebuilt from those two lower-level helpers on each call rather than read from a cached runtime object.
- In the current backend implementations, the resulting pair is `NativeWorker + true` or `Compatibility + false`.
- `async_runtime_state_to_json(...)` and `stringify_async_runtime_state(...)` serialize this state.
- This API is environment-scoped and does not depend on a particular `AsyncLogger` instance.
- The returned value is a snapshot data object, not a live runtime handle, so later backend checks require calling `async_runtime_state()` again rather than reusing an older value as if it refreshed itself.
### How to Use
@@ -68,6 +72,8 @@ In this example, branch decisions are based on actual runtime capability instead
e.g.:
- This API does not normally expose a dynamic error path; it reports the currently compiled backend behavior.
- The returned `AsyncRuntimeState` value is not cached onto the helper. If callers need a newer backend read, they must call `async_runtime_state()` again instead of expecting an older value to refresh itself.
- If callers need richer runtime state, they should use `AsyncLogger::state()` on a logger instance instead.
### Notes
@@ -75,3 +81,5 @@ e.g.:
1. Use this API for environment-level diagnostics.
2. Use `AsyncLogger::state()` for logger-instance diagnostics.
3. Use `AsyncRuntimeState::new(...)` only when code or tests need to construct a manual snapshot instead of probing the current backend.
@@ -36,6 +36,9 @@ Detailed rules explaining key parameters and behaviors
- `true` indicates native worker capability.
- `false` indicates compatibility-mode behavior.
- This helper is derived from backend-specific async runtime implementation choice.
- The boolean is read from the active backend helper on each call rather than from a cached runtime snapshot object.
- In the current backend split, the native implementation returns `true` while the compatibility stub returns `false`, matching the same mode pair exposed through `async_runtime_mode()` and `async_runtime_state()`.
- The async library still targets multiple backends even when this helper returns `false`.
- Use it when an enum branch is unnecessary and a boolean capability check is enough.
### How to Use
@@ -67,6 +70,8 @@ In this example, a simple boolean can drive compact status output.
e.g.:
- This API does not normally fail at runtime; it reflects compiled backend behavior.
- If callers need the current paired mode and worker flag together, prefer a fresh `async_runtime_state()` call instead of mixing this boolean with an older saved mode value.
- If you need the exact mode name rather than a boolean, use `async_runtime_mode()` or `async_runtime_state()`.
### Notes
@@ -75,3 +80,6 @@ e.g.:
2. Prefer `async_runtime_state()` when you want the same information in a richer object.
3. A `false` result should be read as "compatibility-mode runtime behavior" rather than "async library unsupported on this target".
4. This helper does not by itself imply that every non-worker backend has been equally re-verified for the current release; see [target-verification.md](./target-verification.md).
+71
View File
@@ -0,0 +1,71 @@
---
name: buffered-sink-flush
group: api
category: sink
update-time: 20260613
description: Flush buffered records from a BufferedSink into its wrapped sink.
key-word:
- sink
- buffer
- flush
- public
---
## Buffered-sink-flush
Flush buffered records from a `BufferedSink[S]` into its wrapped sink. This is the direct sink-level batching control API for simple synchronous buffering.
### Interface
```moonbit
pub fn[S : Sink] BufferedSink::flush(self : BufferedSink[S]) -> Unit {
```
#### input
- `self : BufferedSink[S]` - Buffered sink whose pending records should be forwarded to the wrapped sink.
### Explanation
Detailed rules explaining key parameters and behaviors
- If the buffer is empty, the method does nothing.
- When buffered records exist, they are copied into a temporary `pending` array, the live buffer is cleared, and each record is then written to the wrapped sink in order.
- This helper drains only the local buffer; any later behavior still depends on the wrapped sink `S`.
- The method does not return a count or success flag.
### How to Use
Here are some specific examples provided.
#### When Need Explicit Batch Delivery
When a buffered sink should forward pending records before the threshold is reached:
```moonbit
sink.flush()
```
In this example, callers force buffered records to be written to the wrapped sink immediately.
#### When Need A Manual Flush Barrier
When tests or synchronous code want buffering but still need a deliberate release point:
```moonbit
let sink = buffered_sink(console_sink(), flush_limit=4)
sink.flush()
```
In this example, the buffered sink exposes a direct batching barrier without using queue semantics.
### Error Case
e.g.:
- If the wrapped sink's write behavior fails or has side effects, this helper does not add an extra reporting layer on top of `S`.
- If callers need a count-returning drain API with drop tracking, `QueuedSink::flush()` or `QueuedSink::drain()` may fit better.
### Notes
1. Use this helper when code owns a `BufferedSink` directly and wants explicit control over when buffered records are forwarded.
2. Automatic flush still happens when buffered record count reaches `flush_limit` during writes.
+75
View File
@@ -0,0 +1,75 @@
---
name: buffered-sink-pending-count
group: api
category: sink
update-time: 20260613
description: Read the current buffered-record count from a BufferedSink.
key-word:
- sink
- buffer
- queue
- public
---
## Buffered-sink-pending-count
Read the current buffered-record count from a `BufferedSink[S]`. This is the direct sink-level metric for how many records are still waiting in the in-memory buffer.
### Interface
```moonbit
pub fn[S] BufferedSink::pending_count(self : BufferedSink[S]) -> Int {
```
#### input
- `self : BufferedSink[S]` - Buffered sink whose current in-memory backlog should be inspected.
#### output
- `Int` - Current number of buffered records.
### Explanation
Detailed rules explaining key parameters and behaviors
- The return value is `self.buffer.val.length()` at the time of the call.
- This is a point-in-time metric and may change immediately after it is read.
- It reflects buffered records that have not yet been flushed to the wrapped sink.
- This helper does not mutate the sink.
### How to Use
Here are some specific examples provided.
#### When Need Direct Buffer Backlog Visibility
When code is working with a `BufferedSink` value directly and wants to observe pending buffered records:
```moonbit
let pending = sink.pending_count()
```
In this example, callers can inspect the current in-memory backlog without touching the wrapped sink.
#### When Verify Manual Flush Progress
When explicit flush steps should be checked operationally:
```moonbit
ignore(sink.flush())
ignore(sink.pending_count())
```
In this example, the metric helps verify whether buffered records were forwarded out of the local buffer.
### Error Case
e.g.:
- This helper does not have a normal failure mode; it only reads current buffer length.
- If callers need overflow-aware backlog semantics instead of simple buffering, `QueuedSink::pending_count()` is the better API.
### Notes
1. Use this helper for direct visibility into simple synchronous buffering.
2. Pair it with `flush()` when inspecting whether batched writes have been forwarded.
+77
View File
@@ -0,0 +1,77 @@
---
name: buffered-sink-type
group: api
category: sink
update-time: 20260613
description: Public buffered sink type used for threshold-based synchronous batching over another sink.
key-word:
- sink
- buffer
- type
- public
---
## Buffered-sink-type
`BufferedSink[S]` is the public buffering sink type used for threshold-based synchronous batching over another sink. It is the concrete sink type returned by `buffered_sink(...)` and preserves the wrapped sink type in its type parameter.
### Interface
```moonbit
pub struct BufferedSink[S] {
sink : S
buffer : Ref[Array[Record]]
flush_limit : Int
}
```
#### output
- `BufferedSink[S]` - Public synchronous sink type that buffers records before forwarding them to the wrapped sink.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a public root struct, not a type alias.
- The current fields are `sink : S`, `buffer : Ref[Array[Record]]`, and `flush_limit : Int`.
- `buffered_sink(...)` constructs this type directly from a wrapped sink and flush threshold.
- The sink preserves the wrapped sink type `S`, which is useful when typed composition still matters after buffering is introduced.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Buffered Sink Value
When code should keep the concrete buffering sink type visible:
```moonbit
let sink : BufferedSink[ConsoleSink] = buffered_sink(console_sink(), flush_limit=2)
```
In this example, the sink value stays explicit and preserves the wrapped console sink type.
#### When Need A Typed Logger With Synchronous Batching
When logging should preserve the buffering sink type in the logger:
```moonbit
let logger : Logger[BufferedSink[ConsoleSink]] = Logger::new(
buffered_sink(console_sink(), flush_limit=2),
target="buffered",
)
```
In this example, the concrete buffered sink remains part of the logger type.
### Error Case
e.g.:
- `BufferedSink[S]` itself does not have a runtime failure mode.
- Runtime behavior still depends on the wrapped sink `S`, and buffered records can remain pending if flushing never happens.
### Notes
1. Use `buffered_sink(...)` when you need a value of this type.
2. Use `QueuedSink[S]` when overflow-aware queueing behavior is needed instead of simple buffering.
+41 -6
View File
@@ -2,8 +2,8 @@
name: build-application-async-logger
group: api
category: facade
update-time: 20260520
description: Build the application-facing async logger facade from an AsyncLoggerBuildConfig.
update-time: 20260614
description: Build the application-facing runtime-sink async logger alias from an AsyncLoggerBuildConfig through the sync-first async builder path.
key-word:
- application
- async
@@ -13,7 +13,7 @@ key-word:
## Build-application-async-logger
Build an `ApplicationAsyncLogger` from `AsyncLoggerBuildConfig`. This is the application-facing async facade over `build_async_logger(...)`.
Build an `ApplicationAsyncLogger` from `AsyncLoggerBuildConfig`. This is the application-facing runtime-sink async alias returned through `build_async_logger(...)`.
### Interface
@@ -35,9 +35,19 @@ pub fn build_application_async_logger(
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_async_logger(...)`.
- The returned logger keeps the standard async lifecycle and state helper surface.
- Use this facade when application code wants a dedicated async app-level entry point.
- This API delegates to `build_async_logger(...)` directly.
- That means the embedded `LoggerConfig` is built first through the normal synchronous config path before the outer async layer is applied.
- Any optional synchronous queue layer and runtime-sink controls chosen by `build_logger(config.logger)` remain active under the returned logger.
- In particular, a sync queue configured on `LoggerConfig.queue` is preserved inside the wrapped `RuntimeSink` variant instead of being stripped away by the application alias.
- Because the result is only the `ApplicationAsyncLogger` alias over `AsyncLogger[@bitlogger.RuntimeSink]`, this builder does not hide any async helpers or introduce a wrapper layer.
- The broader async helper surface is therefore preserved and directly exposed on the returned alias rather than being rebuilt or hidden behind an unwrap step.
- The returned alias also keeps inherited async logger target behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- The returned logger keeps the full async lifecycle and state helper surface directly, including helpers such as `run()`, `shutdown()`, `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, `has_failed()`, and `last_error()`.
- It also keeps the same queue counters, failure state, sink shape, and runtime-dependent post-close behavior as the underlying runtime-sink async logger.
- Because this facade delegates to `build_async_logger(...)`, `Batch` and `Shutdown` also keep the underlying runtime-sink flush wiring: the returned alias uses the built sink's real `flush()` path instead of the default no-op callback used by the text-specific builder line.
- In the current direct builder coverage, this alias matches `build_async_logger(config)` all the way through serialized state snapshots, runtime-sink variant choice, queue counters, lifecycle flags, and later failure fields after `run()` or `shutdown()`.
- File-backed runtime helpers on the returned `RuntimeSink` also stay aligned with the direct builder result instead of being hidden behind a separate application-layer wrapper.
- Use this alias-oriented builder when application code wants the standard runtime-sink async shape without narrowing the public surface.
### How to Use
@@ -57,6 +67,23 @@ let logger = build_application_async_logger(
In this example, the app-facing async facade is built directly from typed config.
And any configured synchronous runtime sink controls remain available through the returned `RuntimeSink`-backed async logger.
And unlike `build_library_async_logger(...)`, no `to_async_logger()` unwrap is required to reach queue, lifecycle, or file-backed runtime helpers.
The returned value also keeps the ordinary async logger target semantics because the facade does not wrap or narrow the underlying `AsyncLogger[@bitlogger.RuntimeSink]`.
#### When Need Async State Helpers Immediately After App Construction
When application code should keep the ordinary async helper surface directly:
```moonbit
let logger = build_application_async_logger(config)
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, the application alias exposes async state helpers directly because no narrowing wrapper is added.
### Error Case
e.g.:
@@ -64,8 +91,16 @@ e.g.:
- If the logger is never `run()`, enqueue behavior and lifecycle state still follow the normal async logger rules.
- If callers rely on file-backed runtime helpers, they should treat this builder as the same runtime-sink result as `build_async_logger(config)`, not as a reduced alias with different helper behavior.
- If callers specifically want the text-console async path where `Batch` and `Shutdown` keep the default no-op flush callback, `build_application_text_async_logger(...)` is the different contract; this runtime-sink alias preserves the real sink `flush()` behavior from `build_async_logger(...)`.
### Notes
1. This is a facade over the existing async runtime logger builder.
2. Use `parse_and_build_application_async_logger(...)` when starting from JSON text.
3. Use `build_application_text_async_logger(...)` instead when callers should keep the concrete text-console sink type and the direct text-builder path.
4. Use `build_library_async_logger(...)` instead when a library boundary should narrow the exposed async surface.
+28 -4
View File
@@ -3,7 +3,7 @@ name: build-application-logger
group: api
category: facade
update-time: 20260520
description: Build the application-facing configured logger facade from a LoggerConfig.
description: Build the application-facing configured logger alias from a LoggerConfig by delegating directly to the normal runtime logger build path.
key-word:
- application
- facade
@@ -33,9 +33,13 @@ pub fn build_application_logger(config : LoggerConfig) -> ApplicationLogger {
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_logger(...)`.
- The returned value keeps the same public logging, queue, and file runtime helper surface as `ConfiguredLogger`.
- Use this facade when application boot code wants an app-specific entry name without exposing lower-level builder naming in its own code.
- This API delegates to `build_logger(...)` directly.
- The embedded config still goes through the normal runtime logger build path, including runtime sink selection, optional queue wrapping, and timestamp application.
- Because the result is only the `ApplicationLogger` alias over `ConfiguredLogger`, this builder does not hide any queue, drain, flush, or file runtime helper methods.
- The configured runtime helper surface is therefore preserved and directly exposed on the returned alias rather than being rebuilt or hidden behind an unwrap step.
- The returned alias also keeps inherited `Logger` behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- That means `log(..., target=...)` can override the target for one write, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- Use this alias-oriented entrypoint when application boot code wants an app-specific name without changing the underlying configured runtime logger surface.
### How to Use
@@ -52,6 +56,24 @@ let logger = build_application_logger(
In this example, the application facade builds the same configured runtime logger shape as `build_logger(...)`.
And any queue/file/runtime helpers selected by the config remain directly available on the returned alias value.
And unlike `build_library_logger(...)`, no `to_logger()` unwrap is required to reach that helper surface.
The returned value also keeps the ordinary logger target semantics because the facade does not wrap or narrow the underlying `ConfiguredLogger`.
#### When Need A Per-call Target Override After App-oriented Build
When typed app config should still build a logger that supports a one-write target override:
```moonbit
let logger = build_application_logger(config)
logger.log(Level::Error, "boom", target="app.audit")
```
In this example, the emitted record uses `app.audit` for that write.
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(...)`.
### Error Case
e.g.:
@@ -64,3 +86,5 @@ e.g.:
1. This is a facade API, not a separate runtime implementation.
2. Use `parse_and_build_application_logger(...)` when starting from JSON text.
3. Use `build_library_logger(...)` instead when the public surface should intentionally hide configured-runtime helper methods.
@@ -2,8 +2,8 @@
name: build-application-text-async-logger
group: api
category: facade
update-time: 20260520
description: Build the application-facing text-console async logger facade from an AsyncLoggerBuildConfig.
update-time: 20260614
description: Build the application-facing text-console async logger alias from an AsyncLoggerBuildConfig using the direct concrete text-sink builder path.
key-word:
- application
- async
@@ -13,7 +13,7 @@ key-word:
## Build-application-text-async-logger
Build an `ApplicationTextAsyncLogger` from `AsyncLoggerBuildConfig`. This facade is the application-oriented async builder for the text-console runtime sink shape returned by `build_async_text_logger(...)`.
Build an `ApplicationTextAsyncLogger` from `AsyncLoggerBuildConfig`. This is the application-oriented async alias builder for the concrete text-console sink shape returned by `build_async_text_logger(...)`.
### Interface
@@ -35,9 +35,22 @@ pub fn build_application_text_async_logger(
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_async_text_logger(...)`.
- This API delegates to `build_async_text_logger(...)` directly.
- It is intended for config-driven async text console output where callers want the concrete text sink shape rather than the broader runtime sink enum wrapper.
- The returned logger keeps the usual async lifecycle helpers.
- The builder always creates a `FormattedConsoleSink` from `config.logger.sink.text_formatter` instead of selecting among sink kinds.
- That means even if `config.logger.sink.kind` says `Console`, `JsonConsole`, or `File`, this facade still follows the text-console path and uses only the configured `text_formatter` details.
- Unlike `build_application_async_logger(...)`, this alias-oriented builder does not go through the full synchronous configured-logger build path first.
- It uses the selected text-oriented `LoggerConfig` fields directly and therefore does not apply `LoggerConfig.queue` or preserve sync runtime sink controls.
- A sync queue configured on `LoggerConfig.queue` is therefore ignored by this builder instead of being preserved under the returned async logger.
- Because the result is only the `ApplicationTextAsyncLogger` alias over `AsyncLogger[@bitlogger.FormattedConsoleSink]`, this builder returns the same underlying async logger value as `build_async_text_logger(...)` and does not hide any async helpers or introduce a wrapper layer.
- The returned alias also keeps inherited async logger target behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- In particular, `log(..., target=...)` can override the target for one call, while severity helpers such as `debug(...)`, `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- The returned logger keeps the full async lifecycle and state helper surface directly, including helpers such as `run()`, `shutdown()`, `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, `has_failed()`, and `last_error()`.
- It also keeps the same close, queue, and failure-state semantics as the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`.
- In the current direct text-builder coverage, this alias matches `build_async_text_logger(config)` through serialized state snapshots, formatter behavior, queue counters, lifecycle flags, and later failure fields after worker execution.
- Its configured `flush_policy` is still visible on the returned alias, but this text-specific build path does not wire the explicit sink flush callback that `build_application_async_logger(...)` inherits through `build_async_logger(...)`.
- That means `Batch` and `Shutdown` only drive the default no-op async flush callback here; they do not add an extra explicit sink flush step beyond ordinary `FormattedConsoleSink` writes.
- Use `build_library_async_text_logger(...)` instead when the next boundary should keep the same concrete text sink type but intentionally narrow the directly exposed async helper surface.
### How to Use
@@ -57,15 +70,50 @@ let logger = build_application_text_async_logger(
In this example, the async logger is built for text-console output specifically.
And the chosen builder path matters more than `config.logger.sink.kind`: this facade still uses the text formatter directly because it always builds a `FormattedConsoleSink`.
And the returned value keeps the ordinary async logger target semantics because this facade does not wrap or narrow the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`.
#### When Need A Per-call Target Override After App Text Construction
When typed app async text config should still build a logger that supports a one-call target override:
```moonbit
let logger = build_application_text_async_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="app.text.audit")
```
In this example, the emitted record uses `app.text.audit` for that call.
And later `debug(...)`, `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 Async State Helpers Immediately After Text App Construction
When application code should keep the ordinary async helper surface directly on the text-console variant:
```moonbit
let logger = build_application_text_async_logger(config)
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, the application text alias exposes async state helpers directly because no narrowing wrapper is added.
### Error Case
e.g.:
- If the embedded logger config selects a non-text sink shape, the caller should use the general async builder facade instead.
- If callers need sink-kind-driven branching such as JSON console or file-backed async output, they should use `build_application_async_logger(...)` instead.
- If the config carries file-oriented fields such as `path` only because `sink.kind` was set to `File`, this facade still ignores that sink-kind choice and does not create a file-backed async logger.
- If runtime draining is never started, records still follow the normal async queue lifecycle rules.
- If callers rely on the formatter or state shape after text-builder construction, they should treat this as the same direct `build_async_text_logger(config)` result rather than a reduced alias with different helper behavior.
### Notes
1. This is a narrower text-console async facade than `build_application_async_logger(...)`.
2. It is most useful when callers want the `FormattedConsoleSink`-backed async type explicitly.
3. Use `build_application_async_logger(...)` instead when callers need the broader runtime-sink build path, including sync queue application through `build_logger(config.logger)`.
4. Use `build_library_async_text_logger(...)` instead when the public boundary should narrow the exposed async surface while preserving the same concrete text sink type.
+43 -3
View File
@@ -2,8 +2,8 @@
name: build-async-logger
group: api
category: async
update-time: 20260512
description: Build an async logger from combined logger and async config without manually wiring the runtime sink.
update-time: 20260614
description: Build an async logger from combined logger and async config by first building the sync runtime logger and then wrapping its sink in the async layer.
key-word:
- async
- config
@@ -34,9 +34,22 @@ pub fn build_async_logger(config : AsyncLoggerBuildConfig) -> AsyncLogger[@bitlo
Detailed rules explaining key parameters and behaviors
- The `logger` section is built through the same config machinery used by synchronous configured loggers.
- That means `LoggerConfig.sink` and the optional synchronous `LoggerConfig.queue` are applied before the async layer is added.
- The resulting async logger inherits `min_level`, `target`, and timestamp behavior from the built synchronous logger.
- File, queue, and formatter choices all come from config rather than direct code-side sink wiring.
- File, formatter, and any configured synchronous queue choices all come from config rather than direct code-side sink wiring.
- The returned sink type is `RuntimeSink`, which keeps configured control helpers available where relevant.
- This builder returns the underlying `AsyncLogger[@bitlogger.RuntimeSink]` value directly. `build_application_async_logger(...)` only re-exports the same result under the `ApplicationAsyncLogger` alias, while `build_library_async_logger(...)` wraps the same result in `LibraryAsyncLogger[@bitlogger.RuntimeSink]`.
- The returned async logger also keeps ordinary target rules unchanged: `log(..., target=...)` can override the target for one call, while severity helpers such as `debug(...)`, `info(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- Because this path starts from `build_logger(config.logger)`, it preserves the broader runtime-sink build path, including sync-side queue decoration when `LoggerConfig.queue` is present.
- Because this path starts from `build_logger(config.logger)`, `config.logger.sink.kind` has already selected the concrete `RuntimeSink` variant before async wrapping, and optional sync queue decoration can further turn that built sink into queued runtime variants such as `QueuedConsole` or `QueuedFile`.
- This runtime-sink path also wires the explicit async flush callback as `flush=fn(sink) { sink.flush() }`, so `Batch` and `Shutdown` policies invoke the built runtime sink's real flush behavior instead of the default no-op callback.
- In the current direct builder coverage, the returned logger exposes the expected serialized async state snapshot, runtime-sink variant choice, queue counters, lifecycle flags, and later failure fields after `run()` or `shutdown()`.
- File-backed runtime helpers also stay directly available on the returned `RuntimeSink` with the same behavior later observed through the application and library facade equivalence tests.
- Use `build_async_text_logger(...)` instead when you want the direct text-console async builder path with `FormattedConsoleSink` and without the sync runtime-sink construction layer.
- The `src-async` library is designed to compile on `native / llvm / js / wasm / wasm-gc`, but runtime mode differs by backend.
- Current local release-facing verification is explicit for `native / js / wasm / wasm-gc`.
- `llvm` remains experimental and did not complete local verification in this environment.
- On non-native targets, the async library still compiles and exposes the same public surface through compatibility-mode behavior rather than native background-worker semantics.
### How to Use
@@ -56,6 +69,18 @@ In this example, parsing and async runtime wiring are separated cleanly.
And the returned logger can immediately be started with `run()`.
#### When Need A Per-call Target Override After Typed Async Build
When typed async config should still build a logger that supports a one-call target override:
```moonbit
let logger = build_async_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="svc.audit")
```
In this example, the emitted record uses `svc.audit` for that call.
And later `debug(...)`, `info(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Runtime Sink Features After Async Build
When the sink shape is configured but runtime features still matter:
@@ -66,6 +91,10 @@ println(stringify_async_logger_state(logger.state(), pretty=true))
In this example, the built async logger remains introspectable even though construction was config-driven.
And any configured synchronous runtime sink controls are preserved inside the returned `RuntimeSink`.
And if `AsyncFlushPolicy::Batch` or `Shutdown` is configured, this builder uses the runtime sink's real `flush()` path rather than the default no-op callback used by the text-specific builder line.
### Error Case
e.g.:
@@ -73,9 +102,20 @@ e.g.:
- If the configured sink shape is unsupported for a specific capability, the resulting runtime behavior follows the existing sink/runtime rules rather than inventing a separate builder-only failure model.
- If callers depend on queued runtime-sink helpers or file-backed runtime helpers, this builder is the direct API that preserves them rather than a reduced facade path.
- If callers depend on the exact runtime-sink variant, they should read this builder as preserving the sync-first sink selection result from `build_logger(config.logger)`, not as deferring sink-kind branching until after the async layer is added.
- If callers need the text-console path where `Batch` and `Shutdown` keep the default no-op flush callback, `build_async_text_logger(...)` is the different builder contract; this runtime-sink path intentionally wires the sink's real `flush()` behavior.
### Notes
1. Prefer this API when applications externalize both sync sink choice and async queue behavior.
2. Use `async_logger(...)` directly when you want explicit code-defined sink wiring.
3. Library portability is broader than example portability: a runnable `async fn main` example may still be target-limited even when the async library itself compiles for that backend.
4. See [target-verification.md](./target-verification.md) for the current verification boundary.
5. If the next boundary is library-facing rather than application-facing, build here and then narrow with `build_library_async_logger(...)` instead of documenting the broader runtime helper surface directly.
+35 -3
View File
@@ -2,8 +2,8 @@
name: build-async-text-logger
group: api
category: async
update-time: 20260520
description: Build an async logger with a concrete text-console sink from combined logger and async config.
update-time: 20260614
description: Build an async logger with a concrete text-console sink from combined logger and async config, using only the selected text-oriented LoggerConfig fields instead of the full sync build path.
key-word:
- async
- text
@@ -34,8 +34,18 @@ pub fn build_async_text_logger(config : AsyncLoggerBuildConfig) -> AsyncLogger[@
Detailed rules explaining key parameters and behaviors
- This builder converts `config.logger.sink.text_formatter` into a runtime `TextFormatter` and wires it into `text_console_sink(...)`.
- It always constructs a `FormattedConsoleSink` directly instead of branching on `config.logger.sink.kind`.
- That means even if `config.logger.sink.kind` says `Console`, `JsonConsole`, or `File`, this builder still follows the text-console path and uses only the configured `text_formatter` details.
- The returned logger inherits `min_level`, `target`, and timestamp behavior from `config.logger`.
- Unlike `build_async_logger(...)`, this helper does not run the full synchronous `build_logger(config.logger)` path first.
- That means it uses `config.logger.sink.text_formatter`, `min_level`, `target`, and `timestamp` directly, but it does not apply `LoggerConfig.queue` or preserve other sync runtime sink controls.
- This builder returns the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]` value directly. `build_application_text_async_logger(...)` only re-exports that same result under the `ApplicationTextAsyncLogger` alias, while `build_library_async_text_logger(...)` wraps the same result in `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]`.
- The returned async logger also keeps ordinary target rules unchanged: `log(..., target=...)` can override the target for one call, while severity helpers such as `debug(...)`, `info(...)`, `warn(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- In the current direct text-builder coverage, the returned logger exposes the expected serialized async state snapshot, formatter behavior, queue counters, lifecycle flags, and later failure fields after worker execution.
- The async `flush_policy` still comes from `config.async_config`, but this text-specific builder does not supply the explicit `flush=fn(sink) { sink.flush() }` callback used by `build_async_logger(...)`.
- In practice, `Batch` and `Shutdown` therefore only trigger the default no-op async flush callback on this path, while each record write still follows whatever immediate behavior `FormattedConsoleSink` already has on its own.
- This helper is best suited to text-console output paths where callers want the concrete formatted sink type instead of `RuntimeSink`.
- This async text path follows the same target story as the broader async library: `native / js / wasm / wasm-gc` have stronger local verification, while `llvm` remains experimental and locally unverified in this environment.
### How to Use
@@ -55,15 +65,37 @@ let logger = build_async_text_logger(
In this example, the async logger is built around a text console sink rather than the generic runtime sink enum.
And the chosen builder path matters more than `config.logger.sink.kind`: this helper still uses the text formatter directly because it always builds a `FormattedConsoleSink`.
#### When Need A Per-call Target Override After Built Async Text Construction
When typed async text config should still build a logger that supports a one-call target override:
```moonbit
let logger = build_async_text_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="async.text.audit")
```
In this example, the emitted record uses `async.text.audit` for that call.
And later `debug(...)`, `info(...)`, `warn(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If the logger config was not intended for text-console style output, the broader `build_async_logger(...)` path may be a better fit.
- If callers need sink-kind-driven branching across console, JSON, text, or file output, `build_async_logger(...)` is the better fit.
- If the config carries file-oriented fields such as `path` only because `sink.kind` was set to `File`, this builder still ignores that sink-kind choice and does not create a file-backed async logger.
- If the logger is never `run()`, pending records still follow the normal async queue lifecycle rules.
- If callers rely on the concrete formatter or post-run state shape, this builder is the direct API that preserves those text-console details rather than a reduced alias or wrapper.
### Notes
1. This API is narrower than `build_async_logger(...)` because it preserves a concrete text sink type.
2. It is the base builder used by the application and library async text facades.
3. See [target-verification.md](./target-verification.md) for the current local verification matrix.
4. Use this direct builder when callers should keep the full async helper surface on the concrete text sink type rather than a naming alias or a narrowed library wrapper.
+45 -4
View File
@@ -2,8 +2,8 @@
name: build-library-async-logger
group: api
category: facade
update-time: 20260520
description: Build the library-facing async logger facade from an AsyncLoggerBuildConfig.
update-time: 20260614
description: Build the library-facing async logger facade from an AsyncLoggerBuildConfig while intentionally hiding direct async state helpers.
key-word:
- library
- async
@@ -35,9 +35,19 @@ pub fn build_library_async_logger(
Detailed rules explaining key parameters and behaviors
- This API builds the general async runtime logger and then wraps it in the narrower `LibraryAsyncLogger` facade.
- This API builds the general async runtime logger and then wraps it in the narrower `LibraryAsyncLogger[@bitlogger.RuntimeSink]` facade.
- The embedded `LoggerConfig` still goes through the normal synchronous config path first, so sink shape and any optional synchronous queue layer are already applied before the outer async layer is wrapped and then narrowed.
- The returned facade wraps the same underlying `AsyncLogger[@bitlogger.RuntimeSink]` value that `build_async_logger(...)` would produce directly.
- The result keeps async lifecycle operations such as `run()` and `shutdown()` while narrowing the public shape.
- The broader async helper surface is preserved rather than rebuilt, but it is intentionally not directly exposed on the returned facade. Queue counters, lifecycle state, idle-wait helpers, and file-backed runtime helpers stay behind `to_async_logger()` instead of disappearing.
- The narrower facade does not change the underlying runtime-sink queue counters, failure state, sink shape, or runtime-dependent post-close behavior; it only hides the broader helper surface until `to_async_logger()` is used.
- Because this facade starts from `build_async_logger(...)`, the wrapped async logger also keeps the runtime-sink flush wiring: `Batch` and `Shutdown` use the built sink's real `flush()` path instead of the default no-op callback used by the text-specific builder line.
- The facade still preserves the underlying async target rules on its exposed write methods: `log(..., target=...)` can override the target for one call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless the facade first derived another logger with `with_target(...)` or `child(...)`.
- In the current direct builder coverage, unwrapping this facade produces the same async state snapshot, runtime-sink variant, queue counters, lifecycle flags, and failure fields as calling `build_async_logger(config)` directly.
- That same unwrap also preserves file-backed runtime helpers on `RuntimeSink` values rather than replacing them with facade-specific behavior.
- Async state helpers such as `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, and failure-status inspection remain on the underlying `AsyncLogger`, not on the returned facade itself.
- `to_async_logger()` can be used to recover the underlying full async logger.
- Use this builder when the boundary should preserve the runtime-sink async build path but still hide broader async inspection helpers from downstream callers.
### How to Use
@@ -57,15 +67,46 @@ let logger = build_library_async_logger(
In this example, async runtime construction is hidden behind the library facade.
#### When Need Async State Helpers After Library-oriented Construction
When config-built async state inspection is still needed internally:
```moonbit
let logger = build_library_async_logger(config)
let full = logger.to_async_logger()
ignore(full.pending_count())
```
In this example, the library async facade is unwrapped before using async state helpers.
And unlike `ApplicationAsyncLogger`, the narrower builder result does not expose those broader async helpers directly on the facade surface.
#### When Need A Per-call Target Override Through The Library Async Builder Facade
When typed library async config should still build a facade that allows a one-call target override without unwrapping first:
```moonbit
let logger = build_library_async_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="lib.audit")
```
In this example, the emitted record uses `lib.audit` for that call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the facade's stored target unless code derives another facade first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If backend-specific sink limitations exist, they still apply under the facade.
- If callers need methods outside the library facade, they must unwrap with `to_async_logger()`.
- If callers need async state, failure-status, or idle-wait helpers outside the library facade, they must unwrap with `to_async_logger()`.
- If callers need file-backed runtime helpers such as file-state or queued runtime inspection, they must unwrap first, but the helper behavior itself stays aligned with the direct `build_async_logger(config)` result.
- If callers instead want the text-console builder path where `Batch` and `Shutdown` keep the default no-op flush callback, they should use `build_library_async_text_logger(...)`; this runtime-sink facade preserves the real sink `flush()` behavior from `build_async_logger(...)`.
### Notes
1. Prefer this API when library boundaries should stay narrow.
2. Use `parse_and_build_library_async_logger(...)` when starting from JSON text.
3. Use `build_library_async_text_logger(...)` instead when the library-facing async type should preserve the narrower `FormattedConsoleSink` shape rather than `RuntimeSink`.
+50 -6
View File
@@ -2,8 +2,8 @@
name: build-library-async-text-logger
group: api
category: facade
update-time: 20260520
description: Build the library-facing text-console async logger facade from an AsyncLoggerBuildConfig.
update-time: 20260614
description: Build the library-facing text-console async logger facade from an AsyncLoggerBuildConfig using the concrete text-console builder path.
key-word:
- library
- async
@@ -13,7 +13,7 @@ key-word:
## Build-library-async-text-logger
Build a `LibraryAsyncLogger[FormattedConsoleSink]` from `AsyncLoggerBuildConfig`. This facade is the library-oriented async builder for text-console runtime output.
Build a `LibraryAsyncLogger[FormattedConsoleSink]` from `AsyncLoggerBuildConfig`. This facade is the library-oriented async builder for the concrete text-console sink shape returned by `build_async_text_logger(...)`.
### Interface
@@ -35,9 +35,20 @@ pub fn build_library_async_text_logger(
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_async_text_logger(...)` and then wraps the result as `LibraryAsyncLogger`.
- It is useful when library code wants a narrow async facade while preserving a concrete text-console sink type.
- This API delegates to `build_async_text_logger(...)` and then narrows the result to `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]`.
- It always produces a concrete `FormattedConsoleSink` from `config.logger.sink.text_formatter` instead of branching on sink kinds.
- That means even if `config.logger.sink.kind` says `Console`, `JsonConsole`, or `File`, this facade still follows the text-console path and uses only the configured `text_formatter` details.
- Unlike `build_library_async_logger(...)`, this facade does not go through the full synchronous configured-logger build path first.
- It uses the selected text-oriented `LoggerConfig` fields directly and therefore does not apply `LoggerConfig.queue` or preserve sync runtime sink controls.
- A sync queue configured on `LoggerConfig.queue` is therefore ignored by this builder instead of being preserved behind the wrapped text-console async logger.
- The returned facade wraps the same underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]` value that `build_async_text_logger(...)` would return directly, so `run()`, `shutdown()`, and queue or failure-state behavior are unchanged under the narrower public type.
- In the current direct text-builder coverage, unwrapping this facade yields the same async state snapshot, formatter behavior, queue counters, lifecycle flags, and later failure fields as calling `build_async_text_logger(config)` directly.
- The configured `flush_policy` is still carried by that underlying async logger, but this text-specific builder path does not provide the explicit sink flush callback used by `build_library_async_logger(...)` through `build_async_logger(...)`.
- As a result, `Batch` and `Shutdown` only invoke the default no-op async flush callback on this text-console path unless downstream code unwraps and adds different behavior elsewhere.
- The facade still preserves the underlying async target rules on its exposed write methods: `log(..., target=...)` can override the target for one call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless the facade first derived another logger with `with_target(...)` or `child(...)`.
- Async state helpers such as `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, and failure-status inspection remain on the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`, not on the returned facade itself.
- `to_async_logger()` can recover the underlying full async logger if needed.
- Use this builder when the boundary should preserve the concrete text-console sink type while still hiding broader async inspection and helper APIs from downstream callers.
### How to Use
@@ -57,10 +68,41 @@ let logger = build_library_async_text_logger(
In this example, the async text sink shape is preserved under the library facade.
And the chosen builder path matters more than `config.logger.sink.kind`: this facade still uses the text formatter directly because it always builds a `FormattedConsoleSink` before narrowing to `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]`.
#### When Need Async State Helpers After Library Text Construction
When library-facing text-console construction should still allow internal async inspection later:
```moonbit
let logger = build_library_async_text_logger(config)
let full = logger.to_async_logger()
ignore(full.pending_count())
```
In this example, the facade is unwrapped before using async state helpers.
#### When Need A Per-call Target Override Through The Library Async Text Builder Facade
When typed library async text config should still build a facade that allows a one-call target override without unwrapping first:
```moonbit
let logger = build_library_async_text_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="lib.text.audit")
```
In this example, the emitted record uses `lib.text.audit` for that call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the facade's stored target unless code derives another facade first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If the embedded logger config does not describe text-console output, the caller should use the broader async facade instead.
- If callers need sink-kind-driven branching such as JSON console or file-backed async output, they should use `build_library_async_logger(...)` instead.
- If the config carries file-oriented fields such as `path` only because `sink.kind` was set to `File`, this facade still ignores that sink-kind choice and does not create a file-backed async logger.
- If callers expect async state or idle-wait helpers directly on the returned facade, they must unwrap first with `to_async_logger()`.
- If callers need to inspect the actual formatter-backed async state after library-level `run()` or `shutdown()` calls, unwrapping exposes the same post-call counters and failure fields that the direct text builder would have produced.
- Normal async lifecycle expectations still apply if the logger is never run.
@@ -69,3 +111,5 @@ e.g.:
1. This is the library-side counterpart to `build_application_text_async_logger(...)`.
2. It is most useful when a concrete text-console async sink type matters to the caller boundary.
3. Use `build_library_async_logger(...)` instead when the library-facing async type should keep the broader `RuntimeSink` build path, including sync queue application through `build_logger(config.logger)`.
+39 -4
View File
@@ -2,8 +2,8 @@
name: build-library-logger
group: api
category: facade
update-time: 20260520
description: Build the library-facing sync logger facade from a LoggerConfig.
update-time: 20260613
description: Build the library-facing sync logger facade from a LoggerConfig by delegating to the configured runtime logger build path and then wrapping the result.
key-word:
- library
- facade
@@ -33,9 +33,15 @@ pub fn build_library_logger(config : LoggerConfig) -> LibraryLogger[RuntimeSink]
Detailed rules explaining key parameters and behaviors
- This API builds a configured runtime logger first and then wraps it as `LibraryLogger`.
- This API builds a configured runtime logger first and then wraps that same value as `LibraryLogger[RuntimeSink]`.
- The embedded config still goes through the normal runtime logger build path, including runtime sink selection, optional queue wrapping, and timestamp application.
- The returned facade wraps the same underlying `ConfiguredLogger` value that `build_logger(...)` would produce directly.
- The facade intentionally exposes a smaller logging surface than the full configured runtime logger.
- In particular, the configured runtime helper surface is preserved rather than rebuilt, but it is intentionally not directly exposed on the returned facade. Queue metrics, flush or drain helpers, and file controls stay behind `to_logger()` instead of disappearing.
- The facade still preserves the underlying logger target rules on its exposed write methods: `log(..., target=...)` can override the target for one write, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless the facade first derived another logger with `with_target(...)` or `child(...)`.
- Queue metrics, flush and drain helpers, and file runtime controls remain on the underlying `ConfiguredLogger`, not on the returned facade itself.
- Call `to_logger()` if a caller must recover the underlying full logger object.
- Use this builder when the boundary should preserve the configured runtime logger path but still hide broader runtime helper methods from downstream callers.
### How to Use
@@ -52,15 +58,44 @@ let logger = build_library_logger(
In this example, the logger is built from config and then narrowed to the library facade.
#### When Need Runtime Helpers After Library-oriented Construction
When config-built runtime queue or file controls are still needed internally:
```moonbit
let logger = build_library_logger(config)
let full = logger.to_logger()
ignore(full.sink.pending_count())
```
In this example, the library facade is unwrapped before using runtime-specific helpers through the preserved `RuntimeSink` value.
And the unwrapped value still carries the same `RuntimeSink` pipeline built from the original config.
And unlike `ApplicationLogger`, the narrower builder result does not expose those runtime helpers directly on the facade surface.
#### When Need A Per-call Target Override Through The Library Builder Facade
When typed library config should still build a facade that allows a one-write target override without unwrapping first:
```moonbit
let logger = build_library_logger(config)
logger.log(Level::Error, "boom", target="lib.audit")
```
In this example, the emitted record uses `lib.audit` for that write.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the facade's stored target unless code derives another facade first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If backend-specific sink limitations exist, they still apply after the facade is built.
- If code later needs methods outside the library facade, it must unwrap with `to_logger()`.
- If code later needs queue metrics, flush or drain helpers, or file runtime controls, it must unwrap with `to_logger()`.
### Notes
1. Prefer this facade when library APIs should not expose the full configured runtime logger type.
2. Use `parse_and_build_library_logger(...)` when starting from JSON text.
3. Use `to_logger()` when internal code later needs broader logger composition or direct access to the preserved `RuntimeSink` value without changing the public facade type.
+22 -2
View File
@@ -15,6 +15,8 @@ key-word:
Build a `ConfiguredLogger` from `LoggerConfig`. This is the main config-to-runtime bridge for synchronous logging and is the builder used before async wrapping in config-driven async flows.
At the public `src` layer, this API is the entry facade that consumes the shared config model owned by `src/config_model` and produces a `ConfiguredLogger` backed by `src/runtime.RuntimeSink`.
### Interface
```moonbit
@@ -33,9 +35,13 @@ pub fn build_logger(config : LoggerConfig) -> ConfiguredLogger {}
Detailed rules explaining key parameters and behaviors
- `build_logger(...)` constructs the runtime sink shape based on `SinkConfig` and optional queue wrapper.
- `build_logger(...)` first constructs a base `RuntimeSink` from `config.sink`, then applies `config.queue` when present, and finally builds `Logger::new(...)` with `config.min_level`, `config.target`, and `config.timestamp`.
- The returned file-control and file-diagnostics helpers are still facade methods over `RuntimeSink`, which in turn delegates file behavior to `src/file_runtime` and file model values to `src/file_model`.
- The returned logger still supports normal logging methods because `ConfiguredLogger` is `Logger[RuntimeSink]`.
- The returned logger also keeps inherited logger target behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- That means `log(..., target=...)` can override the target for one write, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- Queue metrics and file controls remain available through forwarding helpers on the configured logger.
- `build_application_logger(...)` only re-exports this same configured runtime logger result under the `ApplicationLogger` alias, while `build_library_logger(...)` wraps the same result in `LibraryLogger[RuntimeSink]`.
- This API is deterministic and data-driven, making it suitable for bootstrapping from parsed config.
### How to Use
@@ -57,7 +63,19 @@ let logger = build_logger(
In this example, no JSON parsing is required because config objects were built directly.
And the runtime logger is ready immediately.
And the runtime logger is ready immediately, with the same ordinary logger target semantics as any other `Logger` value.
#### When Need A Per-call Target Override After Typed Config Build
When typed config should still build a logger that supports a one-write target override:
```moonbit
let logger = build_logger(config)
logger.log(Level::Error, "boom", target="svc.audit")
```
In this example, the emitted record uses `svc.audit` for that write.
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 Config-built Queue Or File Runtime Helpers
@@ -83,3 +101,5 @@ e.g.:
2. Use `parse_and_build_logger(...)` when the starting point is raw JSON text.
3. Use the application or library facade builders only when the boundary name or exposed surface should differ; they do not change the underlying configured runtime logger pipeline built here.
+72
View File
@@ -0,0 +1,72 @@
---
name: callback-sink-type
group: api
category: sink
update-time: 20260613
description: Public callback sink type used for forwarding structured records to user code.
key-word:
- sink
- callback
- type
- public
---
## Callback-sink-type
`CallbackSink` is the public callback sink type used for forwarding structured `Record` values to user code. It is the concrete sink type returned by `callback_sink(...)` and is intended for tests, adapters, and custom integrations that want raw record access.
### Interface
```moonbit
pub struct CallbackSink {
callback : (Record) -> Unit
}
```
#### output
- `CallbackSink` - Public synchronous sink type that forwards full `Record` values to a stored callback.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a public root struct, not a type alias.
- The current field is `callback : (Record) -> Unit`.
- `callback_sink(...)` constructs this type directly from a record callback.
- Unlike `FormattedCallbackSink`, this sink preserves full structured record access instead of converting records into text first.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Structured Callback Sink Value
When code should keep the concrete record-callback sink type visible:
```moonbit
let sink : CallbackSink = callback_sink(fn(rec) { println(rec.message) })
```
In this example, the sink value stays explicit and preserves direct access to structured record data.
#### When Need A Typed Callback-backed Logger
When logging should preserve the raw-record callback sink type in the logger:
```moonbit
let logger : Logger[CallbackSink] = Logger::new(callback_sink(fn(rec) { println(rec.target) }))
```
In this example, the concrete callback sink remains part of the logger type.
### Error Case
e.g.:
- `CallbackSink` itself does not have a runtime failure mode.
- Behavior inside the stored callback is fully user-defined, so failures there are outside the sink type's own API contract.
### Notes
1. Use `callback_sink(...)` when you need a value of this type.
2. Use `FormattedCallbackSink` when the destination expects final rendered text rather than full `Record` values.
+71
View File
@@ -0,0 +1,71 @@
---
name: color-mode
group: api
category: formatter
update-time: 20260613
description: Public color-mode enum alias used by text formatters and formatter config.
key-word:
- color
- formatter
- alias
- public
---
## Color-mode
`ColorMode` is the public enum that controls when ANSI color rendering is enabled for text formatting. It is a direct alias to the formatter enum used by both `text_formatter(...)` and `TextFormatterConfig::new(...)`.
### Interface
```moonbit
pub type ColorMode = @utils.ColorMode
```
#### output
- `ColorMode` - Public formatter color-policy enum with the variants `Never`, `Auto`, and `Always`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a wrapper or a separate formatter mode.
- `ColorMode::Never` disables ANSI color output.
- `ColorMode::Always` always enables ANSI color rendering.
- `ColorMode::Auto` currently follows the built-in `NO_COLOR` environment check.
- The same enum is used by runtime formatters and serializable formatter config objects.
### How to Use
Here are some specific examples provided.
#### When Need Plain Text Without ANSI Codes
When logs should stay uncolored even if the terminal supports color:
```moonbit
let formatter = text_formatter(color_mode=ColorMode::Never)
```
In this example, rendered text keeps the formatter output readable without ANSI escape sequences.
#### When Need Forced Terminal Colors
When tests or demos should always emit colored output:
```moonbit
let formatter = text_formatter(color_mode=ColorMode::Always)
```
In this example, ANSI color rendering is enabled regardless of `NO_COLOR`.
### Error Case
e.g.:
- `ColorMode` itself does not have a runtime failure mode.
- `ColorMode::Auto` is policy-based, so the final visible result still depends on environment state such as `NO_COLOR`.
### Notes
1. Use `color_mode_label(...)` when you need a stable string form for diagnostics or tests.
2. This enum controls whether color is used, while `ColorSupport` controls how rich the color encoding can be.
+77
View File
@@ -0,0 +1,77 @@
---
name: color-support
group: api
category: formatter
update-time: 20260613
description: Public color-support enum alias used by text formatters and formatter config.
key-word:
- color
- formatter
- alias
- public
---
## Color-support
`ColorSupport` is the public enum that controls how much color precision a text formatter should use when ANSI color output is enabled. It is a direct alias to the formatter enum used by both runtime formatters and serializable formatter config.
### Interface
```moonbit
pub type ColorSupport = @utils.ColorSupport
```
#### output
- `ColorSupport` - Public formatter color-capability enum with the variants `Basic` and `TrueColor`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a wrapper or a separate rendering engine.
- `ColorSupport::Basic` prefers named/basic ANSI color codes.
- `ColorSupport::TrueColor` allows 24-bit color codes when the style input uses values such as hex colors.
- This setting only matters when color output is enabled through `ColorMode`.
- The same enum is used by `text_formatter(...)` and `TextFormatterConfig::new(...)`.
### How to Use
Here are some specific examples provided.
#### When Need Conservative Basic ANSI Colors
When output should stay within the smaller named ANSI color set:
```moonbit
let formatter = text_formatter(
color_mode=ColorMode::Always,
color_support=ColorSupport::Basic,
)
```
In this example, style rendering prefers the basic color vocabulary instead of 24-bit codes.
#### When Need Hex-style Richer Color Rendering
When custom style tags should preserve truecolor output where possible:
```moonbit
let formatter = text_formatter(
color_mode=ColorMode::Always,
color_support=ColorSupport::TrueColor,
)
```
In this example, formatter styles can emit richer ANSI color codes for hex-based styles.
### Error Case
e.g.:
- `ColorSupport` itself does not have a runtime failure mode.
- If `ColorMode::Never` disables ANSI color output, changing `ColorSupport` does not produce a visible effect.
### Notes
1. Use `color_support_label(...)` when you need a stable string form.
2. This enum controls color precision, not whether color rendering is enabled at all.
+78
View File
@@ -0,0 +1,78 @@
---
name: config-error
group: api
category: config
update-time: 20260707
description: Public config parsing error re-exported from config_model for synchronous config-loading helpers.
key-word:
- config
- error
- alias
- public
---
## Config-error
`ConfigError` is the public error type raised by synchronous config parsing helpers. On the root `src` facade, it is re-exported from `src/config_model`, which is the real owner of the structured config error definition.
### Interface
```moonbit
pub using @config_model { type ConfigError }
```
#### output
- `ConfigError` - Public config parsing error type with the case `InvalidConfig(String)`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This root surface is a re-export, not the concrete owner definition.
- The concrete error lives in `@config_model.ConfigError`, not in `@utils`.
- The current public error case is `ConfigError::InvalidConfig(message)`.
- The alias is used by parsing helpers such as `parse_logger_config_text(...)` and by lower-level config parsing routines beneath it.
- Error messages describe concrete schema problems such as invalid JSON, wrong value types, unsupported enum text, or missing required values for a chosen sink kind.
### How to Use
Here are some specific examples provided.
#### When Need To Catch Config Parse Failures
When raw config text should be validated before boot:
```moonbit
let config = parse_logger_config_text(raw) catch {
err if err is ConfigError => {
println(err.to_string())
return
}
}
```
In this example, the caller keeps config validation failures separate from normal runtime work.
#### When Need To Surface A Clear Parse Message
When tooling should report why config input was rejected:
```moonbit
ignore(parse_logger_config_text("{bad json}")) catch {
err => println(err.to_string())
}
```
In this example, the error carries a concrete message instead of failing silently.
### Error Case
e.g.:
- Invalid JSON input raises `ConfigError::InvalidConfig(...)`.
- Wrong field types, unsupported level text, unsupported sink kinds, or an empty `path` for a file sink also raise `ConfigError::InvalidConfig(...)`.
### Notes
1. This alias currently belongs to the synchronous config-loading path in `bitlogger`.
2. Async config parsers in `bitlogger_async` currently raise the generic failure surface used by that package instead of `ConfigError`.
+23 -11
View File
@@ -2,8 +2,8 @@
name: configured-logger-close
group: api
category: runtime
update-time: 20260512
description: Close the configured runtime logger sink and return whether the underlying sink reported a close action.
update-time: 20260707
description: Close the configured runtime logger sink and return whether the wrapped RuntimeSink close path succeeded.
key-word:
- logger
- runtime
@@ -13,7 +13,7 @@ key-word:
## Configured-logger-close
Close the sink behind a `ConfiguredLogger`. This helper is the config-driven runtime close surface for queue-backed or file-backed sinks.
Close the sink behind a `ConfiguredLogger`. This helper is the configured logger wrapper over `RuntimeSink::close(...)` for config-driven runtime teardown.
### Interface
@@ -27,16 +27,20 @@ pub fn ConfiguredLogger::close(self : ConfiguredLogger) -> Bool {}
#### output
- `Bool` - Whether the underlying runtime sink reported a successful close action.
- `Bool` - Whether the wrapped `RuntimeSink::close(...)` call reported a successful close path.
### Explanation
Detailed rules explaining key parameters and behaviors
- Queue-backed file sinks close the wrapped file sink.
- Plain file sinks forward directly to file close behavior.
- Console-style sinks usually report `true` because they do not have a meaningful close step.
- This helper delegates to `RuntimeSink::close(...)` through the configured logger wrapper.
- This helper delegates directly to `self.sink.close()`.
- Plain file sinks forward to `FileSink::close()` through `RuntimeSink`.
- Queue-backed file sinks now follow the same safe teardown path as `file_close()`: they first try to drain the queue, then flush the wrapped inner file sink, then close it.
- On queued file sinks, the returned `Bool` is `true` only when that whole teardown path succeeds without introducing new file failures.
- Plain console-style sinks return `true` as a no-op success because they do not expose a meaningful close step here.
- Queue-wrapped console-style sinks now use drain-first teardown before returning `true`.
- For `QueuedConsole`, `QueuedJsonConsole`, and `QueuedTextConsole`, success means the queued backlog was consumed to zero.
- For file-backed configured loggers, later `close()` calls return `false` after the wrapped file handle has already been cleared.
### How to Use
@@ -63,12 +67,20 @@ In this example, callers can branch on the reported close result.
### Error Case
e.g.:
- If the runtime sink has no real close action, the helper may still return `true` as a no-op success.
- If the configured runtime sink shape has no real close action, the helper may still return `true` as a no-op success.
- If callers need a file-specific close path with queue flush nuances, `file_close()` may be the better API.
- If the configured logger shares the same wrapped runtime sink state with another facade and one path already closed that file-backed sink, a later close attempt returns `false`.
- If callers need a file-specific close path with queue flush nuances, `file_close()` may still be the clearer API.
- On queued file sinks, `true` still does not mean the records are guaranteed to be durable beyond what the current backend can report; it means the queue-drain, file-flush, and file-close path completed without a newly observed file failure.
- On queued non-file sinks, `true` means the queued backlog was consumed before teardown completed.
### Notes
1. This is the generic configured runtime close helper.
2. Prefer file-specific helpers when the sink shape is known to be file-backed.
2. Generic `close()` is now safe for queue-backed runtime sinks and no longer skips queued records silently.
3. Converting the same configured logger through wrapper paths such as library projection still shares the wrapped runtime sink state, so close effects are visible across those facades.
@@ -0,0 +1,79 @@
---
name: configured-logger-drain-progress
group: api
category: runtime
update-time: 20260707
description: Return a structured generic drain progress snapshot for a ConfiguredLogger through RuntimeSink without collapsing queue and plain-file fallback semantics into one Int.
key-word:
- logger
- runtime
- drain
- public
---
## Configured-logger-drain-progress
Return a structured generic drain progress snapshot for a `ConfiguredLogger`. This is the recommended configured runtime wrapper over `RuntimeSink::drain_progress(...)` when code wants truthful generic progress instead of the compatibility `Int` returned by `drain()`.
### Interface
```moonbit
pub fn ConfiguredLogger::drain_progress(
self : ConfiguredLogger,
max_items~ : Int = -1,
) -> RuntimeSinkProgress {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose generic drain progress should be observed.
- `max_items : Int` - Optional upper bound on drained queued items. Negative values mean no explicit bound.
#### output
- `RuntimeSinkProgress` - Structured progress snapshot returned by the wrapped `RuntimeSink::drain_progress(...)` call.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates directly to `self.sink.drain_progress(max_items=max_items)`.
- Queue-wrapped sinks report drained queued items through `queue_advanced_count`.
- Plain file sinks report the generic fallback flush step through `file_flush_step_count`.
- Plain console-style sinks return a zeroed snapshot.
- This helper keeps configured-runtime drain behavior explicit without forcing callers to interpret one mixed `Int`.
### How to Use
Here are some specific examples provided.
#### When Need Recommended Bounded Generic Drain Progress On A Config-built Logger
When queued config-built output should be advanced in chunks with explicit semantics:
```moonbit
let progress = logger.drain_progress(max_items=16)
```
In this example, queue advancement and plain-file fallback stay separated.
#### When Need Full Manual Generic Drain Progress
When config-built support code should empty runtime queue work while keeping shape information:
```moonbit
let progress = logger.drain_progress()
```
In this example, the structured result still tells whether the logger was queue-backed or plain file-backed.
### Error Case
e.g.:
- If the configured runtime sink is not queue-backed, the snapshot may stay zeroed or use the plain-file fallback field.
- If callers need file-specific success semantics after queue delivery, `file_flush()` is the better API.
### Notes
1. Prefer `drain_progress()` over `drain()` in new configured-runtime code.
2. Use `pending_count()` together with this helper when remaining backlog should also be observed.
+23 -16
View File
@@ -2,8 +2,8 @@
name: configured-logger-drain
group: api
category: runtime
update-time: 20260512
description: Drain queued work from a configured runtime logger with optional item limits.
update-time: 20260707
description: Compatibility configured runtime drain helper that still returns one Int by collapsing generic queue progress and plain-file fallback steps.
key-word:
- logger
- runtime
@@ -13,7 +13,7 @@ key-word:
## Configured-logger-drain
Drain queued work from a `ConfiguredLogger`. This helper is useful when config-driven queue wrapping should be advanced in a controlled, bounded way.
Drain queued work from a `ConfiguredLogger` and return one compatibility count. This helper is still available for older code, but new configured-runtime code should prefer `drain_progress()` because it exposes queue advancement and plain-file fallback steps separately.
### Interface
@@ -28,16 +28,17 @@ pub fn ConfiguredLogger::drain(self : ConfiguredLogger, max_items~ : Int = -1) -
#### output
- `Int` - Number of drained items.
- `Int` - Compatibility count returned by the wrapped `RuntimeSink::drain(...)` call.
### Explanation
Detailed rules explaining key parameters and behaviors
- Queue-wrapped sinks may drain up to `max_items` records.
- For plain file sinks, the runtime falls back to file flush behavior instead of queue draining.
- For sinks without queue semantics, the result is typically `0`.
- This helper delegates to `RuntimeSink::drain(...)` through the configured logger wrapper.
- This helper delegates directly to `self.sink.drain(max_items=max_items)`.
- Under the hood, `RuntimeSink::drain()` now rebuilds one compatibility count from `drain_progress()`.
- Queue-wrapped sinks still report drained queued items through that compatibility count.
- Plain file sinks still report the generic fallback flush step as `1` or `0`.
- Plain console-style sinks still return `0`.
### How to Use
@@ -45,31 +46,37 @@ Here are some specific examples provided.
#### When Need Bounded Queue Progress
When queued output should be advanced in chunks:
When older code already expects one numeric drain count from a config-built logger:
```moonbit
let drained = logger.drain(max_items=16)
```
In this example, callers limit how much queued work is processed in one step.
In this example, callers still limit how much queued work is processed in one step while keeping the legacy return shape.
#### When Need Full Manual Drain
When the configured queue should be emptied explicitly:
When code should keep the older full-drain compatibility path:
```moonbit
ignore(logger.drain())
```
In this example, the configured runtime logger drains without imposing an item cap.
In this example, the configured runtime logger drains without changing the legacy return shape.
### Error Case
e.g.:
- If the runtime sink is not queue-backed, draining may return `0` or follow fallback flush behavior.
- If the configured runtime sink is not queue-backed, draining may return `0` or follow the plain-file flush fallback.
- If callers only need generic flush semantics, `flush()` may be the simpler API.
- If callers need truthful generic progress semantics, `drain_progress()` is the better API.
- If callers only need generic flush semantics, `flush_progress()` may be the simpler structured API.
- If callers need file-specific success semantics after draining a queued file logger, they should inspect failure counters or use `file_flush()` for the file-specific path.
### Notes
1. Prefer this helper when queue progress should be bounded or observable.
1. Treat this method as a compatibility helper for older numeric code.
2. Use `pending_count()` to inspect remaining backlog after the drain call.
2. Prefer `drain_progress()` in new configured-runtime code.
3. Use `pending_count()` to inspect remaining backlog after the drain call when the configured sink is queue-backed.
+9 -9
View File
@@ -2,8 +2,8 @@
name: configured-logger-dropped-count
group: api
category: runtime
update-time: 20260512
description: Read the cumulative dropped-record count from a configured runtime logger.
update-time: 20260613
description: Read the cumulative dropped-record count from a configured runtime logger through RuntimeSink.
key-word:
- logger
- runtime
@@ -13,7 +13,7 @@ key-word:
## Configured-logger-dropped-count
Read the cumulative dropped-record count from a `ConfiguredLogger`. This helper is useful when config-driven queue wrapping may discard records under pressure.
Read the cumulative dropped-record count from a `ConfiguredLogger`. This helper is the configured logger wrapper over `RuntimeSink::dropped_count(...)` when config-driven queue wrapping may discard records under pressure.
### Interface
@@ -27,16 +27,16 @@ pub fn ConfiguredLogger::dropped_count(self : ConfiguredLogger) -> Int {}
#### output
- `Int` - Number of dropped records reported by the runtime sink.
- `Int` - Number returned by the wrapped `RuntimeSink::dropped_count()` call.
### Explanation
Detailed rules explaining key parameters and behaviors
- Queue-backed sinks return their live dropped-count metric.
- Non-queued sinks report `0`.
- The counter is cumulative for the runtime sink lifetime.
- This helper delegates to `RuntimeSink::dropped_count(...)` through the configured logger wrapper.
- This helper delegates directly to `self.sink.dropped_count()`.
- Queue-backed runtime sink variants return their live dropped-count metric.
- Plain console and plain file runtime sink variants return `0` because they do not track queued record drops.
- The counter is cumulative for the lifetime of the concrete runtime sink value owned by the configured logger.
### How to Use
@@ -65,7 +65,7 @@ In this example, the helper exposes the metric needed to compare runtime queue t
### Error Case
e.g.:
- If the logger is not queue-backed, the method simply returns `0`.
- If the configured logger is not queue-backed, the method simply returns `0`.
- If callers need queue shape and file status together, `file_runtime_state()` may carry more useful context for file sinks.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_append_mode(self : ConfiguredLogger) -> Bool {}
Detailed rules explaining key parameters and behaviors
- File-backed sinks report their current append policy.
- Queued file sinks forward the policy from the wrapped file sink.
- File-backed sinks report their current append policy through the wrapped `RuntimeSink`.
- Queued file sinks forward the policy from the wrapped inner file sink.
- Non-file sinks return `false`.
- This helper reports runtime file policy, not whether a file is currently writable.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_auto_flush(self : ConfiguredLogger) -> Bool {}
Detailed rules explaining key parameters and behaviors
- File-backed sinks report their current auto-flush policy.
- Queued file sinks forward the policy from the wrapped file sink.
- File-backed sinks report their current auto-flush policy through the wrapped `RuntimeSink`.
- Queued file sinks forward the policy from the wrapped inner file sink.
- Non-file sinks return `false`.
- This helper exposes policy state only and does not force any flush action.
+9 -5
View File
@@ -2,7 +2,7 @@
name: configured-logger-file-available
group: api
category: runtime
update-time: 20260512
update-time: 20260707
description: Read whether the configured runtime logger currently has an available file sink behind its runtime sink shape.
key-word:
- logger
@@ -33,9 +33,11 @@ pub fn ConfiguredLogger::file_available(self : ConfiguredLogger) -> Bool {}
Detailed rules explaining key parameters and behaviors
- File-backed runtime sinks report actual file availability.
- File-backed runtime sinks report actual file availability through the wrapped `RuntimeSink`.
- Queued file sinks still expose the availability of their wrapped inner file sink.
- Non-file sinks report `false`.
- Queued file sinks still expose the availability of their wrapped file sink.
- This means `false` currently merges two different situations: “not file-backed” and “file-backed but currently unavailable”.
- Use `file_path_or_none()`, `file_policy_or_none()`, `file_default_policy_or_none()`, `file_state_or_none()`, or `file_runtime_state()` when callers need truthful file-semantics detection.
- This helper delegates to the runtime sink and does not mutate logger state.
### How to Use
@@ -67,10 +69,12 @@ In this example, callers can decide whether a recovery action is needed.
e.g.:
- If the configured sink is not file-backed, the method returns `false`.
- If callers need detailed failure counters rather than a simple availability flag, `file_state()` or `file_runtime_state()` is the better API.
- If callers need to distinguish “not file-backed” from “file-backed but unavailable”, use one of the truthful `*_or_none()` helpers or `file_runtime_state()`.
- If callers need detailed failure counters rather than a simple availability flag, `file_state_or_none()` or `file_runtime_state()` is the better API.
### Notes
1. Use this helper for lightweight file sink health checks.
2. Pair it with reopen and failure-counter APIs when diagnosing file sink problems.
2. Pair it with reopen and failure-counter APIs when diagnosing file sink problems, but prefer the truthful `*_or_none()` helpers when file semantics themselves must be checked.
@@ -2,8 +2,8 @@
name: configured-logger-file-clear-rotation
group: api
category: runtime
update-time: 20260512
description: Disable runtime rotation on the configured file-backed logger sink.
update-time: 20260707
description: Clear the rotation policy used by the configured runtime file sink.
key-word:
- logger
- runtime
@@ -13,7 +13,7 @@ key-word:
## Configured-logger-file-clear-rotation
Disable runtime rotation on a `ConfiguredLogger` file sink. This helper is the direct shortcut for clearing rotation policy.
Clear the rotation policy used by a `ConfiguredLogger` file sink.
### Interface
@@ -23,7 +23,7 @@ pub fn ConfiguredLogger::file_clear_rotation(self : ConfiguredLogger) -> Bool {}
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose rotation policy should be cleared.
- `self : ConfiguredLogger` - Config-driven runtime logger whose file rotation policy should be cleared.
#### output
@@ -33,42 +33,13 @@ pub fn ConfiguredLogger::file_clear_rotation(self : ConfiguredLogger) -> Bool {}
Detailed rules explaining key parameters and behaviors
- File-backed sinks clear their runtime rotation policy.
- Queued file sinks forward the update to the wrapped file sink.
- File-backed sinks clear their runtime rotation policy through the wrapped `RuntimeSink`.
- Queued file sinks clear the wrapped inner file sink rotation policy only when no queued records are pending.
- Non-file sinks return `false`.
- This helper is equivalent in intent to setting rotation to `None`, but is clearer at the call site.
### How to Use
Here are some specific examples provided.
#### When Need To Disable Rotation Explicitly
When runtime file rotation should be turned off:
```moonbit
ignore(logger.file_clear_rotation())
```
In this example, rotation policy is removed directly.
#### When Need A Clear Intent Shortcut
When code should make the disable-rotation intent obvious:
```moonbit
let ok = logger.file_clear_rotation()
```
In this example, the call site reads more clearly than a generic config setter.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `false`.
- If callers need to switch to another rotation config rather than disable rotation, `file_set_rotation(...)` is the better API.
- If a queued file sink still has pending records, the update is rejected and returns `false` so already queued records are not later written under a different rotation policy than the one they were queued under.
### Notes
1. Use this helper when the desired effect is simply “rotation off”.
1. Use this helper when runtime rotation policy should be removed without rebuilding the logger.
2. It is clearer than passing `None` through a broader setter when intent matters.
2. On queued file sinks, clear pending records first so policy mutation does not retroactively affect already queued writes.
+15 -7
View File
@@ -2,8 +2,8 @@
name: configured-logger-file-close
group: api
category: runtime
update-time: 20260512
description: Close the file sink behind a configured runtime logger when one is present.
update-time: 20260707
description: Close the file sink behind a configured runtime logger when one is present, with queued-file success tied to safe drain-flush-close behavior.
key-word:
- logger
- runtime
@@ -27,16 +27,18 @@ pub fn ConfiguredLogger::file_close(self : ConfiguredLogger) -> Bool {}
#### output
- `Bool` - Whether the underlying file close succeeded.
- `Bool` - Whether the file-specific close path succeeded.
### Explanation
Detailed rules explaining key parameters and behaviors
- Plain file sinks forward directly to file close behavior.
- Queued file sinks flush queue work before closing the wrapped file sink.
- Plain file sinks forward directly to file close behavior through the wrapped `RuntimeSink`.
- Queued file sinks first drain the queue, then flush the wrapped inner file sink, then close it.
- For queued file sinks, the returned `Bool` is `true` only when that whole path succeeds and no new write, flush, or rotation failures are observed during the drain-flush-close step.
- Non-file sinks return `false`.
- This helper is narrower than generic `close()` because it specifically targets file sink shutdown.
- After a file-backed configured logger has already cleared its file handle, later `file_close()` calls return `false`.
### How to Use
@@ -58,17 +60,23 @@ When application code wants the file close outcome directly:
let closed = logger.file_close()
```
In this example, the result describes file close behavior rather than generic sink close behavior.
In this example, the result describes the file-specific close path rather than generic sink close behavior.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `false`.
- If the file handle was already closed earlier through this logger or another facade sharing the same wrapped runtime sink state, the method returns `false`.
- If callers only need generic sink teardown, `close()` is the broader API.
- On queued file sinks, `true` still does not mean a stronger durability contract than the current backend can report; it means the safe drain-flush-close path completed without a newly observed file failure.
### Notes
1. Prefer this helper when file-backed runtime behavior matters specifically.
2. Queued file sinks may flush pending records before closing the file.
2. Generic `close()` now uses the same safe queued-file teardown path.
3. Library or application facades derived from the same configured runtime logger still observe the same underlying file-close state.
@@ -0,0 +1,82 @@
---
name: configured-logger-file-default-policy-or-none
group: api
category: runtime
update-time: 20260707
description: Read the default runtime file policy from a ConfiguredLogger only when it is actually file-backed.
key-word:
- logger
- runtime
- file
- truthful
---
## Configured-logger-file-default-policy-or-none
Read the default runtime file policy from a `ConfiguredLogger` only when it is actually file-backed.
The returned `FileSinkPolicy` value is owned by `src/file_model`; this configured-logger surface is a facade over `Logger[@runtime.RuntimeSink]` and avoids fabricating that model on non-file variants.
This is the truthful companion to `file_default_policy()`. Prefer it when default-policy inspection should not fabricate a fallback object on non-file sinks.
### Interface
```moonbit
pub fn ConfiguredLogger::file_default_policy_or_none(self : ConfiguredLogger) -> FileSinkPolicy? {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose default file policy should be inspected.
#### output
- `FileSinkPolicy?` - `Some(policy)` when the configured sink is file-backed, otherwise `None`.
### Explanation
Detailed rules explaining key parameters and behaviors
- File-backed sinks return `Some(default_policy)` through the wrapped `RuntimeSink`.
- Queued file sinks forward the wrapped inner file sink default policy as `Some(default_policy)`.
- The wrapped value is the shared `@file_model.FileSinkPolicy` model, not a configured-logger-owned concrete type.
- Non-file sinks return `None`.
- This helper is useful when callers need to compare runtime drift or restore defaults without fabricating file semantics.
### How to Use
Here are some specific examples provided.
#### When Need Truthful Baseline Policy Visibility
When tooling should inspect defaults only for real file-backed sinks:
```moonbit
let defaults = logger.file_default_policy_or_none()
```
In this example, `None` means the configured logger has no file default policy.
#### When Compare Current And Default Policy Truthfully
When diagnostics should avoid fallback policy objects:
```moonbit
match (logger.file_policy_or_none(), logger.file_default_policy_or_none()) {
(Some(current), Some(defaults)) => ignore((current, defaults))
_ => ()
}
```
In this example, comparisons happen only when real file semantics exist.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `None`.
- If callers only need a drift boolean, `file_policy_matches_default()` remains simpler.
### Notes
1. Prefer this helper over `file_default_policy()` for truthful runtime diagnostics.
2. `file_default_policy()` remains available as the compatibility fallback API.
@@ -2,8 +2,8 @@
name: configured-logger-file-default-policy
group: api
category: runtime
update-time: 20260512
description: Read the initial default file policy associated with a configured file-backed logger.
update-time: 20260707
description: Read the initial default file policy associated with a configured logger, with a compatibility fallback for non-file sinks.
key-word:
- logger
- runtime
@@ -15,6 +15,10 @@ key-word:
Read the initial default file policy associated with a `ConfiguredLogger`. This helper exposes the baseline file policy captured when the runtime sink was created.
The returned `FileSinkPolicy` value is owned by `src/file_model`; this configured-logger surface is a facade over `Logger[@runtime.RuntimeSink]` and delegates default-policy reads to the wrapped `RuntimeSink`.
`file_default_policy()` is the compatibility form. For truthful file-semantics detection, prefer `file_default_policy_or_none()`.
### Interface
```moonbit
@@ -33,9 +37,12 @@ pub fn ConfiguredLogger::file_default_policy(self : ConfiguredLogger) -> FileSin
Detailed rules explaining key parameters and behaviors
- File-backed sinks return the default policy captured at creation time.
- Queued file sinks forward the default policy from the wrapped file sink.
- Non-file sinks return a neutral fallback policy value.
- File-backed sinks return the default policy captured at creation time through the wrapped `RuntimeSink`.
- Queued file sinks forward the default policy from the wrapped inner file sink.
- The returned policy object itself is the shared `@file_model.FileSinkPolicy` model, not a configured-logger-owned concrete type.
- Non-file sinks return the same neutral fallback policy value produced by `RuntimeSink::file_default_policy()`.
- This fallback keeps older callers source-compatible, but it is not a real file default policy.
- New diagnostic or recovery code should prefer `file_default_policy_or_none()`.
- This helper is useful when callers need to compare runtime drift or restore defaults later.
### How to Use
@@ -65,10 +72,12 @@ In this example, callers can reason about “factory” file policy separately f
e.g.:
- If the configured sink is not file-backed, the return value is a neutral fallback policy.
- If callers need a truthful file-semantics check, use `file_default_policy_or_none()`.
- If callers only need to know whether runtime drift exists, `file_policy_matches_default()` is the simpler API.
### Notes
1. Use this helper when the original file policy matters operationally.
2. It complements `file_policy()` and `file_reset_policy()`.
2. It complements `file_policy()` and `file_reset_policy()`, while `file_default_policy_or_none()` is the truthful diagnostic form.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_flush_failures(self : ConfiguredLogger) -> Int {}
Detailed rules explaining key parameters and behaviors
- File-backed sinks report their recorded flush-failure count.
- Queued file sinks forward the metric from the wrapped file sink.
- File-backed sinks report their recorded flush-failure count through the wrapped `RuntimeSink`.
- Queued file sinks forward the metric from the wrapped inner file sink.
- Non-file sinks return `0`.
- The counter is cumulative until reset.
+16 -7
View File
@@ -2,8 +2,8 @@
name: configured-logger-file-flush
group: api
category: runtime
update-time: 20260512
description: Flush the file sink behind a configured runtime logger when one is present.
update-time: 20260707
description: Flush the file sink behind a configured runtime logger when one is present, with queued-file success tied to both queue drain and file flush outcome.
key-word:
- logger
- runtime
@@ -27,16 +27,19 @@ pub fn ConfiguredLogger::file_flush(self : ConfiguredLogger) -> Bool {}
#### output
- `Bool` - Whether the underlying file flush succeeded.
- `Bool` - Whether the file-specific flush path succeeded.
### Explanation
Detailed rules explaining key parameters and behaviors
- Plain file sinks forward directly to file flush behavior.
- Queued file sinks first flush queued records, then flush the wrapped file sink.
- Plain file sinks forward directly to file flush behavior through the wrapped `RuntimeSink`.
- Queued file sinks first drain queued records, then flush the wrapped inner file sink.
- For queued file sinks, the returned `Bool` is `true` only when all queued records were consumed, the final file flush succeeded, and no new write, flush, or rotation failures were observed during that flush path.
- For queued file sinks, this step may also be the point where queued records finally hit the inner file sink, so failure counters can change here even if earlier log calls only queued records.
- Non-file sinks return `false`.
- This helper is narrower than generic `flush()` because it targets file sink behavior specifically.
- After a file-backed configured logger has already cleared its file handle, later `file_flush()` calls return `false`.
### How to Use
@@ -58,17 +61,23 @@ When code should branch on the outcome of a file flush:
let ok = logger.file_flush()
```
In this example, callers can distinguish file flush success from a non-file sink shape.
In this example, callers can distinguish file-specific flush success from a non-file sink shape.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `false`.
- If the file handle was already closed earlier through this logger or another facade sharing the same wrapped runtime sink state, the method returns `false`.
- If callers want generic queue or sink advancement instead of file-specific behavior, `flush()` is the broader API.
- On queued file sinks, a `false` result may still mean queue consumption happened but file delivery observed a new failure.
### Notes
1. Prefer this helper when the configured sink is known to be file-backed.
2. Queued file sinks may perform both queue flush and file flush work here.
2. Queued file sinks may perform both queue drain and file flush work here, including surfacing delayed failures from previously queued records.
3. Library or application facades derived from the same configured runtime logger still observe the same underlying file-flush availability state.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_open_failures(self : ConfiguredLogger) -> Int {}
Detailed rules explaining key parameters and behaviors
- File-backed sinks report their recorded open-failure count.
- Queued file sinks forward the metric from the wrapped file sink.
- File-backed sinks report their recorded open-failure count through the wrapped `RuntimeSink`.
- Queued file sinks forward the metric from the wrapped inner file sink.
- Non-file sinks return `0`.
- The counter is cumulative until reset.
@@ -0,0 +1,79 @@
---
name: configured-logger-file-path-or-none
group: api
category: runtime
update-time: 20260707
description: Read the effective file path from a ConfiguredLogger only when it is actually file-backed.
key-word:
- logger
- runtime
- file
- truthful
---
## Configured-logger-file-path-or-none
Read the effective file path from a `ConfiguredLogger` only when it is actually file-backed.
This is the truthful companion to `file_path()`. Prefer it for diagnostics, branching, and recovery logic that must distinguish “not file-backed” from compatibility fallbacks.
### Interface
```moonbit
pub fn ConfiguredLogger::file_path_or_none(self : ConfiguredLogger) -> String? {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose file path should be inspected.
#### output
- `String?` - `Some(path)` when the configured sink is file-backed, otherwise `None`.
### Explanation
Detailed rules explaining key parameters and behaviors
- File-backed sinks return `Some(path)` through the wrapped `RuntimeSink`.
- Queued file sinks forward the wrapped inner file sink path as `Some(path)`.
- Non-file sinks return `None`.
- This helper is observation-only and does not mutate logger state.
### How to Use
Here are some specific examples provided.
#### When Need Truthful File-backed Detection
When code should branch only if file semantics really exist:
```moonbit
match logger.file_path_or_none() {
Some(path) => println(path)
None => ()
}
```
In this example, `None` means the configured logger is not file-backed.
#### When Need Path Diagnostics Without Fallback Values
When support output should avoid compatibility placeholders:
```moonbit
let maybe_path = logger.file_path_or_none()
```
In this example, callers can keep “no file semantics” distinct from a real path.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `None`.
- If callers need the broader file snapshot or queue context, prefer `file_state_or_none()` or `file_runtime_state()`.
### Notes
1. Prefer this helper over `file_path()` for truthful runtime diagnostics.
2. `file_path()` remains available as the compatibility fallback API.
+12 -6
View File
@@ -2,8 +2,8 @@
name: configured-logger-file-path
group: api
category: runtime
update-time: 20260512
description: Read the effective file path used by the configured runtime logger when it is file-backed.
update-time: 20260707
description: Read the effective file path used by the configured runtime logger, with a compatibility fallback for non-file sinks.
key-word:
- logger
- runtime
@@ -15,6 +15,8 @@ key-word:
Read the effective file path used by a `ConfiguredLogger`. This helper is useful for diagnostics, support output, and confirming which file-backed runtime sink is active.
`file_path()` is the compatibility form. For truthful file-semantics detection, prefer `file_path_or_none()`.
### Interface
```moonbit
@@ -33,9 +35,11 @@ pub fn ConfiguredLogger::file_path(self : ConfiguredLogger) -> String {}
Detailed rules explaining key parameters and behaviors
- File-backed sinks return their current file path.
- Queued file sinks forward the wrapped file sink path.
- File-backed sinks return their current file path through the wrapped `RuntimeSink`.
- Queued file sinks forward the wrapped inner file sink path.
- Non-file sinks return an empty string.
- This fallback keeps older callers source-compatible, but it does not truthfully express whether file semantics exist.
- New diagnostic code should prefer `file_path_or_none()`.
- This helper is observation-only and does not modify file state.
### How to Use
@@ -65,10 +69,12 @@ In this example, the path can be surfaced without reading broader runtime state.
e.g.:
- If the configured sink is not file-backed, the method returns an empty string.
- If callers need richer file status than just the path, `file_state()` or `file_runtime_state()` is the better API.
- If callers need a truthful file-semantics check, use `file_path_or_none()`.
- If callers need richer file status than just the path, `file_state_or_none()` or `file_runtime_state()` is the better API.
### Notes
1. Use this helper for direct runtime path visibility.
2. Empty string usually means the configured sink is not file-backed.
2. Empty string is a compatibility fallback; `file_path_or_none()` is the recommended truthful API.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_policy_matches_default(self : ConfiguredLogger) ->
Detailed rules explaining key parameters and behaviors
- File-backed sinks compare current runtime file policy against their stored defaults.
- Queued file sinks forward the comparison from the wrapped file sink.
- File-backed sinks compare current runtime file policy against their stored defaults through the wrapped `RuntimeSink`.
- Queued file sinks forward the comparison from the wrapped inner file sink.
- Non-file sinks return `false`.
- This helper is a compact drift signal when callers do not need to compare full policy objects directly.
@@ -0,0 +1,79 @@
---
name: configured-logger-file-policy-or-none
group: api
category: runtime
update-time: 20260707
description: Read the current runtime file policy from a ConfiguredLogger only when it is actually file-backed.
key-word:
- logger
- runtime
- file
- truthful
---
## Configured-logger-file-policy-or-none
Read the current runtime file policy from a `ConfiguredLogger` only when it is actually file-backed.
This is the truthful companion to `file_policy()`. Prefer it for diagnostics and recovery logic that must avoid fallback policy objects.
### Interface
```moonbit
pub fn ConfiguredLogger::file_policy_or_none(self : ConfiguredLogger) -> FileSinkPolicy? {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose current file policy should be inspected.
#### output
- `FileSinkPolicy?` - `Some(policy)` when the configured sink is file-backed, otherwise `None`.
### Explanation
Detailed rules explaining key parameters and behaviors
- File-backed sinks return `Some(current_policy)` through the wrapped `RuntimeSink`.
- Queued file sinks forward the wrapped inner file sink policy as `Some(current_policy)`.
- Non-file sinks return `None`.
- This helper is broader than `file_append_mode()` or `file_auto_flush()` because it returns the whole policy object without fallback synthesis.
### How to Use
Here are some specific examples provided.
#### When Need Truthful Policy Diagnostics
When diagnostics should inspect file policy only if file semantics exist:
```moonbit
match logger.file_policy_or_none() {
Some(policy) => ignore(policy)
None => ()
}
```
In this example, `None` means there is no real file policy to inspect.
#### When Need Compatibility-free Recovery Decisions
When recovery logic should not treat a fallback object as real state:
```moonbit
let maybe_policy = logger.file_policy_or_none()
```
In this example, callers can distinguish missing file semantics from a live policy snapshot.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `None`.
- If callers need default-policy comparison, pair it with `file_default_policy_or_none()`.
### Notes
1. Prefer this helper over `file_policy()` for truthful runtime diagnostics.
2. `file_policy()` remains available as the compatibility fallback API.
+15 -6
View File
@@ -2,8 +2,8 @@
name: configured-logger-file-policy
group: api
category: runtime
update-time: 20260512
description: Read the current runtime file policy from a configured file-backed logger.
update-time: 20260707
description: Read the current runtime file policy from a configured logger, with a compatibility fallback for non-file sinks.
key-word:
- logger
- runtime
@@ -15,6 +15,10 @@ key-word:
Read the current runtime file policy from a `ConfiguredLogger`. This helper exposes the active append, auto-flush, and rotation settings as one policy object.
The returned `FileSinkPolicy` value is owned by `src/file_model`; this configured-logger surface is a facade over `Logger[@runtime.RuntimeSink]` and delegates policy reads to the wrapped `RuntimeSink`, which in turn reaches file-backed variants that ultimately use `src/file_runtime.FileSink`.
`file_policy()` is the compatibility form. For truthful file-semantics detection, prefer `file_policy_or_none()`.
### Interface
```moonbit
@@ -33,9 +37,12 @@ pub fn ConfiguredLogger::file_policy(self : ConfiguredLogger) -> FileSinkPolicy
Detailed rules explaining key parameters and behaviors
- File-backed sinks return their current runtime file policy.
- Queued file sinks forward the policy from the wrapped file sink.
- Non-file sinks return a neutral fallback policy value.
- File-backed sinks return their current runtime file policy through the wrapped `RuntimeSink`.
- Queued file sinks forward the policy from the wrapped inner file sink.
- The returned policy object itself is the shared `@file_model.FileSinkPolicy` model, not a configured-logger-owned concrete type.
- Non-file sinks return the same neutral fallback policy value produced by `RuntimeSink::file_policy()`.
- This fallback keeps older callers source-compatible, but it is not a real file policy.
- New diagnostic or recovery code should prefer `file_policy_or_none()`.
- This helper is broader than `file_append_mode()` or `file_auto_flush()` because it returns the whole policy object.
### How to Use
@@ -66,10 +73,12 @@ In this example, callers can compare current runtime settings with the initial p
e.g.:
- If the configured sink is not file-backed, the return value is a neutral fallback policy rather than a real active file policy.
- If callers need a truthful file-semantics check, use `file_policy_or_none()`.
- If callers only need one field from the policy, a narrower helper may be simpler.
### Notes
1. Use this helper when file policy should be handled as one object.
2. Pair it with `file_set_policy(...)` for roundtrip-style policy management.
2. Pair it with `file_set_policy(...)` for roundtrip-style policy management, or prefer `file_policy_or_none()` for truthful diagnostics.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_reopen_append(self : ConfiguredLogger) -> Bool {}
Detailed rules explaining key parameters and behaviors
- Plain file sinks reopen in append mode.
- Queued file sinks forward reopen behavior to the wrapped file sink.
- Plain file sinks reopen in append mode through the wrapped `RuntimeSink`.
- Queued file sinks forward reopen behavior to the wrapped inner file sink.
- This helper is a specialized shortcut for a common reopen mode.
- Non-file sinks return `false`.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_reopen_truncate(self : ConfiguredLogger) -> Bool {
Detailed rules explaining key parameters and behaviors
- Plain file sinks reopen in truncate mode.
- Queued file sinks forward reopen behavior to the wrapped file sink.
- Plain file sinks reopen in truncate mode through the wrapped `RuntimeSink`.
- Queued file sinks forward reopen behavior to the wrapped inner file sink.
- This helper is a specialized shortcut for a common reset-style reopen mode.
- Non-file sinks return `false`.
@@ -33,8 +33,8 @@ pub fn ConfiguredLogger::file_reopen_with_current_policy(self : ConfiguredLogger
Detailed rules explaining key parameters and behaviors
- Plain file sinks reuse their current stored reopen policy.
- Queued file sinks forward reopen behavior to the wrapped file sink.
- Plain file sinks reuse their current stored reopen policy through the wrapped `RuntimeSink`.
- Queued file sinks forward reopen behavior to the wrapped inner file sink.
- This helper differs from `file_reopen(...)` because it does not accept a per-call append override.
- Non-file sinks return `false`.
+7 -4
View File
@@ -2,7 +2,7 @@
name: configured-logger-file-reopen
group: api
category: runtime
update-time: 20260512
update-time: 20260707
description: Reopen the file sink behind a configured runtime logger with an optional append-mode override.
key-word:
- logger
@@ -34,10 +34,11 @@ pub fn ConfiguredLogger::file_reopen(self : ConfiguredLogger, append~ : Bool? =
Detailed rules explaining key parameters and behaviors
- Plain file sinks reopen directly.
- Queued file sinks forward reopen behavior to the wrapped file sink.
- Plain file sinks reopen directly through the wrapped `RuntimeSink`.
- Queued file sinks forward reopen behavior to the wrapped inner file sink only when no queued records are pending.
- `append=None` preserves current reopen policy, while `Some(true/false)` overrides append mode.
- Non-file sinks return `false`.
- If a queued file sink still has pending records, reopen is rejected and returns `false` so queued records are not later written under a different reopen mode than the one they were queued under.
### How to Use
@@ -68,8 +69,10 @@ e.g.:
- If callers only need the current configured policy, `file_reopen_with_current_policy()` may be the clearer API.
- If a queued file sink still has pending records, callers should flush or close it first before attempting reopen.
### Notes
1. Use this helper for explicit recovery flows.
2. Pair it with `file_available()` and failure counters when diagnosing reopen behavior.
2. On queued file sinks, clear pending records first so reopen semantics stay stable for already queued writes.
@@ -72,4 +72,4 @@ e.g.:
1. Use this helper after diagnostics or recovery, not before capturing needed evidence.
2. It is the reset companion for the file failure-counter helpers.
2. For queued file sinks, it resets the inner file counters only; it does not clear still-pending queued records that may produce new failures on a later `file_flush()`.
@@ -2,8 +2,8 @@
name: configured-logger-file-reset-policy
group: api
category: runtime
update-time: 20260512
description: Reset the runtime file policy of a configured file-backed logger back to its original defaults.
update-time: 20260707
description: Reset the runtime file policy of a configured logger back to its captured defaults.
key-word:
- logger
- runtime
@@ -13,7 +13,7 @@ key-word:
## Configured-logger-file-reset-policy
Reset the runtime file policy of a `ConfiguredLogger` back to its original defaults. This helper restores append, auto-flush, and rotation policy together.
Reset the runtime file policy of a `ConfiguredLogger` back to its captured defaults.
### Interface
@@ -21,56 +21,17 @@ Reset the runtime file policy of a `ConfiguredLogger` back to its original defau
pub fn ConfiguredLogger::file_reset_policy(self : ConfiguredLogger) -> Bool {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose file policy should be restored.
#### output
- `Bool` - Whether the reset was applied.
### Explanation
Detailed rules explaining key parameters and behaviors
- File-backed sinks restore their stored default file policy.
- Queued file sinks forward the reset behavior to the wrapped file sink.
- File-backed sinks restore their runtime default policy through the wrapped `RuntimeSink`.
- Queued file sinks restore the wrapped inner file sink default policy only when no queued records are pending.
- Non-file sinks return `false`.
- This helper is the inverse of runtime policy drift, not a generic reopen or flush action.
### How to Use
Here are some specific examples provided.
#### When Need To Undo Runtime Policy Changes
When file policy should return to the original configured defaults:
```moonbit
ignore(logger.file_reset_policy())
```
In this example, append, auto-flush, and rotation settings are restored together.
#### When Use Drift-aware Recovery
When reset should only happen after runtime changes:
```moonbit
if !logger.file_policy_matches_default() {
ignore(logger.file_reset_policy())
}
```
In this example, reset is only applied when runtime policy has diverged.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `false`.
- If callers need to restore only one setting, a narrower setter may be more appropriate than a full policy reset.
- If a queued file sink still has pending records, the reset is rejected and returns `false` so already queued records are not later written under a different policy than the one they were queued under.
### Notes
1. Use this helper when the original bundled file policy should be restored intact.
1. Use this helper when runtime file policy should return to its captured defaults.
2. It pairs naturally with `file_policy_matches_default()` and `file_default_policy()`.
2. On queued file sinks, clear pending records first so policy mutation does not retroactively affect already queued writes.

Some files were not shown because too many files have changed in this diff Show More