mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-07-24 00:42:18 +00:00
Compare commits
332 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f532af026 | |||
| bee919a557 | |||
| d901bfd435 | |||
| d39d577c90 | |||
| f4b161448c | |||
| cf80e583ac | |||
| 2bee0b1da8 | |||
| 1b621497f9 | |||
| c90a408a9c | |||
| 6b181e7855 | |||
| 7946dcdaee | |||
| 3bac025acf | |||
| d3bff1305d | |||
| b90481186f | |||
| 6e3cc165b6 | |||
| 8de5495f58 | |||
| f44a4794ec | |||
| 0cbe25a551 | |||
| 4cc43def73 | |||
| 812c4c21a8 | |||
| cdfe0117a0 | |||
| b43dbdecfb | |||
| 0cfb308fa2 | |||
| a0e09fb30f | |||
| dfae044f75 | |||
| 5e80593ae0 | |||
| 31f06f5724 | |||
| 159f534164 | |||
| ea0d687da0 | |||
| ce87e32837 | |||
| 866e374ac8 | |||
| aaba931db6 | |||
| 04f7742238 | |||
| cf23bb6dbe | |||
| bf27dacf23 | |||
| 0a9bcd696b | |||
| 8fb9beb008 | |||
| 5514009abe | |||
| c17883262a | |||
| 5476a64478 | |||
| 685797783c | |||
| 11b49208b2 | |||
| 18dc8d5860 | |||
| 18f6bf9eb5 | |||
| 958a7fd26f | |||
| 1124868bd8 | |||
| c800042e46 | |||
| fe7665b9f6 | |||
| 93cadcc7f1 | |||
| 480c5ad602 | |||
| 5720ba6203 | |||
| 9630c6cc3b | |||
| 6eec61eb2d | |||
| 3a216a2ada | |||
| b0364fdd51 | |||
| 0b2e35d138 | |||
| 6dc54379ea | |||
| 458e3a14b3 | |||
| e3c7e00b1e | |||
| 2fd1290179 | |||
| f2b90b44e6 | |||
| cf2c8cdca6 | |||
| 4e964245d5 | |||
| cc3d1b1f1c | |||
| e5140181ba | |||
| 2bb9b20aa4 | |||
| 32cbc2e079 | |||
| 62117a4a69 | |||
| bba206be78 | |||
| 22f5204262 | |||
| 5bb5a75ea7 | |||
| 6d1261f02b | |||
| 33fc929dc6 | |||
| c990a890f9 | |||
| 676317aae7 | |||
| 7f57521e05 | |||
| 9b3533de7b | |||
| e01f7e977e | |||
| 1a2aee4a7c | |||
| 571ae622d0 | |||
| 475b15c41f | |||
| 0eeb3f9811 | |||
| 0b8fbe53dd | |||
| 8dab08b85d | |||
| 5edd853fc5 | |||
| 5957e37720 | |||
| 8cf4eba223 | |||
| 754a878cd8 | |||
| 1073559f6a | |||
| 20e08efb78 | |||
| 64dfc36931 | |||
| d46e90fcf5 | |||
| 891aa83e01 | |||
| 97f6ae969d | |||
| 93fc54226d | |||
| 4421eb985d | |||
| 96c4cc1f88 | |||
| 415fc8d98e | |||
| d89a629f14 | |||
| 83c31f31b2 | |||
| 93afc5fe45 | |||
| a7e9731571 | |||
| 83e1accc8e | |||
| 888581e44a | |||
| 2ecf88d866 | |||
| 19befe9418 | |||
| dcbc2422c6 | |||
| 5bb34b4e66 | |||
| 3fcdf8f64a | |||
| 674e949f96 | |||
| bbc9afbc47 | |||
| e386ef8b28 | |||
| af9e4e853a | |||
| eb3ec42a59 | |||
| 54fdff3317 | |||
| ded41de176 | |||
| c12af269dc | |||
| 03079797f2 | |||
| f6fc9c4201 | |||
| d94b415cb0 | |||
| 4dd1e5a33d | |||
| 54e695942f | |||
| 4889f2b4b8 | |||
| 58d1e622a0 | |||
| af1fc95a3c | |||
| 20919618ce | |||
| 35e87f1761 | |||
| 4b043bc52e | |||
| 296901d2ba | |||
| c78669393c | |||
| 9162b37acd | |||
| d777496d51 | |||
| 44903d113d | |||
| feb786298f | |||
| f91bcc827e | |||
| da1e9c0359 | |||
| acb4a13f14 | |||
| b320b4f1fc | |||
| 8715949242 | |||
| 70c2bad4b1 | |||
| 83b0a2ac3c | |||
| 5ded4439b0 | |||
| 556ee967b8 | |||
| 72e29f5b69 | |||
| 57b1dfb127 | |||
| 4521961227 | |||
| a164678654 | |||
| 50afeacff6 | |||
| 98e514821f | |||
| 97feb57cd7 | |||
| 9c1787e700 | |||
| 7ba66e82ae | |||
| c316c65027 | |||
| 4b68ae27f1 | |||
| 38a5b056b8 | |||
| f636c2df60 | |||
| a66a876659 | |||
| f8192ab677 | |||
| 82200c61fb | |||
| 43e343158e | |||
| 3a2817969a | |||
| 1b508dfc11 | |||
| 959e553648 | |||
| 1f3da3e6ba | |||
| a2b37dfe53 | |||
| 6c5f4aaa0e | |||
| 3ba590a4b0 | |||
| 302e11218c | |||
| b2a1f407ec | |||
| 24c20c16be | |||
| 254a9e8086 | |||
| 0d2bfd7b6d | |||
| b570722434 | |||
| 58e219a512 | |||
| 88ee050529 | |||
| 6962c7f267 | |||
| e91aba10fc | |||
| 74f65b9585 | |||
| 5f722b0073 | |||
| fd9127c524 | |||
| 057eb7c1fc | |||
| 24897a45c9 | |||
| 9370c83ffc | |||
| 3a2d1d74c3 | |||
| ef4ffe4c5b | |||
| f69307ba61 | |||
| 2582876a08 | |||
| bf60ed95c3 | |||
| 39baad5e93 | |||
| 383d2e8dff | |||
| b57013202e | |||
| 13e8a8f484 | |||
| 3cd361dc4d | |||
| c262aefc28 | |||
| a195c93457 | |||
| efacaa6398 | |||
| af54743536 | |||
| 8d28c37e38 | |||
| c2fa1eea35 | |||
| d2612e5a67 | |||
| 49eaa67d57 | |||
| 6d98301daa | |||
| 5f46c04eea | |||
| b36ce021dc | |||
| ead2339ffd | |||
| cca3bc4500 | |||
| 9d55440df4 | |||
| 4077f3e538 | |||
| 658196cf69 | |||
| afe5c75537 | |||
| 2c552adfae | |||
| 1f61e4f66a | |||
| b68ca9eef5 | |||
| 9da5b9d659 | |||
| e97407c22c | |||
| 41fbfa5ac1 | |||
| 5c4e8ba889 | |||
| c96a592ef6 | |||
| eb4abeb81b | |||
| e2609a99b6 | |||
| 2709a482ec | |||
| 999dd5a297 | |||
| bd75deb879 | |||
| db5d95576d | |||
| f4af7bc04c | |||
| 8ce3b8fefd | |||
| da8e7483ed | |||
| e617f5bf6a | |||
| 9042562c54 | |||
| 4c5b6cc2cf | |||
| 1bb3e41a97 | |||
| 03e467d5f4 | |||
| 8c129e1c42 | |||
| a7e955e636 | |||
| 2eebef07db | |||
| 594be4aca5 | |||
| 9157346bf8 | |||
| 7474c07cbd | |||
| 11db21a500 | |||
| 17dbdfffde | |||
| a41515b8eb | |||
| 2f600c71c5 | |||
| e6dc5f52c0 | |||
| 7a0c741857 | |||
| e3b9cbb6e9 | |||
| 264ee05222 | |||
| 9ef2f984ca | |||
| 6a13f70242 | |||
| c767ebe512 | |||
| 402cb7e423 | |||
| 24fbc189ae | |||
| 935f320743 | |||
| d288216bcb | |||
| c3b54e3ae0 | |||
| 413610d5f2 | |||
| 33f71af500 | |||
| f7863083d4 | |||
| fcac1d7993 | |||
| 0bdbe942dd | |||
| 1645bcb66c | |||
| 112edbf8ae | |||
| 8084d0a0cc | |||
| d23e326315 | |||
| e38b0b4150 | |||
| ce89aaf96d | |||
| c53aa38b89 | |||
| 3e0c8a5f99 | |||
| f1250512eb | |||
| 26de2e81d6 | |||
| 9af489336d | |||
| d9b609d064 | |||
| 91b0900b11 | |||
| e13ea2209a | |||
| 034bd8fc99 | |||
| c911ad8345 | |||
| e776455861 | |||
| f824cbfa68 | |||
| abed0f00b8 | |||
| 75f1b457fa | |||
| e53555edb2 | |||
| 323d58059b | |||
| 8e409bbdf9 | |||
| b3fb59e6b5 | |||
| a6e9dd993f | |||
| 324ff47b10 | |||
| 8bf6ec8f52 | |||
| bf545b0e0c | |||
| d4db20fffc | |||
| 861adb7b5d | |||
| 1a33f5c95d | |||
| f50964f5a9 | |||
| ba72a021a8 | |||
| 5f0c7be4e4 | |||
| 4d2e3def14 | |||
| 2a9dbdcc01 | |||
| 47de8dc99a | |||
| 888be5b6fc | |||
| 15a9175cf4 | |||
| 3a68be920e | |||
| 699dd5ff96 | |||
| f8ca093e95 | |||
| be8b4f8626 | |||
| 3d88ac87a1 | |||
| 79529d748f | |||
| ab7cd62851 | |||
| e172e141e9 | |||
| d92155a727 | |||
| 78007a5b22 | |||
| e28dba0800 | |||
| 747f8e3d1b | |||
| 5917256fda | |||
| b937807136 | |||
| 5ce4c29760 | |||
| 911dcd840c | |||
| 4431e01dcb | |||
| d950f11b40 | |||
| 2845076b27 | |||
| 7207c07cbf | |||
| f19e9649af | |||
| c45ee1050f | |||
| d748bfc6e6 | |||
| 77d97cef5e | |||
| 3888d39bc0 | |||
| f7febb9f67 | |||
| 7440f1d328 | |||
| e035625fc1 | |||
| dae9d38a3f | |||
| ab45a9e4e3 | |||
| e27f5680e6 | |||
| 8196fe27b6 | |||
| 4b5456f646 | |||
| f4022ce95c |
@@ -67,15 +67,9 @@ jobs:
|
||||
moon check --deny-warn
|
||||
moon test --deny-warn
|
||||
moon check --target native --deny-warn
|
||||
moon check --target wasm --deny-warn
|
||||
moon check --target wasm-gc --deny-warn
|
||||
moon check --target js --deny-warn
|
||||
|
||||
- name: Test wasm path
|
||||
shell: bash
|
||||
run: |
|
||||
moon test --target wasm --deny-warn
|
||||
|
||||
- name: Run sync example
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -111,17 +105,15 @@ jobs:
|
||||
run: |
|
||||
moon update
|
||||
|
||||
- name: Check async package on wasm, wasm-gc and js
|
||||
- name: Check async package on wasm-gc and js
|
||||
shell: bash
|
||||
run: |
|
||||
moon check src-async --target wasm --deny-warn
|
||||
moon check src-async --target wasm-gc --deny-warn
|
||||
moon check src-async --target js --deny-warn
|
||||
|
||||
- name: Test async package on wasm, wasm-gc and js
|
||||
- name: Test async package on wasm-gc and js
|
||||
shell: bash
|
||||
run: |
|
||||
moon test src-async --target wasm --deny-warn
|
||||
moon test src-async --target wasm-gc --deny-warn
|
||||
moon test src-async --target js --deny-warn
|
||||
|
||||
|
||||
@@ -26,8 +26,5 @@ docs/.vitepress/cache/
|
||||
docs/.vitepress/dist/
|
||||
docs/.vitepress/generated/
|
||||
|
||||
# Benchmark package has no public API; moon info emits an empty snapshot.
|
||||
benchmarks/pkg.generated.mbti
|
||||
|
||||
# vibecoding
|
||||
AGENTS/
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Agent Notes
|
||||
|
||||
## Version Releases
|
||||
|
||||
Do not update package versions and installation snippets by hand. Run the
|
||||
repository release helper from the root instead:
|
||||
|
||||
```powershell
|
||||
.\scripts\update-version.ps1 <x.x.x>
|
||||
```
|
||||
|
||||
For a normal patch release, the script updates `moon.mod`, all current
|
||||
installation examples, creates the next `docs/changes/<release>(<version>).md`,
|
||||
and adds it to `docs/changes/index.md`. Review and replace any generated `TODO`
|
||||
entries in the release note before committing.
|
||||
|
||||
For a major or minor package-version change, supply the public release number
|
||||
explicitly so it is not inferred:
|
||||
|
||||
```powershell
|
||||
.\scripts\update-version.ps1 <x.x.x> -ReleaseVersion <x.x.x>
|
||||
```
|
||||
|
||||
Use `-WhatIf` to preview the target files. Pass `-Change`, `-Verification`,
|
||||
and `-Note` to populate release-note sections directly. Validate the edited
|
||||
repository before committing with a `gitmoji + short desc` message.
|
||||
@@ -6,56 +6,34 @@ BitLogger 是一个使用 MoonBit 编写的结构化日志库,适合命令行
|
||||
- [English README](./docs/README-en.md)
|
||||
- [Wiki](https://bitlogger.naloveyuki.top)
|
||||
|
||||
## 从第一个日志到扩展能力
|
||||
## 介绍
|
||||
|
||||
BitLogger 的文档按使用路径组织:先完成一个可运行的日志输出,再按需进入文件、配置、异步和组合能力。API 文档保留为精确参考,不需要从 API 文件名开始阅读。
|
||||
BitLogger 提供统一的日志级别、目标名、结构化字段和可定制格式,既可以直接输出到控制台,也可以在 native 环境写入文件,并提供异步日志版本。
|
||||
|
||||
### 1. 安装
|
||||
|
||||
从一个空项目开始:
|
||||
|
||||
```bash
|
||||
moon new log-demo
|
||||
cd log-demo
|
||||
moon add Nanaloveyuki/BitLogger@0.7.3
|
||||
```
|
||||
|
||||
### 2. 写入第一条结构化日志
|
||||
|
||||
在应用 package 的 `moon.pkg` 中导入库:
|
||||
## 快速开始
|
||||
|
||||
```moonbit
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
}
|
||||
let logger = build_logger(
|
||||
text_console(
|
||||
min_level=Level::Info,
|
||||
target="demo",
|
||||
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
|
||||
),
|
||||
)
|
||||
|
||||
logger.info("starting", fields=[field("port", "8080")])
|
||||
ignore(logger.flush())
|
||||
```
|
||||
|
||||
然后在 `main.mbt` 中创建控制台 logger:
|
||||
推荐从 `console(...)`、`json_console(...)`、`text_console(...)`、`file(...)` 这几个入口开始,再按需配合 `with_queue(...)` 或 `with_file_rotation(...)`。
|
||||
|
||||
```moonbit
|
||||
fn main {
|
||||
let logger = @log.build_logger(
|
||||
@log.console(min_level=@log.Level::Info, target="app"),
|
||||
)
|
||||
|
||||
logger.info("server started", fields=[
|
||||
@log.field("port", "8080"),
|
||||
@log.field("environment", "development"),
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
运行 `moon run` 后,记录会携带 level、target、message 和 fields。下一步按实际需求选择:
|
||||
|
||||
- [控制台与结构化字段](./docs/examples/console.md)
|
||||
- [文件输出与轮转](./docs/examples/file-rotation.md)
|
||||
- [JSON 配置构建](./docs/examples/config.md)
|
||||
- [异步日志生命周期](./docs/examples/async.md)
|
||||
如果你需要自己组合 sink,比如 `fanout`、`split`、`callback`,再使用 `Logger::new(...)`。
|
||||
|
||||
## 支持情况
|
||||
|
||||
- `BitLogger` 当前在 CI 中检查/验证的目标是 `native`、`wasm`、`js`、`wasm-gc`
|
||||
- `bitlogger_async` 当前在 CI 中检查和测试 `native`、`wasm`、`js`、`wasm-gc`
|
||||
- `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 入口提供
|
||||
@@ -68,13 +46,20 @@ fn main {
|
||||
- 配置驱动构建:`build_logger(...)`、`build_async_logger(...)`
|
||||
- 组合能力:queue、filter、patch、fanout、split、callback
|
||||
- 异步日志:独立 `src-async` package
|
||||
- Trace context:标准 `trace_id`、`span_id`、`trace_flags`、`trace_state` 字段绑定
|
||||
|
||||
## 示例
|
||||
|
||||
- `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/`:异步日志示例
|
||||
|
||||
## 文档
|
||||
|
||||
- [Examples:完整使用流程](./docs/examples/index.md)
|
||||
- [Extend:队列、组合、格式化和跨端边界](./docs/extend/index.md)
|
||||
- [API 索引:按公开符号查询](./docs/api/index.md)
|
||||
- [API 索引](./docs/api/index.md)
|
||||
- [src package README](./src/README.mbt.md)
|
||||
|
||||
仓库内的 `examples/` 目录与 Examples 文档一一对应;文档解释选择、前提、运行命令和后续扩展,源码保持为可执行参考。
|
||||
常用入口:`text_console(...)`、`file(...)`、`with_queue(...)`、`build_logger(...)`、`build_async_logger(...)`
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# BitLogger Benchmarks
|
||||
|
||||
Run the native baseline from the repository root:
|
||||
|
||||
```powershell
|
||||
moon bench benchmarks --target native --release --deny-warn
|
||||
```
|
||||
|
||||
The suite measures callback emission, trace-context field binding, synchronous queue enqueue-and-flush, and native file writes. It intentionally has no pass/fail threshold: compare results only on the same machine, toolchain, target, and power profile.
|
||||
|
||||
Async throughput remains lifecycle-sensitive and is exercised by the native integration test rather than mixed into these synchronous microbenchmarks. Record its end-to-end measurements separately when profiling an application workload.
|
||||
@@ -1,61 +0,0 @@
|
||||
///|
|
||||
let benchmark_record : @log.Record = @log.Record::new(
|
||||
@log.Level::Info,
|
||||
"benchmark record",
|
||||
target="bench",
|
||||
fields=[@log.field("component", "bitlogger")],
|
||||
)
|
||||
|
||||
///|
|
||||
test "bench callback logger emission" (it : @bench.T) {
|
||||
let writes : Ref[Int] = Ref(0)
|
||||
let logger = @log.Logger::new(@log.callback_sink(fn(_) { writes.val += 1 }))
|
||||
it.bench(fn() {
|
||||
logger.info("benchmark record", fields=[
|
||||
@log.field("component", "bitlogger"),
|
||||
])
|
||||
it.keep(writes.val)
|
||||
})
|
||||
}
|
||||
|
||||
///|
|
||||
test "bench trace context field binding" (it : @bench.T) {
|
||||
let writes : Ref[Int] = Ref(0)
|
||||
let context = @log.trace_context("trace-bench", "span-bench")
|
||||
let logger = @log.Logger::new(@log.callback_sink(fn(_) { writes.val += 1 })).with_trace_context(
|
||||
context,
|
||||
)
|
||||
it.bench(fn() {
|
||||
logger.info("benchmark record", fields=[@log.field("event", "request")])
|
||||
it.keep(writes.val)
|
||||
})
|
||||
}
|
||||
|
||||
///|
|
||||
test "bench queued logger enqueue and flush" (it : @bench.T) {
|
||||
let writes : Ref[Int] = Ref(0)
|
||||
let logger = @log.Logger::new(@log.callback_sink(fn(_) { writes.val += 1 })).with_queue(
|
||||
max_pending=8,
|
||||
)
|
||||
it.bench(fn() {
|
||||
logger.info("benchmark record")
|
||||
it.keep(logger.sink.flush())
|
||||
})
|
||||
}
|
||||
|
||||
///|
|
||||
test "bench native file sink write" (it : @bench.T) {
|
||||
let sink = @log.file_sink(
|
||||
"logs/bitlogger-benchmark.log",
|
||||
append=false,
|
||||
auto_flush=false,
|
||||
formatter=fn(rec) { rec.message },
|
||||
)
|
||||
if sink.is_available() {
|
||||
it.bench(fn() {
|
||||
sink.write(benchmark_record)
|
||||
it.keep(sink.write_failures())
|
||||
})
|
||||
ignore(sink.close())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
"moonbitlang/core/bench",
|
||||
}
|
||||
+22
-124
@@ -104,119 +104,6 @@ function buildChangesSidebar(): DefaultTheme.SidebarItem[] {
|
||||
]
|
||||
}
|
||||
|
||||
function buildExamplesSidebar(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{ text: 'Overview', link: '/examples/' },
|
||||
{ text: 'Console And Fields', link: '/examples/console' },
|
||||
{ text: 'File Rotation', link: '/examples/file-rotation' },
|
||||
{ text: 'Configuration', link: '/examples/config' },
|
||||
{ text: 'Async Lifecycle', link: '/examples/async' },
|
||||
]
|
||||
}
|
||||
|
||||
function buildExtendSidebar(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{ text: 'Overview', link: '/extend/' },
|
||||
{ text: 'Queueing', link: '/extend/queue' },
|
||||
{ text: 'Sink Composition', link: '/extend/composition' },
|
||||
{ text: 'Text Formatting', link: '/extend/formatting' },
|
||||
{ text: 'Target Boundaries', link: '/extend/targets' },
|
||||
{ text: 'Trace Context', link: '/extend/observability' },
|
||||
]
|
||||
}
|
||||
|
||||
function buildChineseExamplesSidebar(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{ text: '概览', link: '/zh/examples/' },
|
||||
{ text: '控制台与结构化字段', link: '/zh/examples/console' },
|
||||
{ text: '文件输出与轮转', link: '/zh/examples/file-rotation' },
|
||||
{ text: '配置驱动构建', link: '/zh/examples/config' },
|
||||
{ text: '异步日志生命周期', link: '/zh/examples/async' },
|
||||
]
|
||||
}
|
||||
|
||||
function buildChineseExtendSidebar(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{ text: '概览', link: '/zh/extend/' },
|
||||
{ text: '队列与溢出策略', link: '/zh/extend/queue' },
|
||||
{ text: 'Sink 组合', link: '/zh/extend/composition' },
|
||||
{ text: '文本格式与样式', link: '/zh/extend/formatting' },
|
||||
{ text: '目标平台边界', link: '/zh/extend/targets' },
|
||||
{ text: 'Trace Context', link: '/zh/extend/observability' },
|
||||
]
|
||||
}
|
||||
|
||||
function buildChineseApiSidebar(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{ text: '概览', link: '/zh/api/' },
|
||||
{ text: '基础记录', link: '/zh/api/basics' },
|
||||
{ text: '配置与队列', link: '/zh/api/configuration' },
|
||||
{ text: '文件输出', link: '/zh/api/file-output' },
|
||||
{ text: '文本格式', link: '/zh/api/formatting' },
|
||||
{ text: '异步生命周期', link: '/zh/api/async' },
|
||||
{ text: 'Sink 组合', link: '/zh/api/composition' },
|
||||
]
|
||||
}
|
||||
|
||||
const englishThemeConfig: DefaultTheme.Config = {
|
||||
siteTitle: 'BitLogger',
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Examples', link: '/examples/' },
|
||||
{ text: 'Extend', link: '/extend/' },
|
||||
{ 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: {
|
||||
'/examples/': buildExamplesSidebar(),
|
||||
'/extend/': buildExtendSidebar(),
|
||||
'/api/': buildApiSidebar(),
|
||||
'/changes/': buildChangesSidebar(),
|
||||
},
|
||||
footer: {
|
||||
message: 'Published from the repository docs folder with VitePress.',
|
||||
copyright: 'MIT',
|
||||
},
|
||||
}
|
||||
|
||||
const chineseThemeConfig: DefaultTheme.Config = {
|
||||
siteTitle: 'BitLogger',
|
||||
nav: [
|
||||
{ text: '首页', link: '/zh/' },
|
||||
{ text: '示例', link: '/zh/examples/' },
|
||||
{ text: '扩展', link: '/zh/extend/' },
|
||||
{ text: 'API', link: '/zh/api/' },
|
||||
{ text: '更新记录(英文)', 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: '在 GitHub 上编辑此页',
|
||||
},
|
||||
sidebar: {
|
||||
'/zh/examples/': buildChineseExamplesSidebar(),
|
||||
'/zh/extend/': buildChineseExtendSidebar(),
|
||||
'/zh/api/': buildChineseApiSidebar(),
|
||||
},
|
||||
footer: {
|
||||
message: '由仓库中的 VitePress 文档构建。',
|
||||
copyright: 'MIT',
|
||||
},
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
title: 'BitLogger',
|
||||
description: 'Structured logging library docs for MoonBit.',
|
||||
@@ -227,18 +114,29 @@ export default defineConfig({
|
||||
markdown: {
|
||||
languages: [createMoonbitLanguageRegistration()],
|
||||
},
|
||||
themeConfig: englishThemeConfig,
|
||||
locales: {
|
||||
root: {
|
||||
label: 'English',
|
||||
lang: 'en-US',
|
||||
themeConfig: englishThemeConfig,
|
||||
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',
|
||||
},
|
||||
zh: {
|
||||
label: '简体中文',
|
||||
lang: 'zh-CN',
|
||||
link: '/zh/',
|
||||
themeConfig: chineseThemeConfig,
|
||||
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',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,46 +6,11 @@ import HomeDocHub from './components/HomeDocHub.vue'
|
||||
|
||||
import './custom.css'
|
||||
|
||||
const localePreferenceKey = 'bitlogger-docs-locale'
|
||||
|
||||
function routePath(path: string): string {
|
||||
return path.split(/[?#]/, 1)[0]
|
||||
}
|
||||
|
||||
function isLocaleRoot(path: string): boolean {
|
||||
const normalized = routePath(path)
|
||||
return normalized === '/' || normalized === '/zh/'
|
||||
}
|
||||
|
||||
function browserPrefersChinese(): boolean {
|
||||
return navigator.languages.some(language => language.toLowerCase().startsWith('zh'))
|
||||
}
|
||||
|
||||
const theme: Theme = {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp({ app, router }) {
|
||||
enhanceApp({ app }) {
|
||||
app.component('ApiOverview', ApiOverview)
|
||||
app.component('HomeDocHub', HomeDocHub)
|
||||
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
router.onAfterRouteChange = to => {
|
||||
if (!isLocaleRoot(to)) return
|
||||
window.localStorage.setItem(localePreferenceKey, routePath(to) === '/zh/' ? 'zh' : 'en')
|
||||
}
|
||||
|
||||
if (router.route.path !== '/') return
|
||||
|
||||
const savedLocale = window.localStorage.getItem(localePreferenceKey)
|
||||
const locale = savedLocale === 'zh' || savedLocale === 'en'
|
||||
? savedLocale
|
||||
: browserPrefersChinese()
|
||||
? 'zh'
|
||||
: 'en'
|
||||
|
||||
if (locale === 'zh') {
|
||||
void router.go('/zh/')
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+24
-31
@@ -9,42 +9,28 @@ BitLogger is a structured logging library for MoonBit projects.
|
||||
|
||||
BitLogger gives you consistent levels, targets, structured fields, configurable text output, native file logging, and an async logging layer.
|
||||
|
||||
## Start With A Working Flow
|
||||
|
||||
The documentation is organized around complete usage flows. Start with an executable logger, then move into file output, configuration, async lifecycle, and extensions. The API reference remains the precise symbol-level reference.
|
||||
|
||||
### 1. Install
|
||||
|
||||
```bash
|
||||
moon new log-demo
|
||||
cd log-demo
|
||||
moon add Nanaloveyuki/BitLogger@0.7.3
|
||||
```
|
||||
|
||||
### 2. Import And Log
|
||||
|
||||
Add the package import to your application's `moon.pkg`:
|
||||
## Quick Start
|
||||
|
||||
```moonbit
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
}
|
||||
let logger = build_logger(
|
||||
text_console(
|
||||
min_level=Level::Info,
|
||||
target="demo",
|
||||
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
|
||||
),
|
||||
)
|
||||
|
||||
logger.info("starting", fields=[field("port", "8080")])
|
||||
ignore(logger.flush())
|
||||
```
|
||||
|
||||
```moonbit
|
||||
fn main {
|
||||
let logger = @log.build_logger(
|
||||
@log.console(min_level=@log.Level::Info, target="app"),
|
||||
)
|
||||
logger.info("server started", fields=[@log.field("port", "8080")])
|
||||
}
|
||||
```
|
||||
Start with `console(...)`, `json_console(...)`, `text_console(...)`, or `file(...)`, then add `with_queue(...)` or `with_file_rotation(...)` only when needed.
|
||||
|
||||
Continue with the [Examples guide](./examples/index.md): console fields, file rotation, JSON configuration, and async lifecycle are each documented as a complete path.
|
||||
Use `Logger::new(...)` when you want to assemble custom sink graphs directly.
|
||||
|
||||
## Support Status
|
||||
|
||||
- CI checks and tests `native`, `wasm`, `js`, and `wasm-gc` for the main `src` package and `src-async`
|
||||
- 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
|
||||
@@ -58,12 +44,19 @@ Continue with the [Examples guide](./examples/index.md): console fields, file ro
|
||||
- Config-based builders: `build_logger(...)` and `build_async_logger(...)`
|
||||
- Composition helpers: queue, filter, patch, fanout, split, callback
|
||||
- Separate async package under `src-async`
|
||||
- Trace-context binding through standard `trace_id`, `span_id`, `trace_flags`, and `trace_state` fields
|
||||
|
||||
## Examples
|
||||
|
||||
- `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
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Examples](./examples/index.md): complete application-facing usage flows
|
||||
- [Extend](./extend/index.md): queueing, sink composition, formatting, and target boundaries
|
||||
- [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
|
||||
|
||||
@@ -13,14 +13,14 @@ key-word:
|
||||
|
||||
## Async-logger-build-config-to-json
|
||||
|
||||
Convert `AsyncLoggerBuildConfig` into a `Json`. This helper exports both the base synchronous logger config and the async runtime config as one structured payload.
|
||||
Convert `AsyncLoggerBuildConfig` into a `JsonValue`. This helper exports both the base synchronous logger config and the async runtime config as one structured payload.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn async_logger_build_config_to_json(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> Json {}
|
||||
) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -29,7 +29,7 @@ pub fn async_logger_build_config_to_json(
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the full async build config.
|
||||
- `JsonValue` - Structured JSON representation of the full async build config.
|
||||
|
||||
### Explanation
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Async-logger-config-to-json
|
||||
|
||||
Convert a typed `AsyncLoggerConfig` into a `Json`. This helper exports async queue capacity, overflow policy, batch sizing, linger timing, and flush behavior in a structured form.
|
||||
Convert a typed `AsyncLoggerConfig` into a `JsonValue`. This helper exports async queue capacity, overflow policy, batch sizing, linger timing, and flush behavior in a structured form.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {}
|
||||
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the async config.
|
||||
- `JsonValue` - Structured JSON representation of the async config.
|
||||
|
||||
### Explanation
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Async-logger-state-to-json
|
||||
|
||||
Convert `AsyncLoggerState` into a `Json`. 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.
|
||||
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
|
||||
|
||||
```moonbit
|
||||
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {}
|
||||
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the async logger snapshot.
|
||||
- `JsonValue` - Structured JSON representation of the async logger snapshot.
|
||||
|
||||
### Explanation
|
||||
|
||||
@@ -62,7 +62,7 @@ In this example, callers receive a structured value that can be composed into la
|
||||
When another serializer or pipeline expects a JSON value:
|
||||
```moonbit
|
||||
let payload = async_logger_state_to_json(logger.state())
|
||||
println(payload.stringify())
|
||||
println(@json_parser.stringify(payload))
|
||||
```
|
||||
|
||||
In this example, the helper stays useful even outside the built-in stringify wrapper.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: async-logger-with-trace-context
|
||||
group: api
|
||||
category: async
|
||||
update-time: 20260719
|
||||
description: Bind TraceContext fields before records enter an AsyncLogger queue.
|
||||
key-word:
|
||||
- async
|
||||
- trace
|
||||
- context
|
||||
---
|
||||
|
||||
## Async-logger-with-trace-context
|
||||
|
||||
```moonbit
|
||||
let contextual = logger.with_trace_context(
|
||||
@bitlogger.trace_context("trace-1", "span-1"),
|
||||
)
|
||||
```
|
||||
|
||||
The helper stores the mapped fields on the returned async logger and preserves its queue, lifecycle, target, and threshold state. Context is added before records are enqueued.
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Async-runtime-state-to-json
|
||||
|
||||
Convert `AsyncRuntimeState` into a `Json`. This helper exports the async runtime mode and background worker capability in a structured form.
|
||||
Convert `AsyncRuntimeState` into a `JsonValue`. This helper exports the async runtime mode and background worker capability in a structured form.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {}
|
||||
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the runtime state.
|
||||
- `JsonValue` - Structured JSON representation of the runtime state.
|
||||
|
||||
### Explanation
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## File-rotation-config-to-json
|
||||
|
||||
Convert `FileRotation` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
Convert `FileRotation` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn file_rotation_config_to_json(config : FileRotation) -> Json {}
|
||||
pub fn file_rotation_config_to_json(config : FileRotation) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn file_rotation_config_to_json(config : FileRotation) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the rotation policy.
|
||||
- `JsonValue` - Structured JSON representation of the rotation policy.
|
||||
|
||||
### Explanation
|
||||
|
||||
@@ -82,7 +82,7 @@ e.g.:
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when downstream code expects `Json` rather than a typed `FileRotation` value.
|
||||
1. Use this helper when downstream code expects `JsonValue` rather than a typed `FileRotation` value.
|
||||
|
||||
2. Pair it with `file_rotation(...)` when building rotation policy in code before export.
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## File-sink-policy-to-json
|
||||
|
||||
Convert `FileSinkPolicy` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
Convert `FileSinkPolicy` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> Json {}
|
||||
pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the file policy.
|
||||
- `JsonValue` - Structured JSON representation of the file policy.
|
||||
|
||||
### Explanation
|
||||
|
||||
@@ -73,7 +73,7 @@ e.g.:
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when downstream code expects `Json` rather than text.
|
||||
1. Use this helper when downstream code expects `JsonValue` rather than text.
|
||||
|
||||
2. It pairs naturally with `FileSink::policy()`, `RuntimeSink::file_policy()`, and related default-policy accessors.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: file-sink-reopen-append
|
||||
group: api
|
||||
category: sink
|
||||
update-time: 20260717
|
||||
update-time: 20260613
|
||||
description: Reopen a FileSink in append mode.
|
||||
key-word:
|
||||
- file
|
||||
@@ -37,7 +37,6 @@ Detailed rules explaining key parameters and behaviors
|
||||
- Reopen behavior is fixed to append mode.
|
||||
- The stored append policy is updated to `true` as part of the reopen path.
|
||||
- On failure, the sink remains unavailable and `open_failures` is incremented.
|
||||
- After a rotation failure, this is the explicit recovery path once the underlying remove, rename, or open condition has been resolved.
|
||||
|
||||
### How to Use
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: file-sink-rotation-failures
|
||||
group: api
|
||||
category: sink
|
||||
update-time: 20260717
|
||||
update-time: 20260707
|
||||
description: Read the number of rotation failures recorded by a FileSink.
|
||||
key-word:
|
||||
- file
|
||||
@@ -36,7 +36,6 @@ Detailed rules explaining key parameters and behaviors
|
||||
- The sink reports its recorded rotation-failure count directly.
|
||||
- The counter increases when a rotation attempt cannot complete its critical file steps, such as removing the active file with zero backups, renaming a live file into `.1`, shifting an existing backup to a higher slot, or reopening the fresh active file afterward.
|
||||
- Missing optional backup slots do not count by themselves; only steps that fail while their source path still exists are treated as rotation failures.
|
||||
- When a critical rotation step fails, the triggering record is not written and the sink remains unavailable until an explicit reopen succeeds.
|
||||
- The counter is cumulative until reset.
|
||||
- This helper is observation-only and does not mutate sink state.
|
||||
|
||||
@@ -71,8 +70,6 @@ e.g.:
|
||||
|
||||
- This counter alone does not say whether the sink is currently available.
|
||||
|
||||
- After a recorded rotation failure, use `reopen_append()` only after the underlying filesystem condition has been resolved.
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when diagnosing incomplete direct file rotations.
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## File-sink-state-to-json
|
||||
|
||||
Convert `FileSinkState` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
Convert `FileSinkState` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn file_sink_state_to_json(state : FileSinkState) -> Json {}
|
||||
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn file_sink_state_to_json(state : FileSinkState) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the file sink state.
|
||||
- `JsonValue` - Structured JSON representation of the file sink state.
|
||||
|
||||
### Explanation
|
||||
|
||||
@@ -72,7 +72,7 @@ e.g.:
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when diagnostics consumers expect `Json`.
|
||||
1. Use this helper when diagnostics consumers expect `JsonValue`.
|
||||
|
||||
2. It pairs naturally with `FileSink::state()`, `RuntimeSink::file_state()`, and `ConfiguredLogger::file_state()`.
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ BitLogger API navigation.
|
||||
- [logger-with-timestamp.md](./logger-with-timestamp.md)
|
||||
- [logger-with-context-fields.md](./logger-with-context-fields.md)
|
||||
- [logger-bind.md](./logger-bind.md)
|
||||
- [logger-with-trace-context.md](./logger-with-trace-context.md)
|
||||
- [logger-with-filter.md](./logger-with-filter.md)
|
||||
- [logger-with-patch.md](./logger-with-patch.md)
|
||||
- [logger-with-queue.md](./logger-with-queue.md)
|
||||
@@ -100,10 +99,6 @@ BitLogger API navigation.
|
||||
- [fields.md](./fields.md)
|
||||
- [text-formatter-config-type.md](./text-formatter-config-type.md)
|
||||
|
||||
## Observability
|
||||
|
||||
- [trace-context.md](./trace-context.md)
|
||||
|
||||
## Record and level
|
||||
|
||||
- [record.md](./record.md)
|
||||
@@ -271,7 +266,6 @@ BitLogger API navigation.
|
||||
- [async-logger-with-min-level.md](./async-logger-with-min-level.md)
|
||||
- [async-logger-with-timestamp.md](./async-logger-with-timestamp.md)
|
||||
- [async-logger-with-context-fields.md](./async-logger-with-context-fields.md)
|
||||
- [async-logger-with-trace-context.md](./async-logger-with-trace-context.md)
|
||||
- [async-logger-with-filter.md](./async-logger-with-filter.md)
|
||||
- [async-logger-with-patch.md](./async-logger-with-patch.md)
|
||||
|
||||
@@ -328,7 +322,6 @@ BitLogger API navigation.
|
||||
- [library-logger-child.md](./library-logger-child.md)
|
||||
- [library-logger-with-context-fields.md](./library-logger-with-context-fields.md)
|
||||
- [library-logger-bind.md](./library-logger-bind.md)
|
||||
- [library-logger-with-trace-context.md](./library-logger-with-trace-context.md)
|
||||
- [library-logger-is-enabled.md](./library-logger-is-enabled.md)
|
||||
- [library-logger-log.md](./library-logger-log.md)
|
||||
- [library-logger-info.md](./library-logger-info.md)
|
||||
@@ -348,7 +341,6 @@ BitLogger API navigation.
|
||||
- [library-async-logger-child.md](./library-async-logger-child.md)
|
||||
- [library-async-logger-with-context-fields.md](./library-async-logger-with-context-fields.md)
|
||||
- [library-async-logger-bind.md](./library-async-logger-bind.md)
|
||||
- [library-async-logger-with-trace-context.md](./library-async-logger-with-trace-context.md)
|
||||
- [library-async-logger-is-enabled.md](./library-async-logger-is-enabled.md)
|
||||
- [library-async-logger-log.md](./library-async-logger-log.md)
|
||||
- [library-async-logger-info.md](./library-async-logger-info.md)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: library-async-logger-with-trace-context
|
||||
group: api
|
||||
category: facade
|
||||
update-time: 20260719
|
||||
description: Bind TraceContext fields through the restricted LibraryAsyncLogger facade.
|
||||
key-word:
|
||||
- library
|
||||
- async
|
||||
- trace
|
||||
---
|
||||
|
||||
## Library-async-logger-with-trace-context
|
||||
|
||||
`LibraryAsyncLogger::with_trace_context(...)` returns a derived library facade with `trace_id`, `span_id`, `trace_flags`, and optional `trace_state` attached to each later record. It does not start a worker or change lifecycle state.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: library-logger-with-trace-context
|
||||
group: api
|
||||
category: facade
|
||||
update-time: 20260719
|
||||
description: Bind TraceContext fields through the restricted LibraryLogger facade.
|
||||
key-word:
|
||||
- library
|
||||
- trace
|
||||
- context
|
||||
---
|
||||
|
||||
## Library-logger-with-trace-context
|
||||
|
||||
```moonbit
|
||||
let logger = LibraryLogger::new(console_sink())
|
||||
.with_trace_context(trace_context("trace-1", "span-1"))
|
||||
```
|
||||
|
||||
The derived facade preserves its library-facing API while wrapping the sink with context fields. Use `to_logger()` only when broader composition is required.
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Logger-config-to-json
|
||||
|
||||
Convert a typed `LoggerConfig` into a `Json`. This helper is the structured export path when config should be persisted, inspected, or embedded into larger JSON payloads.
|
||||
Convert a typed `LoggerConfig` into a `JsonValue`. This helper is the structured export path when config should be persisted, inspected, or embedded into larger JSON payloads.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn logger_config_to_json(config : LoggerConfig) -> Json {}
|
||||
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn logger_config_to_json(config : LoggerConfig) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - JSON representation of the logger config.
|
||||
- `JsonValue` - JSON representation of the logger config.
|
||||
|
||||
### Explanation
|
||||
|
||||
@@ -73,5 +73,5 @@ e.g.:
|
||||
|
||||
1. Use this helper when you need a reusable JSON value rather than a final JSON string.
|
||||
|
||||
2. Use `stringify_logger_config(...)` when the next consumer expects JSON text instead of `Json`.
|
||||
2. Use `stringify_logger_config(...)` when the next consumer expects JSON text instead of `JsonValue`.
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: logger-with-trace-context
|
||||
group: api
|
||||
category: logger
|
||||
update-time: 20260719
|
||||
description: Bind a TraceContext to every record from a synchronous Logger.
|
||||
key-word:
|
||||
- logger
|
||||
- trace
|
||||
- context
|
||||
---
|
||||
|
||||
## Logger-with-trace-context
|
||||
|
||||
```moonbit
|
||||
pub fn[S] Logger::with_trace_context(
|
||||
self : Logger[S],
|
||||
context : TraceContext,
|
||||
) -> Logger[ContextSink[S]]
|
||||
```
|
||||
|
||||
Returns a derived logger that prepends the context fields to every record. The original logger is unchanged. This is equivalent to `with_context_fields(context.as_fields())`, while making trace intent explicit.
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Queue-config-to-json
|
||||
|
||||
Convert a typed `QueueConfig` into a `Json`. This helper is the structured export path for synchronous queue wrapper configuration when callers want machine-readable config output.
|
||||
Convert a typed `QueueConfig` into a `JsonValue`. This helper is the structured export path for synchronous queue wrapper configuration when callers want machine-readable config output.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn queue_config_to_json(queue : QueueConfig) -> Json {}
|
||||
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn queue_config_to_json(queue : QueueConfig) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the queue config.
|
||||
- `JsonValue` - Structured JSON representation of the queue config.
|
||||
|
||||
### Explanation
|
||||
|
||||
@@ -74,5 +74,5 @@ e.g.:
|
||||
|
||||
1. Use this helper when you need a reusable JSON value rather than a final JSON string.
|
||||
|
||||
2. Use `stringify_queue_config(...)` when the next consumer expects text instead of `Json`.
|
||||
2. Use `stringify_queue_config(...)` when the next consumer expects text instead of `JsonValue`.
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Runtime-file-state-to-json
|
||||
|
||||
Convert `RuntimeFileState` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
Convert `RuntimeFileState` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {}
|
||||
pub fn runtime_file_state_to_json(state : RuntimeFileState) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the runtime file state.
|
||||
- `JsonValue` - Structured JSON representation of the runtime file state.
|
||||
|
||||
### Explanation
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Sink-config-to-json
|
||||
|
||||
Convert `SinkConfig` into a `Json`. This helper is used directly for sink export and indirectly when exporting `LoggerConfig`.
|
||||
Convert `SinkConfig` into a `JsonValue`. This helper is used directly for sink export and indirectly when exporting `LoggerConfig`.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn sink_config_to_json(config : SinkConfig) -> Json {}
|
||||
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn sink_config_to_json(config : SinkConfig) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - JSON representation of the sink configuration.
|
||||
- `JsonValue` - JSON representation of the sink configuration.
|
||||
|
||||
### Explanation
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` returns compact JSON.
|
||||
- `pretty=true` returns indented JSON for human inspection.
|
||||
- This helper is built on top of `async_logger_build_config_to_json(...)`.
|
||||
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured async build-config export helper.
|
||||
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured async build-config export helper.
|
||||
- The output keeps `logger` and `async_config` as separate sections, matching supported parser input.
|
||||
- The serialized `logger` section preserves the full `LoggerConfig` shape, even though `build_async_text_logger(...)` later consumes only the selected text-oriented subset of that logger config.
|
||||
- That includes preserving whatever `logger.sink.kind` text is present in the config, even though the later text-specific builder path still ignores that sink-kind branch and constructs `FormattedConsoleSink` from `logger.sink.text_formatter`.
|
||||
@@ -80,7 +80,7 @@ In this example, compact JSON is returned without extra formatting.
|
||||
### Error Case
|
||||
|
||||
e.g.:
|
||||
- If callers need a `Json` for further composition, they should use `async_logger_build_config_to_json(...)` instead.
|
||||
- If callers need a `JsonValue` for further composition, they should use `async_logger_build_config_to_json(...)` instead.
|
||||
|
||||
- If only one layer of config is required, this helper may be broader than necessary.
|
||||
|
||||
@@ -96,7 +96,7 @@ e.g.:
|
||||
|
||||
4. In particular, serialized `logger.sink.kind` text is descriptive config data, not a guarantee that the text-specific builder path will branch on that sink kind later.
|
||||
|
||||
5. Use `async_logger_build_config_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
|
||||
5. Use `async_logger_build_config_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
|
||||
|
||||
6. After parsing, the same text can also feed the application or library facade builders; string export preserves one shared build-config shape, not a direct-builder-only route.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` returns compact JSON suitable for transport and snapshots.
|
||||
- `pretty=true` returns indented JSON for humans.
|
||||
- This helper is built on top of `async_logger_config_to_json(...)`.
|
||||
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured async-config export helper.
|
||||
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured async-config export helper.
|
||||
- The exported text follows the supported async config schema rather than internal queue implementation details.
|
||||
- Canonical policy labels such as `DropNewest` and `Never` are emitted even though the parser also accepts aliases like `DropLatest` and `None`.
|
||||
|
||||
@@ -68,7 +68,7 @@ In this example, compact JSON is returned without extra formatting.
|
||||
### Error Case
|
||||
|
||||
e.g.:
|
||||
- If callers need a `Json` for composition, they should use `async_logger_config_to_json(...)` instead.
|
||||
- If callers need a `JsonValue` for composition, they should use `async_logger_config_to_json(...)` instead.
|
||||
|
||||
- If invalid constructor inputs were normalized earlier, the resulting text contains the normalized config values.
|
||||
|
||||
@@ -78,5 +78,5 @@ e.g.:
|
||||
|
||||
2. If negative `max_pending` semantics matter, remember that the serialized text preserves the config value while runtime queue creation later clamps the queue limit to `0`.
|
||||
|
||||
3. Use `async_logger_config_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
|
||||
3. Use `async_logger_config_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` returns compact JSON.
|
||||
- `pretty=true` returns indented JSON suitable for human diagnostics.
|
||||
- This helper is built on top of `async_logger_state_to_json(...)`.
|
||||
- Internally it stringifies the same JSON snapshot shape returned by the public export helper, using `value.stringify(...)` or `value.stringify(indent=2)`.
|
||||
- Internally it stringifies the same JSON snapshot shape returned by the public export helper, using `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`.
|
||||
- The compact form matches snapshots such as `{"runtime":{"mode":"native_worker","background_worker":true},"phase":"ready","pending_count":0,...}`.
|
||||
- String output is convenient for logs, CLI output, and startup diagnostics.
|
||||
- The serializer does not care whether the input came from a live logger read or a synthetic `AsyncLoggerState::new(...)` call.
|
||||
@@ -83,7 +83,7 @@ e.g.:
|
||||
|
||||
2. The compact output shape is already locked by the async runtime snapshot tests, so it is suitable for stable diagnostics and assertions.
|
||||
|
||||
3. Use `async_logger_state_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
|
||||
3. Use `async_logger_state_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
|
||||
|
||||
4. Pair it with `AsyncLogger::state()` for current logger diagnostics, or with `AsyncLoggerState::new(...)` when exporting a manual snapshot built by tests or adapters.
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` returns compact JSON.
|
||||
- `pretty=true` returns indented JSON for human diagnostics.
|
||||
- This helper is built on top of `async_runtime_state_to_json(...)`.
|
||||
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured runtime-state export helper.
|
||||
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured runtime-state export helper.
|
||||
- The compact form matches the tested snapshot shape such as `{"mode":"native_worker","background_worker":true}`.
|
||||
- It is well-suited for startup banners, support reports, and target capability logs.
|
||||
|
||||
@@ -79,5 +79,5 @@ e.g.:
|
||||
|
||||
2. This helper is the direct text form of the same two-field runtime snapshot exported by `async_runtime_state_to_json(...)`.
|
||||
|
||||
3. Use `async_runtime_state_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
|
||||
3. Use `async_runtime_state_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ In this example, compact JSON is produced without extra formatting logic.
|
||||
### Error Case
|
||||
|
||||
e.g.:
|
||||
- If callers need a `Json` for composition rather than text, `file_sink_policy_to_json(...)` is the better API.
|
||||
- If callers need a `JsonValue` for composition rather than text, `file_sink_policy_to_json(...)` is the better API.
|
||||
|
||||
- If rotation is disabled, the output still includes `rotation` as `null`.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` produces compact JSON.
|
||||
- `pretty=true` produces indented human-readable JSON.
|
||||
- This helper builds on top of `logger_config_to_json(...)`.
|
||||
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured logger-config export helper.
|
||||
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured logger-config export helper.
|
||||
- Output is stable and suited for roundtrip config workflows.
|
||||
|
||||
### How to Use
|
||||
@@ -71,7 +71,7 @@ e.g.:
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when the next consumer expects JSON text instead of `Json`.
|
||||
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
|
||||
|
||||
2. Use `logger_config_to_json(...)` when you still need to embed the config inside a larger JSON object before final stringification.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` returns compact JSON suitable for logs and snapshots.
|
||||
- `pretty=true` returns indented JSON for human inspection.
|
||||
- This helper is built on top of `queue_config_to_json(...)`.
|
||||
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured export helper.
|
||||
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured export helper.
|
||||
- The resulting text follows the same queue schema accepted by config parsing flows.
|
||||
|
||||
### How to Use
|
||||
@@ -67,13 +67,13 @@ In this example, compact JSON is returned without extra formatting.
|
||||
### Error Case
|
||||
|
||||
e.g.:
|
||||
- If callers need a `Json` for further composition, this API is the wrong layer and `queue_config_to_json(...)` should be used instead.
|
||||
- If callers need a `JsonValue` for further composition, this API is the wrong layer and `queue_config_to_json(...)` should be used instead.
|
||||
|
||||
- If queue policy is too aggressive for workload burst size, serialization still succeeds because this helper only exports config.
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when the next consumer expects JSON text instead of `Json`.
|
||||
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
|
||||
|
||||
2. Use `queue_config_to_json(...)` when you still need to embed the queue config inside a larger JSON object before final stringification.
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ In this example, compact JSON is returned without extra formatting logic.
|
||||
### Error Case
|
||||
|
||||
e.g.:
|
||||
- If callers need a `Json` rather than text, `runtime_file_state_to_json(...)` is the better API.
|
||||
- If callers need a `JsonValue` rather than text, `runtime_file_state_to_json(...)` is the better API.
|
||||
|
||||
- If queue wrapping is not relevant, `stringify_file_sink_state(...)` may be the simpler API.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` gives compact JSON.
|
||||
- `pretty=true` gives indented output.
|
||||
- This helper builds on top of `sink_config_to_json(...)`.
|
||||
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured sink export helper.
|
||||
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured sink export helper.
|
||||
- It is useful when examples or generated docs want to show only sink-specific config.
|
||||
|
||||
### How to Use
|
||||
@@ -71,7 +71,7 @@ e.g.:
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when the next consumer expects JSON text instead of `Json`.
|
||||
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
|
||||
|
||||
2. Use `sink_config_to_json(...)` when you still need to embed the sink config inside a larger JSON object before final stringification.
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
|
||||
- `pretty=false` returns compact JSON.
|
||||
- `pretty=true` returns indented JSON for humans.
|
||||
- This helper is built on top of `text_formatter_config_to_json(...)`.
|
||||
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured formatter export helper.
|
||||
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured formatter export helper.
|
||||
- The output preserves the supported formatter config schema instead of any runtime-only formatter instance details.
|
||||
|
||||
### How to Use
|
||||
@@ -76,7 +76,7 @@ e.g.:
|
||||
|
||||
### Notes
|
||||
|
||||
1. Use this helper when the next consumer expects JSON text instead of `Json`.
|
||||
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
|
||||
|
||||
2. Use `text_formatter_config_to_json(...)` when you still need to embed formatter config inside a larger JSON object before final stringification.
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ key-word:
|
||||
|
||||
## Text-formatter-config-to-json
|
||||
|
||||
Convert a typed `TextFormatterConfig` into a `Json`. This helper exports formatter toggles, separators, color settings, markup behavior, and optional style tags in a machine-readable form.
|
||||
Convert a typed `TextFormatterConfig` into a `JsonValue`. This helper exports formatter toggles, separators, color settings, markup behavior, and optional style tags in a machine-readable form.
|
||||
|
||||
### Interface
|
||||
|
||||
```moonbit
|
||||
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {}
|
||||
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_parser.JsonValue {}
|
||||
```
|
||||
|
||||
#### input
|
||||
@@ -27,7 +27,7 @@ pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {}
|
||||
|
||||
#### output
|
||||
|
||||
- `Json` - Structured JSON representation of the formatter config.
|
||||
- `JsonValue` - Structured JSON representation of the formatter config.
|
||||
|
||||
### Explanation
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: trace-context
|
||||
group: api
|
||||
category: observability
|
||||
update-time: 20260719
|
||||
description: Represent and bind distributed trace metadata using stable structured-log fields.
|
||||
key-word:
|
||||
- trace
|
||||
- context
|
||||
- observability
|
||||
---
|
||||
|
||||
## Trace-context
|
||||
|
||||
`TraceContext` carries the standard log fields used to correlate records with distributed traces: `trace_id`, `span_id`, `trace_flags`, and optional `trace_state`.
|
||||
|
||||
```moonbit
|
||||
let context = trace_context(
|
||||
"4bf92f3577b34da6a3ce929d0e0e4736",
|
||||
"00f067aa0ba902b7",
|
||||
trace_state="vendor=value",
|
||||
)
|
||||
```
|
||||
|
||||
Use `context.as_fields()` when integrating with a custom carrier, or pass the value to a logger's `with_trace_context(...)` helper. BitLogger does not parse HTTP headers or export OTLP data; an application-owned tracing library remains responsible for propagation and sampling.
|
||||
@@ -1,26 +0,0 @@
|
||||
## BitLogger Update Changes
|
||||
|
||||
version 1.1.1(0.7.1)
|
||||
|
||||
### Toolchain
|
||||
|
||||
- fix: migrate runnable example packages to `pkgtype(kind: "executable")` for MoonBit 0.10.4 formatter compatibility
|
||||
- fix: replace ambiguous empty map literals with `Map([])` so `--deny-warn` checks remain clean on MoonBit 0.10.4
|
||||
|
||||
### Test
|
||||
|
||||
- test: write file-backed test output under `logs/` instead of the repository root
|
||||
- test: keep the tracked `logs/.gitkeep` directory available for clean test checkouts
|
||||
|
||||
### Docs
|
||||
|
||||
- docs: update installation snippets to `Nanaloveyuki/BitLogger@0.7.1`
|
||||
- docs: add this 1.1.1(0.7.1) release entry to the change index
|
||||
|
||||
### Verification
|
||||
|
||||
- verify: pass formatting, warning-denied checks, synchronous and async cross-target tests, examples, and coverage on MoonBit 0.10.4
|
||||
|
||||
### Notes
|
||||
|
||||
- this is a compatibility and test-output maintenance release with no public API changes
|
||||
@@ -1,21 +0,0 @@
|
||||
## BitLogger Update Changes
|
||||
|
||||
version 1.1.2(0.7.2)
|
||||
|
||||
### JSON
|
||||
|
||||
- refactor: replace `maria/json_parser` with the official `moonbitlang/core/json` implementation
|
||||
- refactor: preserve existing serialized JSON field shapes while JSON conversion APIs now return the built-in `Json` type
|
||||
|
||||
### Docs
|
||||
|
||||
- docs: update installation snippets to `Nanaloveyuki/BitLogger@0.7.2`
|
||||
- docs: add this 1.1.2(0.7.2) release entry to the change index
|
||||
|
||||
### Verification
|
||||
|
||||
- verify: pass formatting, warning-denied checks, and default, native, JavaScript, WebAssembly, and Wasm-GC test targets
|
||||
|
||||
### Notes
|
||||
|
||||
- LLVM was not tested because the local MoonBit toolchain is missing the experimental LLVM core bundle required by that target
|
||||
@@ -1,19 +0,0 @@
|
||||
## BitLogger Update Changes
|
||||
|
||||
version 1.1.3(0.7.3)
|
||||
|
||||
### Changes
|
||||
|
||||
- Add wasm CI checks and tests for the sync and async packages.
|
||||
- Add TraceContext binding for sync and async logger facades.
|
||||
- Add native async file lifecycle integration coverage and reproducible benchmarks.
|
||||
|
||||
### Verification
|
||||
|
||||
- Pass moon fmt --check, warning-denied checks, and tests across native, wasm, wasm-gc, and js.
|
||||
- Build the VitePress documentation site and run the native benchmark suite.
|
||||
|
||||
### Notes
|
||||
|
||||
- LLVM remains experimental and was not verified locally.
|
||||
- TraceContext maps structured fields only; HTTP propagation and OTLP export remain application-owned.
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
Versioned BitLogger change summaries.
|
||||
|
||||
- [1.1.3](./1.1.3(0.7.3).md)
|
||||
- [1.1.2](./1.1.2(0.7.2).md)
|
||||
- [1.1.1](./1.1.1(0.7.1).md)
|
||||
- [1.1.0](./1.1.0(0.7.0).md)
|
||||
- [1.0.1](./1.0.1(0.6.1).md)
|
||||
- [1.0.0](./1.0.0(0.6.0).md)
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# Async Logger Lifecycle
|
||||
|
||||
Use the async package when a native application needs logging to run through an async queue. The library supports broader targets, but the shipped `async fn main` example is native-focused.
|
||||
|
||||
## Import Both Packages
|
||||
|
||||
```moonbit
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
"Nanaloveyuki/BitLogger/src-async" @async_log,
|
||||
"moonbitlang/async",
|
||||
}
|
||||
```
|
||||
|
||||
## Build, Run, Then Shut Down
|
||||
|
||||
```moonbit
|
||||
async fn main {
|
||||
let raw = "{\"logger\":{\"min_level\":\"info\",\"target\":\"service.async\",\"sink\":{\"kind\":\"text_console\"}},\"async_config\":{\"max_pending\":64,\"overflow\":\"DropOldest\",\"max_batch\":16,\"linger_ms\":5,\"flush\":\"Batch\"}}"
|
||||
let config = @async_log.parse_async_logger_build_config_text(raw) catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
let logger = @async_log.build_async_logger(config)
|
||||
|
||||
@async.with_task_group(group => {
|
||||
group.spawn_bg(allow_failure=true, () => logger.run())
|
||||
logger.info("worker started", fields=[@log.field("mode", "async")])
|
||||
logger.shutdown()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
`run()` owns the worker loop. Start it before producing records, then call `shutdown()` so pending work can follow the configured flush policy.
|
||||
|
||||
## Run The Repository Example
|
||||
|
||||
```bash
|
||||
moon run examples/async_basic --target native
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Inspect queue policy and flush choices in [Queueing](../extend/queue.md).
|
||||
- Reference: [`parse_async_logger_build_config_text(...)`](../api/parse-async-logger-build-config-text.md), [`build_async_logger(...)`](../api/build-async-logger.md), [`run()`](../api/async-logger-run.md), and [`shutdown()`](../api/async-logger-shutdown.md).
|
||||
@@ -1,35 +0,0 @@
|
||||
# Configuration-Driven Logging
|
||||
|
||||
Use configuration when deployment should select levels, output, formatting, or queue behavior without changing application code.
|
||||
|
||||
## Parse, Build, Then Log
|
||||
|
||||
```moonbit
|
||||
let raw = "{\"min_level\":\"info\",\"target\":\"service\",\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \"}},\"queue\":{\"max_pending\":64,\"overflow\":\"DropOldest\"}}"
|
||||
|
||||
let config = @log.parse_logger_config_text(raw) catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
println("invalid logger configuration")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let logger = @log.build_logger(config)
|
||||
logger.info("configured logger ready")
|
||||
ignore(logger.flush())
|
||||
```
|
||||
|
||||
Parsing validates the data before runtime construction. Keep the raw configuration outside application code in a real deployment, but handle parsing errors at the boundary where configuration enters the process.
|
||||
|
||||
## Run The Repository Example
|
||||
|
||||
```bash
|
||||
moon run examples/config_build
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Use [Queueing](../extend/queue.md) to choose overflow behavior deliberately.
|
||||
- Use [Async lifecycle](./async.md) when the queue needs a background worker.
|
||||
- Reference: [`parse_logger_config_text(...)`](../api/parse-logger-config-text.md), [`build_logger(...)`](../api/build-logger.md), and [`with_queue(...)`](../api/with-queue.md).
|
||||
@@ -1,51 +0,0 @@
|
||||
# Console And Structured Fields
|
||||
|
||||
Use this flow for command-line tools and services that need a human-readable starting point. A record always has a level, target, message, and optional fields.
|
||||
|
||||
## Install And Import
|
||||
|
||||
```bash
|
||||
moon add Nanaloveyuki/BitLogger@0.7.3
|
||||
```
|
||||
|
||||
```moonbit
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
}
|
||||
```
|
||||
|
||||
## Build A Console Logger
|
||||
|
||||
```moonbit
|
||||
fn main {
|
||||
let logger = @log.build_logger(
|
||||
@log.console(min_level=@log.Level::Info, target="service.http"),
|
||||
)
|
||||
|
||||
logger.info("listening", fields=[
|
||||
@log.field("port", "8080"),
|
||||
@log.field("environment", "development"),
|
||||
])
|
||||
logger.warn("slow request", fields=[@log.field("path", "/health")])
|
||||
}
|
||||
```
|
||||
|
||||
`min_level` filters records before they reach the sink. `target` identifies the subsystem, while fields carry queryable context without interpolating it into the message.
|
||||
|
||||
## Switch To JSON
|
||||
|
||||
Keep the logging calls unchanged and replace the preset:
|
||||
|
||||
```moonbit
|
||||
let logger = @log.build_logger(
|
||||
@log.json_console(min_level=@log.Level::Info, target="service.http"),
|
||||
)
|
||||
```
|
||||
|
||||
This is the preferred output for a collector that consumes one structured record per line.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Use [Text formatting](../extend/formatting.md) for a custom terminal format.
|
||||
- Use [Configuration](./config.md) when levels, targets, and sinks come from JSON.
|
||||
- Reference: [`console(...)`](../api/console.md), [`json_console(...)`](../api/json-console.md), [`field(...)`](../api/field.md), and [`build_logger(...)`](../api/build-logger.md).
|
||||
@@ -1,51 +0,0 @@
|
||||
# File Output And Rotation
|
||||
|
||||
File output is for native applications that need local persistence. Treat it as a native-only capability and keep a console fallback for portable code.
|
||||
|
||||
## Check The Capability
|
||||
|
||||
```moonbit
|
||||
if !@log.native_files_supported() {
|
||||
println("file logging is unavailable on this target")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
## Build A Rotating File Logger
|
||||
|
||||
```moonbit
|
||||
let config = @log.with_file_rotation(
|
||||
@log.file(
|
||||
"service.log",
|
||||
min_level=@log.Level::Info,
|
||||
target="service.file",
|
||||
auto_flush=true,
|
||||
) catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
return
|
||||
}
|
||||
},
|
||||
1024 * 1024,
|
||||
max_backups=3,
|
||||
)
|
||||
|
||||
let logger = @log.build_logger(config)
|
||||
logger.info("file logger ready")
|
||||
ignore(logger.flush())
|
||||
ignore(logger.file_close())
|
||||
```
|
||||
|
||||
The rotation limit is the size of the active file in bytes. `max_backups=3` retains the rotated backup chain. Always close a file-backed logger during orderly application shutdown.
|
||||
|
||||
## Run The Repository Example
|
||||
|
||||
```bash
|
||||
moon run examples/file_rotation --target native
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Add a bounded queue with [Queueing](../extend/queue.md) when bursts should not write synchronously.
|
||||
- Read [Target boundaries](../extend/targets.md) before sharing the same logging setup with web targets.
|
||||
- Reference: [`file(...)`](../api/file.md), [`with_file_rotation(...)`](../api/with-file-rotation.md), and [`file_close(...)`](../api/configured-logger-file-close.md).
|
||||
@@ -1,24 +0,0 @@
|
||||
# Examples
|
||||
|
||||
Examples are the primary BitLogger learning path. Each page starts from an application concern, shows the complete minimum code, and then points to the API reference for exact contracts.
|
||||
|
||||
## Choose A Flow
|
||||
|
||||
| Need | Start here | Then extend with |
|
||||
| --- | --- | --- |
|
||||
| Print structured logs to a terminal | [Console and fields](./console.md) | [Text formatting](../extend/formatting.md) |
|
||||
| Keep local logs with bounded disk use | [File rotation](./file-rotation.md) | [Target boundaries](../extend/targets.md) |
|
||||
| Build logging from app configuration | [Configuration](./config.md) | [Queueing](../extend/queue.md) |
|
||||
| Avoid blocking a native application on writes | [Async lifecycle](./async.md) | [Sink composition](../extend/composition.md) |
|
||||
|
||||
## Repository Examples
|
||||
|
||||
The runnable sources live under `examples/`. The guides below explain their intended use instead of duplicating every API detail:
|
||||
|
||||
- `examples/console_basic` for terminal and JSON records
|
||||
- `examples/config_build` for parsed configuration
|
||||
- `examples/file_rotation` for native file output
|
||||
- `examples/async_basic` for the async lifecycle
|
||||
- `examples/presets`, `examples/text_formatter`, and `examples/style_tags` for follow-on customization
|
||||
|
||||
Use the [API reference](../api/index.md) when a guide links to a symbol and you need its full signature or edge-case behavior.
|
||||
@@ -1,42 +0,0 @@
|
||||
# Sink Composition
|
||||
|
||||
Presets cover common console and file configurations. Build a `Logger::new(...)` directly when records need routing or multiple outputs.
|
||||
|
||||
## Send A Record To Two Destinations
|
||||
|
||||
```moonbit
|
||||
let logger = @log.Logger::new(
|
||||
@log.fanout_sink(@log.console_sink(), @log.json_console_sink()),
|
||||
min_level=@log.Level::Info,
|
||||
target="service",
|
||||
)
|
||||
|
||||
logger.info("request complete", fields=[@log.field("status", "200")])
|
||||
```
|
||||
|
||||
`fanout_sink(...)` writes the same record to every child sink. This keeps human-oriented terminal output and machine-oriented JSON output in one application path.
|
||||
|
||||
## Route Warnings Separately
|
||||
|
||||
```moonbit
|
||||
let logger = @log.Logger::new(
|
||||
@log.split_by_level(
|
||||
@log.callback_sink(fn(rec) {
|
||||
println("alert: \{rec.message}")
|
||||
}),
|
||||
@log.console_sink(),
|
||||
min_level=@log.Level::Warn,
|
||||
),
|
||||
min_level=@log.Level::Trace,
|
||||
target="service",
|
||||
)
|
||||
```
|
||||
|
||||
Use direct sink composition only when the routing behavior is part of the application design. A preset remains easier to audit when one sink is sufficient.
|
||||
|
||||
## API Reference
|
||||
|
||||
- [`Logger::new(...)`](../api/logger-new.md)
|
||||
- [`fanout_sink(...)`](../api/fanout-sink.md)
|
||||
- [`split_by_level(...)`](../api/split-by-level.md)
|
||||
- [`callback_sink(...)`](../api/callback-sink.md)
|
||||
@@ -1,44 +0,0 @@
|
||||
# Text Formatting And Style Tags
|
||||
|
||||
Formatting changes presentation while keeping the record's level, target, message, and fields intact. Use text output for people; use JSON output when another system parses the record.
|
||||
|
||||
## Customize The Text Shape
|
||||
|
||||
```moonbit
|
||||
let logger = @log.build_logger(
|
||||
@log.text_console(
|
||||
min_level=@log.Level::Info,
|
||||
target="service",
|
||||
text_formatter=@log.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
field_separator=",",
|
||||
template="[{level}] {target} {message} :: {fields}",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
logger.info("ready", fields=[@log.field("port", "8080")])
|
||||
```
|
||||
|
||||
The template controls the visible order. Keep fields separate from the message so they can still be rendered consistently across sinks.
|
||||
|
||||
## Add Named Styles
|
||||
|
||||
```moonbit
|
||||
let formatter = @log.text_formatter(
|
||||
show_timestamp=false,
|
||||
color_mode=@log.ColorMode::Always,
|
||||
).with_style_tags(
|
||||
@log.default_style_tag_registry()
|
||||
.set_tag("accent", fg=Some("#4cc9f0"), bold=true),
|
||||
)
|
||||
```
|
||||
|
||||
Style tags are terminal presentation metadata. Do not use them as the only way to encode operational meaning; preserve that meaning in level, target, and fields.
|
||||
|
||||
## API Reference
|
||||
|
||||
- [`text_console(...)`](../api/text-console.md)
|
||||
- [`TextFormatterConfig`](../api/text-formatter-config.md)
|
||||
- [`text_formatter(...)`](../api/text-formatter.md)
|
||||
- [`ColorMode`](../api/color-mode.md)
|
||||
@@ -1,15 +0,0 @@
|
||||
# Extend A Logger
|
||||
|
||||
Start from an [Example flow](../examples/index.md), then add one extension at a time. These pages explain when an abstraction is useful and which operational boundary it introduces.
|
||||
|
||||
## Extension Map
|
||||
|
||||
| Need | Extension | Main trade-off |
|
||||
| --- | --- | --- |
|
||||
| Absorb short output bursts | [Queueing](./queue.md) | You must choose overflow and flush behavior. |
|
||||
| Send records to multiple destinations or route by level | [Sink composition](./composition.md) | More explicit construction than presets. |
|
||||
| Control terminal appearance | [Text formatting](./formatting.md) | Formatting affects presentation, not record structure. |
|
||||
| Share code across native and web targets | [Target boundaries](./targets.md) | File and async runtime behavior differ by backend. |
|
||||
| Correlate logs with distributed requests | [Trace context](./observability.md) | Propagation and export stay application-owned. |
|
||||
|
||||
For exact function signatures, follow each page's links into the [API reference](../api/index.md).
|
||||
@@ -1,18 +0,0 @@
|
||||
# Trace Context
|
||||
|
||||
BitLogger keeps trace interoperability in its structured record model rather than owning HTTP propagation or a telemetry transport. Use the tracing library already selected by the application to extract a request context, then bind its identifiers to the logger.
|
||||
|
||||
```moonbit
|
||||
let request_logger = logger.with_trace_context(
|
||||
@log.trace_context(
|
||||
trace_id,
|
||||
span_id,
|
||||
trace_flags="01",
|
||||
trace_state=trace_state,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Every record from `request_logger` receives `trace_id`, `span_id`, `trace_flags`, and, when supplied, `trace_state`. The base logger is unchanged, so a child request cannot leak identifiers into unrelated work.
|
||||
|
||||
Use `context.as_fields()` for a custom logger facade. BitLogger intentionally does not parse `traceparent` headers, create spans, make sampling decisions, or export OTLP data; those concerns remain with the application tracing implementation.
|
||||
@@ -1,30 +0,0 @@
|
||||
# Queueing And Overflow
|
||||
|
||||
Use a queue when a short burst of log writes should not immediately reach the output sink. Queueing is explicit because loss behavior is an application decision.
|
||||
|
||||
## Add A Synchronous Queue
|
||||
|
||||
```moonbit
|
||||
let config = @log.with_queue(
|
||||
@log.text_console(target="service"),
|
||||
max_pending=128,
|
||||
overflow=@log.QueueOverflowPolicy::DropOldest,
|
||||
)
|
||||
let logger = @log.build_logger(config)
|
||||
|
||||
logger.info("queued record")
|
||||
ignore(logger.flush())
|
||||
```
|
||||
|
||||
`DropOldest` preserves the newest operational information when the queue is full. Use `DropNewest` when preserving earlier records matters more. Calling `flush()` at a shutdown boundary makes the pending-record policy visible in application code.
|
||||
|
||||
## When To Use Async Instead
|
||||
|
||||
The synchronous queue is configuration around a normal runtime logger. When a native async application needs a worker lifecycle and batching, follow [Async logger lifecycle](../examples/async.md) instead.
|
||||
|
||||
## API Reference
|
||||
|
||||
- [`with_queue(...)`](../api/with-queue.md)
|
||||
- [`QueueConfig`](../api/queue-config.md)
|
||||
- [`QueueOverflowPolicy`](../api/queue-overflow-policy.md)
|
||||
- [`flush()`](../api/configured-logger-flush.md)
|
||||
@@ -1,33 +0,0 @@
|
||||
# Target Boundaries
|
||||
|
||||
BitLogger keeps a portable structured logging surface, but native file output and async worker behavior are target-sensitive. Keep those boundaries in application code instead of assuming every sink behaves identically everywhere.
|
||||
|
||||
## Portable Default
|
||||
|
||||
`console(...)`, `json_console(...)`, structured fields, filters, patches, and the ordinary logger surface are the appropriate starting point for code shared across targets.
|
||||
|
||||
## Native-Only File Output
|
||||
|
||||
```moonbit
|
||||
if @log.native_files_supported() {
|
||||
let logger = @log.build_logger(@log.file("service.log") catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
logger.info("file output enabled")
|
||||
}
|
||||
```
|
||||
|
||||
File sinks need native filesystem support. Do not construct a file-only configuration unconditionally in code intended for web targets.
|
||||
|
||||
## Async Library Versus Async Entry Point
|
||||
|
||||
The async library exposes a compatibility surface across declared targets, while an executable `async fn main` still has stricter entry-point support. Keep a native-only executable example separate from portable library code.
|
||||
|
||||
## API Reference
|
||||
|
||||
- [`native_files_supported()`](../api/native-files-supported.md)
|
||||
- [Target verification](../api/target-verification.md)
|
||||
- [Async logger lifecycle](../examples/async.md)
|
||||
+16
-14
@@ -4,29 +4,31 @@ layout: home
|
||||
hero:
|
||||
name: BitLogger
|
||||
text: Structured logging for MoonBit
|
||||
tagline: Start with a runnable logging flow, then extend it with precise API reference.
|
||||
tagline: API reference, examples, and release notes for BitLogger.
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Start Examples
|
||||
link: /examples/
|
||||
text: Open API Reference
|
||||
link: /api/
|
||||
- theme: alt
|
||||
text: Extend A Logger
|
||||
link: /extend/
|
||||
text: Read Release Notes
|
||||
link: /changes/
|
||||
- theme: alt
|
||||
text: Mooncake Package
|
||||
link: https://mooncakes.io/docs/Nanaloveyuki/BitLogger
|
||||
|
||||
features:
|
||||
- title: Examples first
|
||||
details: Follow complete console, file, config, and async flows before looking up individual symbols.
|
||||
- title: Extend deliberately
|
||||
details: Add queues, custom sink graphs, formatting, and target-specific behavior only when the base flow needs them.
|
||||
- title: API as reference
|
||||
details: Use the one-symbol-per-page API reference for exact signatures, contracts, and edge cases.
|
||||
- title: Structured logging
|
||||
details: Levels, targets, message fields, and configurable text or JSON output.
|
||||
- title: Sync and async paths
|
||||
details: API reference covers both the main package and the async package surface.
|
||||
- title: Config and runtime helpers
|
||||
details: Browse builders, presets, file helpers, queue helpers, and verification notes quickly.
|
||||
---
|
||||
|
||||
## Start Here
|
||||
|
||||
1. Read [Examples](./examples/index.md) for an end-to-end path from installation to a running logger.
|
||||
2. Read [Extend](./extend/index.md) when the base console flow needs queueing, custom routing, formatting, files, or target-aware behavior.
|
||||
3. Use [API Reference](./api/index.md) for exact contracts and [Release Notes](./changes/index.md) for version history.
|
||||
<HomeDocHub />
|
||||
|
||||
- Use [API Reference](./api/index.md) when you want the canonical one-file-per-public-API docs.
|
||||
- Use [Release Notes](./changes/index.md) when you want version-scoped changes and publish-facing summaries.
|
||||
- Use [Chinese README](https://github.com/Nanaloveyuki/BitLogger/blob/main/README.md) or [English README](./README-en.md) when you want a shorter project overview first.
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# 异步生命周期 API
|
||||
|
||||
异步 API 从 combined config 构建 Logger,再以单 worker 生命周期处理待写入记录。可执行 `async fn main` 仍应按目标平台边界使用。
|
||||
|
||||
## 解析与构建
|
||||
|
||||
```moonbit
|
||||
pub fn parse_async_logger_build_config_text(
|
||||
input : String,
|
||||
) -> AsyncLoggerBuildConfig raise
|
||||
|
||||
pub fn build_async_logger(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> AsyncLogger[RuntimeSink]
|
||||
```
|
||||
|
||||
解析函数同时验证 `logger` 与 `async_config` 两部分;缺少任一部分时使用对应默认配置。构建函数先复用同步 `build_logger(config.logger)` 路径,再包裹异步层,因此保留同步 sink、文件与可选同步队列的运行时语义。
|
||||
|
||||
```moonbit
|
||||
let config = @async_log.parse_async_logger_build_config_text(raw) catch {
|
||||
err => { ignore(err); return }
|
||||
}
|
||||
let logger = @async_log.build_async_logger(config)
|
||||
```
|
||||
|
||||
## `run()`
|
||||
|
||||
```moonbit
|
||||
pub async fn AsyncLogger::run(self : AsyncLogger[S]) -> Unit
|
||||
```
|
||||
|
||||
启动 drain worker。单个 Logger 只能同时拥有一个 worker;已经运行时再次调用会失败,关闭后的 Logger 也不能重新启动。worker 正常在队列关闭时退出;worker 失败时会记录状态并将错误向上传递。
|
||||
|
||||
## `shutdown()`
|
||||
|
||||
```moonbit
|
||||
pub async fn AsyncLogger::shutdown(
|
||||
self : AsyncLogger[S],
|
||||
clear? : Bool = false,
|
||||
) -> Unit
|
||||
```
|
||||
|
||||
`clear=false` 先等待 idle 再关闭;`clear=true` 立即关闭并放弃待处理记录。两条路径都会等待活动 worker 退出。没有运行 worker 且仍有待处理记录时,`clear=false` 可能无法推进,因此应用应保证先启动 `run()`。
|
||||
|
||||
## 英文原始 API
|
||||
|
||||
- [`parse_async_logger_build_config_text(...)`](../../api/parse-async-logger-build-config-text.md)
|
||||
- [`build_async_logger(...)`](../../api/build-async-logger.md)
|
||||
- [`run()`](../../api/async-logger-run.md)
|
||||
- [`shutdown()`](../../api/async-logger-shutdown.md)
|
||||
@@ -1,55 +0,0 @@
|
||||
# 基础记录 API
|
||||
|
||||
控制台示例依赖以下四个入口:先用 preset 描述输出,再由 `build_logger(...)` 构建运行时 Logger,最后用 `field(...)` 附加结构化上下文。
|
||||
|
||||
## `console(...)` 与 `json_console(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn console(
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
) -> LoggerConfig
|
||||
|
||||
pub fn json_console(
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
) -> LoggerConfig
|
||||
```
|
||||
|
||||
两者都只生成 `LoggerConfig`,不会直接创建运行时 Logger。`console(...)` 适合人读输出,`json_console(...)` 适合按行采集的机器读取输出;默认都不带队列。
|
||||
|
||||
## `field(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn field(key : String, value : String) -> Field
|
||||
```
|
||||
|
||||
创建一个结构化字段。它保留原始 key/value,不做去重、归一化或校验;应使用稳定字段名,便于下游筛选。
|
||||
|
||||
```moonbit
|
||||
logger.info("accepted", fields=[@log.field("user", "alice")])
|
||||
```
|
||||
|
||||
## `build_logger(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn build_logger(config : LoggerConfig) -> ConfiguredLogger
|
||||
```
|
||||
|
||||
这是同步 config 到运行时的桥梁:它先按 `config.sink` 构建 `RuntimeSink`,再应用可选的 `config.queue`。返回值仍保留普通日志方法,以及适用时的队列和文件控制方法。
|
||||
|
||||
```moonbit
|
||||
let logger = @log.build_logger(
|
||||
@log.console(min_level=@log.Level::Info, target="service"),
|
||||
)
|
||||
logger.info("ready", fields=[@log.field("port", "8080")])
|
||||
```
|
||||
|
||||
## 英文原始 API
|
||||
|
||||
- [`console(...)`](../../api/console.md)
|
||||
- [`json_console(...)`](../../api/json-console.md)
|
||||
- [`field(...)`](../../api/field.md)
|
||||
- [`build_logger(...)`](../../api/build-logger.md)
|
||||
@@ -1,52 +0,0 @@
|
||||
# Sink 组合 API
|
||||
|
||||
当预设输出不足以表达路由规则时,使用 typed `Logger::new(...)` 和 sink 组合函数。它们接收完整 `Record`,因此仍保留 level、target、message 与 fields。
|
||||
|
||||
## `Logger::new(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn Logger::new(
|
||||
sink : S,
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
) -> Logger[S]
|
||||
```
|
||||
|
||||
从任意 `Sink` 实现创建 typed Logger。`min_level` 在任何 sink 写入前生效;`target` 是默认值,之后可由 `with_target(...)`、`child(...)` 或单次 `log(..., target=...)` 覆盖。
|
||||
|
||||
## `fanout_sink(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn fanout_sink(left : A, right : B) -> FanoutSink[A, B]
|
||||
```
|
||||
|
||||
每条记录写入两个目的地,适合控制台加 JSON,或控制台加 callback。右侧获得独立记录副本,两个写入在记录值层面互不影响。
|
||||
|
||||
## `split_by_level(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn split_by_level(
|
||||
left : A,
|
||||
right : B,
|
||||
min_level~ : Level = Level::Warn,
|
||||
) -> SplitSink[A, B]
|
||||
```
|
||||
|
||||
达到阈值的记录进入 `left`,低于阈值的记录进入 `right`。典型用法是把 warning/error 单独输出,而 info/debug 留在普通控制台。
|
||||
|
||||
## `callback_sink(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn callback_sink(
|
||||
callback : (Record) -> Unit,
|
||||
) -> CallbackSink
|
||||
```
|
||||
|
||||
将完整结构化 `Record` 交给应用回调。适合测试、自定义桥接与集成;它不自动格式化记录。
|
||||
|
||||
## 英文原始 API
|
||||
|
||||
- [`Logger::new(...)`](../../api/logger-new.md)
|
||||
- [`fanout_sink(...)`](../../api/fanout-sink.md)
|
||||
- [`split_by_level(...)`](../../api/split-by-level.md)
|
||||
- [`callback_sink(...)`](../../api/callback-sink.md)
|
||||
@@ -1,53 +0,0 @@
|
||||
# 配置与队列 API
|
||||
|
||||
这一组 API 将外部 JSON 或 typed config 变成同步 Logger,并明确规定满队列和关闭时的处理方式。
|
||||
|
||||
## `parse_logger_config_text(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn parse_logger_config_text(input : String) -> LoggerConfig raise ConfigError
|
||||
```
|
||||
|
||||
解析并验证 JSON,但不构建运行时 Logger。缺省字段使用构造器默认值;无效 JSON、错误类型、未知枚举值或空文件路径都会抛出 `ConfigError`。
|
||||
|
||||
```moonbit
|
||||
let config = @log.parse_logger_config_text(raw) catch {
|
||||
err => { ignore(err); return }
|
||||
}
|
||||
let logger = @log.build_logger(config)
|
||||
```
|
||||
|
||||
## `with_queue(...)`、`QueueConfig` 与溢出策略
|
||||
|
||||
```moonbit
|
||||
pub fn with_queue(
|
||||
config : LoggerConfig,
|
||||
max_pending~ : Int = 0,
|
||||
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
) -> LoggerConfig
|
||||
|
||||
pub fn QueueConfig::new(
|
||||
max_pending : Int,
|
||||
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
) -> QueueConfig
|
||||
```
|
||||
|
||||
`with_queue(...)` 是 preset 组合入口;`QueueConfig::new(...)` 用于手写 `LoggerConfig`。两者配置同步 `QueuedSink`,不是异步包的 worker 队列。
|
||||
|
||||
`DropNewest` 丢弃新到记录,`DropOldest` 丢弃最早待处理记录。选择取决于业务更需要保留早期还是最新状态。
|
||||
|
||||
## `flush()`
|
||||
|
||||
```moonbit
|
||||
pub fn ConfiguredLogger::flush(self : ConfiguredLogger) -> Bool
|
||||
```
|
||||
|
||||
在退出或需要确认输出边界时调用。对于队列和文件 sink,它会走对应运行时 flush 路径;返回值表示该路径是否成功。
|
||||
|
||||
## 英文原始 API
|
||||
|
||||
- [`parse_logger_config_text(...)`](../../api/parse-logger-config-text.md)
|
||||
- [`with_queue(...)`](../../api/with-queue.md)
|
||||
- [`QueueConfig`](../../api/queue-config.md)
|
||||
- [`QueueOverflowPolicy`](../../api/queue-overflow-policy.md)
|
||||
- [`flush()`](../../api/configured-logger-flush.md)
|
||||
@@ -1,49 +0,0 @@
|
||||
# 文件输出 API
|
||||
|
||||
文件输出是 native 能力。先检查能力,再创建文件配置,最后在有序退出时 flush 并关闭。
|
||||
|
||||
## `native_files_supported()`
|
||||
|
||||
```moonbit
|
||||
pub fn native_files_supported() -> Bool
|
||||
```
|
||||
|
||||
native 后端当前返回 `true`,非文件能力后端返回 `false`。这是运行时能力检查,不保证后续文件操作一定成功,路径、权限和文件系统状态仍可能导致失败。
|
||||
|
||||
## `file(...)` 与 `with_file_rotation(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn file(
|
||||
path : String,
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
append~ : Bool = true,
|
||||
auto_flush~ : Bool = true,
|
||||
rotation~ : FileRotation? = None,
|
||||
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
|
||||
) -> LoggerConfig raise ConfigError
|
||||
|
||||
pub fn with_file_rotation(
|
||||
config : LoggerConfig,
|
||||
max_bytes : Int,
|
||||
max_backups~ : Int = 1,
|
||||
) -> LoggerConfig
|
||||
```
|
||||
|
||||
`file(...)` 要求非空路径。`with_file_rotation(...)` 只对 file config 生效;输入不是文件 sink 时原样返回。`max_bytes` 是活动文件上限,`max_backups` 是保留的轮转备份数。
|
||||
|
||||
## `file_close()`
|
||||
|
||||
```moonbit
|
||||
pub fn ConfiguredLogger::file_close(self : ConfiguredLogger) -> Bool
|
||||
```
|
||||
|
||||
关闭 config 构建的文件 sink。队列文件 sink 会依次 drain、flush、close;只有整条路径成功且期间没有新的写入、flush 或轮转失败时才返回 `true`。非文件 sink 返回 `false`。
|
||||
|
||||
## 英文原始 API
|
||||
|
||||
- [`native_files_supported()`](../../api/native-files-supported.md)
|
||||
- [`file(...)`](../../api/file.md)
|
||||
- [`with_file_rotation(...)`](../../api/with-file-rotation.md)
|
||||
- [`file_close()`](../../api/configured-logger-file-close.md)
|
||||
@@ -1,40 +0,0 @@
|
||||
# 文本格式 API
|
||||
|
||||
文本格式只改变呈现,不改变 `Record` 的结构。需要稳定机器消费时,优先使用 `json_console(...)`。
|
||||
|
||||
## `text_console(...)`
|
||||
|
||||
```moonbit
|
||||
pub fn text_console(
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
|
||||
) -> LoggerConfig
|
||||
```
|
||||
|
||||
生成 `SinkKind::TextConsole` 配置,并复制 formatter 配置。和其他 preset 一样,它不直接构建 Logger,也默认不带队列。
|
||||
|
||||
## `TextFormatterConfig` 与 `text_formatter(...)`
|
||||
|
||||
`TextFormatterConfig::new(...)` 用于 config-first 路径,控制 timestamp、level、target、fields、分隔符与 template。`text_formatter(...)` 构建直接传给 `text_console_sink(...)` 的运行时 formatter。
|
||||
|
||||
```moonbit
|
||||
let config = @log.text_console(
|
||||
text_formatter=@log.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
template="[{level}] {target} {message} :: {fields}",
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
## `ColorMode`
|
||||
|
||||
`ColorMode` 控制文本 formatter 的颜色策略。终端样式只服务可读性;业务语义仍应放在 level、target 和 fields 中。
|
||||
|
||||
## 英文原始 API
|
||||
|
||||
- [`text_console(...)`](../../api/text-console.md)
|
||||
- [`TextFormatterConfig`](../../api/text-formatter-config.md)
|
||||
- [`text_formatter(...)`](../../api/text-formatter.md)
|
||||
- [`ColorMode`](../../api/color-mode.md)
|
||||
@@ -1,14 +0,0 @@
|
||||
# 高频 API
|
||||
|
||||
这一层只覆盖 Examples 和 Extend 中直接使用、或应用启动时高频需要的 API。它按使用主题归纳,避免把阅读路径重新拆回一函数一页。
|
||||
|
||||
| 场景 | 中文说明 | 英文精确参考 |
|
||||
| --- | --- | --- |
|
||||
| 第一条结构化日志 | [基础记录](./basics.md) | [完整 API 索引](../../api/index.md) |
|
||||
| 配置、队列与 flush | [配置与队列](./configuration.md) | [config 分类](../../api/index.md) |
|
||||
| native 文件输出 | [文件输出](./file-output.md) | [file/sink 分类](../../api/index.md) |
|
||||
| 终端可读性 | [文本格式](./formatting.md) | [formatter 分类](../../api/index.md) |
|
||||
| 后台异步处理 | [异步生命周期](./async.md) | [async 分类](../../api/index.md) |
|
||||
| 多目的地与按 level 路由 | [Sink 组合](./composition.md) | [sink 分类](../../api/index.md) |
|
||||
|
||||
每个中文页面保留高频签名与行为边界;需要全部参数、错误条件或较少使用的公开符号时,沿页面内链接进入英文原始 API。
|
||||
@@ -1,47 +0,0 @@
|
||||
# 异步日志生命周期
|
||||
|
||||
当 native 异步应用需要通过异步队列处理日志写入时,使用异步包。库声明的可用目标比可执行 `async fn main` 示例更广,仓库示例仍以 native 为主。
|
||||
|
||||
## 导入两个包
|
||||
|
||||
```moonbit
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
"Nanaloveyuki/BitLogger/src-async" @async_log,
|
||||
"moonbitlang/async",
|
||||
}
|
||||
```
|
||||
|
||||
## 构建、运行、关闭
|
||||
|
||||
```moonbit
|
||||
async fn main {
|
||||
let raw = "{\"logger\":{\"min_level\":\"info\",\"target\":\"service.async\",\"sink\":{\"kind\":\"text_console\"}},\"async_config\":{\"max_pending\":64,\"overflow\":\"DropOldest\",\"max_batch\":16,\"linger_ms\":5,\"flush\":\"Batch\"}}"
|
||||
let config = @async_log.parse_async_logger_build_config_text(raw) catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
let logger = @async_log.build_async_logger(config)
|
||||
|
||||
@async.with_task_group(group => {
|
||||
group.spawn_bg(allow_failure=true, () => logger.run())
|
||||
logger.info("worker started", fields=[@log.field("mode", "async")])
|
||||
logger.shutdown()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
`run()` 持有 worker 循环。先启动它再记录日志,并调用 `shutdown()`,使待处理记录按配置的 flush 策略结束。
|
||||
|
||||
## 运行仓库示例
|
||||
|
||||
```bash
|
||||
moon run examples/async_basic --target native
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- 查看[队列](../extend/queue.md)中的溢出与 flush 选择。
|
||||
- 阅读[异步生命周期 API](../api/async.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
|
||||
@@ -1,35 +0,0 @@
|
||||
# 配置驱动构建
|
||||
|
||||
当 level、目标名、输出方式或队列策略应由部署配置决定时,使用这一流程。
|
||||
|
||||
## 解析、构建、记录
|
||||
|
||||
```moonbit
|
||||
let raw = "{\"min_level\":\"info\",\"target\":\"service\",\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \"}},\"queue\":{\"max_pending\":64,\"overflow\":\"DropOldest\"}}"
|
||||
|
||||
let config = @log.parse_logger_config_text(raw) catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
println("invalid logger configuration")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let logger = @log.build_logger(config)
|
||||
logger.info("configured logger ready")
|
||||
ignore(logger.flush())
|
||||
```
|
||||
|
||||
解析先验证数据,再构造运行时 Logger。真实部署应将原始配置放在应用代码之外,并在配置进入进程的边界处理错误。
|
||||
|
||||
## 运行仓库示例
|
||||
|
||||
```bash
|
||||
moon run examples/config_build
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- 需要明确选择满队列时的行为,阅读[队列](../extend/queue.md)。
|
||||
- 需要后台 worker 生命周期时,阅读[异步日志生命周期](./async.md)。
|
||||
- 阅读[配置与队列 API](../api/configuration.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
|
||||
@@ -1,55 +0,0 @@
|
||||
# 控制台与结构化字段
|
||||
|
||||
命令行工具和服务都可以从这一流程开始。一条记录由 level、target、message 与可选 fields 构成。
|
||||
|
||||
## 安装与导入
|
||||
|
||||
```bash
|
||||
moon new log-demo
|
||||
cd log-demo
|
||||
moon add Nanaloveyuki/BitLogger@0.7.3
|
||||
```
|
||||
|
||||
在应用的 `moon.pkg` 中加入:
|
||||
|
||||
```moonbit
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
}
|
||||
```
|
||||
|
||||
## 构建控制台 Logger
|
||||
|
||||
```moonbit
|
||||
fn main {
|
||||
let logger = @log.build_logger(
|
||||
@log.console(min_level=@log.Level::Info, target="service.http"),
|
||||
)
|
||||
|
||||
logger.info("listening", fields=[
|
||||
@log.field("port", "8080"),
|
||||
@log.field("environment", "development"),
|
||||
])
|
||||
logger.warn("slow request", fields=[@log.field("path", "/health")])
|
||||
}
|
||||
```
|
||||
|
||||
`min_level` 在记录到达 sink 前过滤日志;`target` 用于识别子系统;fields 用于携带可查询上下文,而不是拼接到 message 中。
|
||||
|
||||
## 切换为 JSON
|
||||
|
||||
保持日志调用不变,只替换配置:
|
||||
|
||||
```moonbit
|
||||
let logger = @log.build_logger(
|
||||
@log.json_console(min_level=@log.Level::Info, target="service.http"),
|
||||
)
|
||||
```
|
||||
|
||||
当日志采集器按行处理结构化记录时,JSON 输出更合适。
|
||||
|
||||
## 下一步
|
||||
|
||||
- 需要自定义终端输出时,阅读[文本格式](../extend/formatting.md)。
|
||||
- 需要由 JSON 配置构建时,阅读[配置构建](./config.md)。
|
||||
- 阅读[基础记录 API](../api/basics.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
|
||||
@@ -1,51 +0,0 @@
|
||||
# 文件输出与轮转
|
||||
|
||||
文件输出面向需要本地持久化的 native 应用。跨端代码应先判断能力,并保留控制台回退方案。
|
||||
|
||||
## 检查能力
|
||||
|
||||
```moonbit
|
||||
if !@log.native_files_supported() {
|
||||
println("file logging is unavailable on this target")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
## 构建可轮转文件 Logger
|
||||
|
||||
```moonbit
|
||||
let config = @log.with_file_rotation(
|
||||
@log.file(
|
||||
"service.log",
|
||||
min_level=@log.Level::Info,
|
||||
target="service.file",
|
||||
auto_flush=true,
|
||||
) catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
return
|
||||
}
|
||||
},
|
||||
1024 * 1024,
|
||||
max_backups=3,
|
||||
)
|
||||
|
||||
let logger = @log.build_logger(config)
|
||||
logger.info("file logger ready")
|
||||
ignore(logger.flush())
|
||||
ignore(logger.file_close())
|
||||
```
|
||||
|
||||
轮转阈值是活动文件的字节数,`max_backups=3` 保留备份链。应用有序退出时必须关闭文件 Logger。
|
||||
|
||||
## 运行仓库示例
|
||||
|
||||
```bash
|
||||
moon run examples/file_rotation --target native
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- 写入突发较多时,加入[队列](../extend/queue.md)。
|
||||
- 与 web 目标共享代码前,阅读[目标平台边界](../extend/targets.md)。
|
||||
- 阅读[文件输出 API](../api/file-output.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
|
||||
@@ -1,24 +0,0 @@
|
||||
# 示例
|
||||
|
||||
示例是 BitLogger 的主学习路径。每页从一个应用需求开始,给出完整最小代码,再链接到英文 API 参考查看精确契约。
|
||||
|
||||
## 选择流程
|
||||
|
||||
| 需求 | 从这里开始 | 后续扩展 |
|
||||
| --- | --- | --- |
|
||||
| 在终端输出结构化日志 | [控制台与字段](./console.md) | [文本格式](../extend/formatting.md) |
|
||||
| 保存本地日志并限制磁盘占用 | [文件轮转](./file-rotation.md) | [目标平台边界](../extend/targets.md) |
|
||||
| 由应用配置决定日志行为 | [配置构建](./config.md) | [队列](../extend/queue.md) |
|
||||
| 在 native 异步应用中处理日志写入 | [异步生命周期](./async.md) | [Sink 组合](../extend/composition.md) |
|
||||
|
||||
## 可运行源码
|
||||
|
||||
仓库中的 `examples/` 是可执行参考;本目录说明何时选择它们、如何运行,以及下一步应该扩展什么:
|
||||
|
||||
- `examples/console_basic`
|
||||
- `examples/config_build`
|
||||
- `examples/file_rotation`
|
||||
- `examples/async_basic`
|
||||
- `examples/presets`、`examples/text_formatter`、`examples/style_tags`
|
||||
|
||||
遇到函数签名、错误语义或边界条件时,请查询[英文 API 参考](../../api/index.md)。
|
||||
@@ -1,39 +0,0 @@
|
||||
# Sink 组合
|
||||
|
||||
Preset 覆盖常见的控制台和文件配置。只有当记录需要路由或多个输出目的地时,才直接构造 `Logger::new(...)`。
|
||||
|
||||
## 同时发送到两个目的地
|
||||
|
||||
```moonbit
|
||||
let logger = @log.Logger::new(
|
||||
@log.fanout_sink(@log.console_sink(), @log.json_console_sink()),
|
||||
min_level=@log.Level::Info,
|
||||
target="service",
|
||||
)
|
||||
|
||||
logger.info("request complete", fields=[@log.field("status", "200")])
|
||||
```
|
||||
|
||||
`fanout_sink(...)` 将同一记录写入每个子 Sink,因此应用可以同时保留面向人的控制台输出和面向采集器的 JSON 输出。
|
||||
|
||||
## 单独处理警告以上日志
|
||||
|
||||
```moonbit
|
||||
let logger = @log.Logger::new(
|
||||
@log.split_by_level(
|
||||
@log.callback_sink(fn(rec) {
|
||||
println("alert: \{rec.message}")
|
||||
}),
|
||||
@log.console_sink(),
|
||||
min_level=@log.Level::Warn,
|
||||
),
|
||||
min_level=@log.Level::Trace,
|
||||
target="service",
|
||||
)
|
||||
```
|
||||
|
||||
只有当路由规则属于应用设计时才使用直接 Sink 组合;单一输出时,preset 更容易审查和维护。
|
||||
|
||||
## API
|
||||
|
||||
阅读[Sink 组合 API](../api/composition.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
|
||||
@@ -1,41 +0,0 @@
|
||||
# 文本格式与样式
|
||||
|
||||
格式化只改变呈现,记录本身的 level、target、message 和 fields 不变。面向人时使用文本输出;由其他系统解析时使用 JSON。
|
||||
|
||||
## 自定义文本形状
|
||||
|
||||
```moonbit
|
||||
let logger = @log.build_logger(
|
||||
@log.text_console(
|
||||
min_level=@log.Level::Info,
|
||||
target="service",
|
||||
text_formatter=@log.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
field_separator=",",
|
||||
template="[{level}] {target} {message} :: {fields}",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
logger.info("ready", fields=[@log.field("port", "8080")])
|
||||
```
|
||||
|
||||
模板控制可见顺序。fields 应保持独立于 message,以便不同 Sink 一致渲染。
|
||||
|
||||
## 添加命名样式
|
||||
|
||||
```moonbit
|
||||
let formatter = @log.text_formatter(
|
||||
show_timestamp=false,
|
||||
color_mode=@log.ColorMode::Always,
|
||||
).with_style_tags(
|
||||
@log.default_style_tag_registry()
|
||||
.set_tag("accent", fg=Some("#4cc9f0"), bold=true),
|
||||
)
|
||||
```
|
||||
|
||||
样式标签是终端呈现元数据,不应作为唯一的业务或运维语义;语义仍应保存在 level、target 与 fields 中。
|
||||
|
||||
## API
|
||||
|
||||
阅读[文本格式 API](../api/formatting.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
|
||||
@@ -1,15 +0,0 @@
|
||||
# 扩展 Logger
|
||||
|
||||
先完成一个[示例流程](../examples/index.md),再一次只加入一种扩展能力。这些页面说明某个抽象何时有用,以及它会引入什么运行时边界。
|
||||
|
||||
## 扩展地图
|
||||
|
||||
| 需求 | 扩展 | 主要取舍 |
|
||||
| --- | --- | --- |
|
||||
| 吸收短时输出突发 | [队列](./queue.md) | 必须选择溢出与 flush 行为。 |
|
||||
| 同时发送到多个位置或按 level 路由 | [Sink 组合](./composition.md) | 比 preset 更需要显式构造。 |
|
||||
| 调整终端可读性 | [文本格式](./formatting.md) | 格式只改变呈现,不改变记录结构。 |
|
||||
| 在 native 与 web 目标之间共享代码 | [目标平台边界](./targets.md) | 文件与异步运行时行为因后端而异。 |
|
||||
| 将日志关联到分布式请求 | [Trace context](./observability.md) | 传播与导出仍由应用负责。 |
|
||||
|
||||
需要精确函数签名时,沿页面链接进入[英文 API 参考](../../api/index.md)。
|
||||
@@ -1,13 +0,0 @@
|
||||
# Trace Context
|
||||
|
||||
BitLogger 通过结构化字段承载 trace 上下文,不负责 HTTP 传播或遥测传输。应用应继续使用已选定的 tracing 库提取请求上下文,再把标识绑定到 logger。
|
||||
|
||||
```moonbit
|
||||
let request_logger = logger.with_trace_context(
|
||||
@log.trace_context(trace_id, span_id, trace_flags="01"),
|
||||
)
|
||||
```
|
||||
|
||||
派生 logger 的每条记录都会带有 `trace_id`、`span_id`、`trace_flags`,以及传入时的 `trace_state`。原 logger 不会被修改,避免请求上下文泄漏到无关任务。
|
||||
|
||||
`context.as_fields()` 可用于自定义 facade。`traceparent` 解析、span 创建、采样与 OTLP 导出仍由应用选用的 tracing 实现负责。
|
||||
@@ -1,27 +0,0 @@
|
||||
# 队列与溢出策略
|
||||
|
||||
当短时日志突发不应立即写入输出 Sink 时使用队列。队列是显式能力,因为丢弃策略必须由应用决定。
|
||||
|
||||
## 加入同步队列
|
||||
|
||||
```moonbit
|
||||
let config = @log.with_queue(
|
||||
@log.text_console(target="service"),
|
||||
max_pending=128,
|
||||
overflow=@log.QueueOverflowPolicy::DropOldest,
|
||||
)
|
||||
let logger = @log.build_logger(config)
|
||||
|
||||
logger.info("queued record")
|
||||
ignore(logger.flush())
|
||||
```
|
||||
|
||||
`DropOldest` 在队列已满时保留最新运行信息。若更重视先到的记录,可选择 `DropNewest`。在退出边界调用 `flush()`,让待处理记录策略在应用代码中清晰可见。
|
||||
|
||||
## 何时改用异步 Logger
|
||||
|
||||
同步队列只是普通运行时 Logger 的一层配置。当 native 异步应用需要 worker 生命周期和批处理时,转到[异步日志生命周期](../examples/async.md)。
|
||||
|
||||
## API
|
||||
|
||||
阅读[配置与队列 API](../api/configuration.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
|
||||
@@ -1,31 +0,0 @@
|
||||
# 目标平台边界
|
||||
|
||||
BitLogger 保持可移植的结构化日志表面,但 native 文件输出与异步 worker 行为依赖目标平台。应把这些边界写在应用代码中,而不是假设每种 Sink 在所有后端都相同。
|
||||
|
||||
## 可移植默认值
|
||||
|
||||
`console(...)`、`json_console(...)`、结构化 fields、filter、patch 与普通 Logger 表面适合作为跨端共享代码的起点。
|
||||
|
||||
## 仅 native 的文件输出
|
||||
|
||||
```moonbit
|
||||
if @log.native_files_supported() {
|
||||
let logger = @log.build_logger(@log.file("service.log") catch {
|
||||
err => {
|
||||
ignore(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
logger.info("file output enabled")
|
||||
}
|
||||
```
|
||||
|
||||
文件 Sink 需要 native 文件系统支持。面向 web 目标的代码不应无条件构造只包含文件输出的配置。
|
||||
|
||||
## 异步库与异步入口
|
||||
|
||||
异步库在声明目标上提供兼容表面,但可执行 `async fn main` 的入口支持更严格。请把 native-only 可执行示例与可移植库代码分开。
|
||||
|
||||
## API
|
||||
|
||||
阅读[文件输出 API](../api/file-output.md)和[异步生命周期](../examples/async.md)。完整目标验证记录仍在[英文 API](../../api/target-verification.md)。
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: BitLogger
|
||||
text: MoonBit 结构化日志库
|
||||
tagline: 先完成一条可运行的日志流程,再按需扩展并查询 API。
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 从示例开始
|
||||
link: /zh/examples/
|
||||
- theme: alt
|
||||
text: 扩展 Logger
|
||||
link: /zh/extend/
|
||||
- theme: alt
|
||||
text: API 参考(英文)
|
||||
link: /api/
|
||||
|
||||
features:
|
||||
- title: 示例优先
|
||||
details: 从控制台、文件、配置和异步四条完整流程开始,而不是从 API 文件名开始。
|
||||
- title: 按需扩展
|
||||
details: 只在基础流程需要时再加入队列、组合 Sink、格式化和跨端处理。
|
||||
- title: API 作为参考
|
||||
details: 英文 API 页面保留精确签名、契约与边界说明。
|
||||
---
|
||||
|
||||
## 从这里开始
|
||||
|
||||
1. 阅读[示例](./examples/index.md),完成从安装到运行 Logger 的全流程。
|
||||
2. 当基础控制台流程需要更多能力时,阅读[扩展](./extend/index.md)。
|
||||
3. 需要高频签名和边界时,查询[中文高频 API](./api/index.md);较少使用的符号仍进入[英文完整 API 参考](../api/index.md)。
|
||||
@@ -6,4 +6,6 @@ import {
|
||||
|
||||
supported_targets = "+native"
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
is_main: true,
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
}
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
"is-main": true,
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
}
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
"is-main": true,
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
}
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
"is-main": true,
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
}
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
"is-main": true,
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
}
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
"is-main": true,
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
}
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
"is-main": true,
|
||||
)
|
||||
|
||||
@@ -2,4 +2,6 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
}
|
||||
|
||||
pkgtype(kind: "executable")
|
||||
options(
|
||||
"is-main": true,
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
name = "Nanaloveyuki/BitLogger"
|
||||
|
||||
version = "0.7.3"
|
||||
version = "0.7.0"
|
||||
|
||||
import {
|
||||
"moonbitlang/async@0.20.2",
|
||||
"maria/json_parser@0.1.1",
|
||||
"moonbitlang/async@0.20.0",
|
||||
}
|
||||
|
||||
readme = "src/README.mbt.md"
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Updates BitLogger's package version, installation snippets, and release-note entry.
|
||||
|
||||
.DESCRIPTION
|
||||
For patch releases, the public release version is inferred from the latest
|
||||
changes entry. For example, after 1.1.2(0.7.2), running:
|
||||
|
||||
.\scripts\update-version.ps1 0.7.3
|
||||
|
||||
creates 1.1.3(0.7.3). A major or minor package-version change requires an
|
||||
explicit -ReleaseVersion so the public release sequence is never guessed.
|
||||
|
||||
The generated release note uses supplied -Change, -Verification, and -Note
|
||||
entries. Without them it contains TODO markers that must be completed before
|
||||
the release is committed.
|
||||
#>
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[Parameter(Mandatory, Position = 0)]
|
||||
[ValidatePattern('^\d+\.\d+\.\d+$')]
|
||||
[string]$Version,
|
||||
|
||||
[ValidatePattern('^\d+\.\d+\.\d+$')]
|
||||
[string]$ReleaseVersion,
|
||||
|
||||
[string[]]$Change,
|
||||
[string[]]$Verification,
|
||||
[string[]]$Note
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Read-TextFile([string]$Path) {
|
||||
[System.IO.File]::ReadAllText($Path)
|
||||
}
|
||||
|
||||
function Write-TextFile([string]$Path, [string]$Content) {
|
||||
$encoding = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($Path, $Content, $encoding)
|
||||
}
|
||||
|
||||
function Replace-ExactlyOnce(
|
||||
[string]$Content,
|
||||
[string]$Pattern,
|
||||
[string]$Replacement,
|
||||
[string]$Path
|
||||
) {
|
||||
$matches = [regex]::Matches($Content, $Pattern)
|
||||
if ($matches.Count -ne 1) {
|
||||
throw "Expected exactly one match for '$Pattern' in $Path, found $($matches.Count)."
|
||||
}
|
||||
[regex]::Replace($Content, $Pattern, $Replacement, 1)
|
||||
}
|
||||
|
||||
function Format-BulletList([string[]]$Items, [string]$Fallback) {
|
||||
$entries = if ($null -eq $Items -or $Items.Count -eq 0) {
|
||||
@($Fallback)
|
||||
} else {
|
||||
$Items
|
||||
}
|
||||
($entries | ForEach-Object { "- $_" }) -join [Environment]::NewLine
|
||||
}
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$moonModPath = Join-Path $repoRoot 'moon.mod'
|
||||
$changesIndexPath = Join-Path $repoRoot 'docs/changes/index.md'
|
||||
|
||||
foreach ($path in @($moonModPath, $changesIndexPath)) {
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "Required repository file is missing: $path"
|
||||
}
|
||||
}
|
||||
|
||||
$moonMod = Read-TextFile $moonModPath
|
||||
$currentVersionMatch = [regex]::Match($moonMod, '(?m)^version\s*=\s*"(?<version>\d+\.\d+\.\d+)"\s*$')
|
||||
if (-not $currentVersionMatch.Success) {
|
||||
throw "Could not read the package version from $moonModPath."
|
||||
}
|
||||
$currentVersion = [version]$currentVersionMatch.Groups['version'].Value
|
||||
$targetVersion = [version]$Version
|
||||
if ($targetVersion -le $currentVersion) {
|
||||
throw "Target version $Version must be greater than the current version $currentVersion."
|
||||
}
|
||||
|
||||
$changesIndex = Read-TextFile $changesIndexPath
|
||||
$latestReleaseMatch = [regex]::Match(
|
||||
$changesIndex,
|
||||
'(?m)^- \[(?<release>\d+\.\d+\.\d+)\]\(\./(?<file>\d+\.\d+\.\d+\(\d+\.\d+\.\d+\)\.md)\)\s*$'
|
||||
)
|
||||
if (-not $latestReleaseMatch.Success) {
|
||||
throw "Could not read the latest release entry from $changesIndexPath."
|
||||
}
|
||||
|
||||
$latestReleaseVersion = [version]$latestReleaseMatch.Groups['release'].Value
|
||||
$latestPackageVersionMatch = [regex]::Match(
|
||||
$latestReleaseMatch.Groups['file'].Value,
|
||||
'\((?<version>\d+\.\d+\.\d+)\)\.md$'
|
||||
)
|
||||
if (-not $latestPackageVersionMatch.Success -or $latestPackageVersionMatch.Groups['version'].Value -ne $currentVersion.ToString()) {
|
||||
throw 'The latest changes entry must describe the current moon.mod package version before starting a new release.'
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($ReleaseVersion)) {
|
||||
if ($targetVersion.Major -ne $currentVersion.Major -or $targetVersion.Minor -ne $currentVersion.Minor) {
|
||||
throw 'A major or minor package-version change requires -ReleaseVersion, for example: -ReleaseVersion 1.2.0.'
|
||||
}
|
||||
$ReleaseVersion = "$($latestReleaseVersion.Major).$($latestReleaseVersion.Minor).$($targetVersion.Build)"
|
||||
}
|
||||
|
||||
$releaseFileName = "$ReleaseVersion($Version).md"
|
||||
$releasePath = Join-Path $repoRoot "docs/changes/$releaseFileName"
|
||||
if (Test-Path -LiteralPath $releasePath) {
|
||||
throw "Release note already exists: $releasePath"
|
||||
}
|
||||
if ($changesIndex.Contains("./$releaseFileName")) {
|
||||
throw "Release index already contains: $releaseFileName"
|
||||
}
|
||||
|
||||
$updatedMoonMod = Replace-ExactlyOnce `
|
||||
$moonMod `
|
||||
'(?m)^version\s*=\s*"\d+\.\d+\.\d+"\s*$' `
|
||||
"version = `"$Version`"" `
|
||||
$moonModPath
|
||||
|
||||
$installFiles = @(
|
||||
'README.md',
|
||||
'docs/README-en.md',
|
||||
'docs/examples/console.md',
|
||||
'docs/zh/examples/console.md'
|
||||
)
|
||||
$updatedFiles = @{}
|
||||
foreach ($relativePath in $installFiles) {
|
||||
$path = Join-Path $repoRoot $relativePath
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "Required installation example is missing: $path"
|
||||
}
|
||||
$content = Read-TextFile $path
|
||||
$updatedFiles[$path] = Replace-ExactlyOnce `
|
||||
$content `
|
||||
'Nanaloveyuki/BitLogger@\d+\.\d+\.\d+' `
|
||||
"Nanaloveyuki/BitLogger@$Version" `
|
||||
$path
|
||||
}
|
||||
|
||||
$updatedChangesIndex = $changesIndex.Replace(
|
||||
$latestReleaseMatch.Value,
|
||||
"- [$ReleaseVersion](./$releaseFileName)$([Environment]::NewLine)$($latestReleaseMatch.Value)"
|
||||
)
|
||||
|
||||
$releaseNote = @"
|
||||
## BitLogger Update Changes
|
||||
|
||||
version $ReleaseVersion($Version)
|
||||
|
||||
### Changes
|
||||
|
||||
$(Format-BulletList $Change 'TODO: summarize the user-visible changes in this release.')
|
||||
|
||||
### Verification
|
||||
|
||||
$(Format-BulletList $Verification 'TODO: record the commands and targets verified for this release.')
|
||||
|
||||
### Notes
|
||||
|
||||
$(Format-BulletList $Note 'TODO: record release limitations or compatibility notes when applicable.')
|
||||
"@
|
||||
|
||||
$writeTargets = @($moonModPath, $changesIndexPath, $releasePath) + $updatedFiles.Keys
|
||||
if ($PSCmdlet.ShouldProcess(($writeTargets -join ', '), "update to $Version")) {
|
||||
Write-TextFile $moonModPath $updatedMoonMod
|
||||
foreach ($path in $updatedFiles.Keys) {
|
||||
Write-TextFile $path $updatedFiles[$path]
|
||||
}
|
||||
Write-TextFile $changesIndexPath $updatedChangesIndex
|
||||
Write-TextFile $releasePath $releaseNote
|
||||
}
|
||||
|
||||
if ($WhatIfPreference) {
|
||||
Write-Output "Previewed release $ReleaseVersion($Version)."
|
||||
} else {
|
||||
Write-Output "Prepared release $ReleaseVersion($Version)."
|
||||
}
|
||||
Write-Output "Release notes: $releasePath"
|
||||
@@ -455,30 +455,6 @@ async test "async logger with_context_fields prepends shared fields without muta
|
||||
inspect(written_fields.val[2].value, content="test")
|
||||
}
|
||||
|
||||
///|
|
||||
test "async facades bind trace context through standard fields" {
|
||||
let context = @bitlogger.trace_context("trace-async", "span-async")
|
||||
let base = async_logger(
|
||||
@bitlogger.callback_sink(fn(_) { }),
|
||||
config=AsyncLoggerConfig::new(max_pending=4),
|
||||
min_level=@bitlogger.Level::Info,
|
||||
)
|
||||
let derived = base.with_trace_context(context)
|
||||
inspect(base.context_fields.length(), content="0")
|
||||
inspect(derived.context_fields.length(), content="3")
|
||||
inspect(derived.context_fields[0].key, content="trace_id")
|
||||
inspect(derived.context_fields[0].value, content="trace-async")
|
||||
inspect(derived.context_fields[1].key, content="span_id")
|
||||
inspect(derived.context_fields[2].value, content="01")
|
||||
|
||||
let library = LibraryAsyncLogger::new(@bitlogger.callback_sink(fn(_) { })).with_trace_context(
|
||||
context,
|
||||
)
|
||||
let full = library.to_async_logger()
|
||||
inspect(full.context_fields.length(), content="3")
|
||||
inspect(full.context_fields[1].value, content="span-async")
|
||||
}
|
||||
|
||||
///|
|
||||
async test "async logger child composes target and preserves other state" {
|
||||
let written_target : Ref[String] = Ref("")
|
||||
@@ -855,29 +831,29 @@ test "async json helpers export stable structured shapes" {
|
||||
flush=AsyncFlushPolicy::Batch,
|
||||
),
|
||||
)
|
||||
let config_obj = config_json.expect_json_object()
|
||||
let config_obj = config_json.as_object().unwrap()
|
||||
inspect(
|
||||
config_json.stringify(),
|
||||
@json_parser.stringify(config_json),
|
||||
content="{\"max_pending\":8,\"max_batch\":3,\"linger_ms\":25,\"overflow\":\"DropOldest\",\"flush\":\"Batch\"}",
|
||||
)
|
||||
inspect(
|
||||
config_obj.get("max_pending").unwrap().expect_json_number().to_int(),
|
||||
config_obj.get("max_pending").unwrap().as_number().unwrap().to_int(),
|
||||
content="8",
|
||||
)
|
||||
inspect(
|
||||
config_obj.get("max_batch").unwrap().expect_json_number().to_int(),
|
||||
config_obj.get("max_batch").unwrap().as_number().unwrap().to_int(),
|
||||
content="3",
|
||||
)
|
||||
inspect(
|
||||
config_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
|
||||
config_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(),
|
||||
content="25",
|
||||
)
|
||||
inspect(
|
||||
config_obj.get("overflow").unwrap().expect_json_string(),
|
||||
config_obj.get("overflow").unwrap().as_string().unwrap(),
|
||||
content="DropOldest",
|
||||
)
|
||||
inspect(
|
||||
config_obj.get("flush").unwrap().expect_json_string(),
|
||||
config_obj.get("flush").unwrap().as_string().unwrap(),
|
||||
content="Batch",
|
||||
)
|
||||
|
||||
@@ -898,84 +874,85 @@ test "async json helpers export stable structured shapes" {
|
||||
),
|
||||
),
|
||||
)
|
||||
let build_obj = build_json.expect_json_object()
|
||||
let logger_obj = build_obj.get("logger").unwrap().expect_json_object()
|
||||
let sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
|
||||
let build_obj = build_json.as_object().unwrap()
|
||||
let logger_obj = build_obj.get("logger").unwrap().as_object().unwrap()
|
||||
let sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap()
|
||||
let formatter_obj = sink_obj
|
||||
.get("text_formatter")
|
||||
.unwrap()
|
||||
.expect_json_object()
|
||||
let async_obj = build_obj.get("async_config").unwrap().expect_json_object()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
let async_obj = build_obj.get("async_config").unwrap().as_object().unwrap()
|
||||
|
||||
inspect(
|
||||
logger_obj.get("min_level").unwrap().expect_json_string(),
|
||||
logger_obj.get("min_level").unwrap().as_string().unwrap(),
|
||||
content="WARN",
|
||||
)
|
||||
inspect(
|
||||
logger_obj.get("target").unwrap().expect_json_string(),
|
||||
logger_obj.get("target").unwrap().as_string().unwrap(),
|
||||
content="async.roundtrip",
|
||||
)
|
||||
inspect(
|
||||
logger_obj.get("timestamp").unwrap().expect_json_bool(),
|
||||
logger_obj.get("timestamp").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(logger_obj.get("queue") is None, content="true")
|
||||
inspect(
|
||||
sink_obj.get("kind").unwrap().expect_json_string(),
|
||||
sink_obj.get("kind").unwrap().as_string().unwrap(),
|
||||
content="text_console",
|
||||
)
|
||||
inspect(sink_obj.get("path").unwrap().expect_json_string(), content="")
|
||||
inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="true")
|
||||
inspect(sink_obj.get("path").unwrap().as_string().unwrap(), content="")
|
||||
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="true")
|
||||
inspect(
|
||||
sink_obj.get("auto_flush").unwrap().expect_json_bool(),
|
||||
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
|
||||
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("separator").unwrap().expect_json_string(),
|
||||
formatter_obj.get("separator").unwrap().as_string().unwrap(),
|
||||
content=" ",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("field_separator").unwrap().expect_json_string(),
|
||||
formatter_obj.get("field_separator").unwrap().as_string().unwrap(),
|
||||
content=" ",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("template").unwrap().expect_json_string(),
|
||||
formatter_obj.get("template").unwrap().as_string().unwrap(),
|
||||
content="",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("color_mode").unwrap().expect_json_string(),
|
||||
formatter_obj.get("color_mode").unwrap().as_string().unwrap(),
|
||||
content="never",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("color_support").unwrap().expect_json_string(),
|
||||
formatter_obj.get("color_support").unwrap().as_string().unwrap(),
|
||||
content="truecolor",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("style_markup").unwrap().expect_json_string(),
|
||||
formatter_obj.get("style_markup").unwrap().as_string().unwrap(),
|
||||
content="full",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("max_pending").unwrap().expect_json_number().to_int(),
|
||||
async_obj.get("max_pending").unwrap().as_number().unwrap().to_int(),
|
||||
content="2",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("max_batch").unwrap().expect_json_number().to_int(),
|
||||
async_obj.get("max_batch").unwrap().as_number().unwrap().to_int(),
|
||||
content="5",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
|
||||
async_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(),
|
||||
content="40",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("overflow").unwrap().expect_json_string(),
|
||||
async_obj.get("overflow").unwrap().as_string().unwrap(),
|
||||
content="DropNewest",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("flush").unwrap().expect_json_string(),
|
||||
async_obj.get("flush").unwrap().as_string().unwrap(),
|
||||
content="Shutdown",
|
||||
)
|
||||
}
|
||||
@@ -984,102 +961,103 @@ test "async json helpers export stable structured shapes" {
|
||||
test "async build config json export materializes parsed omitted defaults" {
|
||||
let parsed = parse_async_logger_build_config_text("{}")
|
||||
let build_json = async_logger_build_config_to_json(parsed)
|
||||
let build_obj = build_json.expect_json_object()
|
||||
let logger_obj = build_obj.get("logger").unwrap().expect_json_object()
|
||||
let sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
|
||||
let build_obj = build_json.as_object().unwrap()
|
||||
let logger_obj = build_obj.get("logger").unwrap().as_object().unwrap()
|
||||
let sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap()
|
||||
let formatter_obj = sink_obj
|
||||
.get("text_formatter")
|
||||
.unwrap()
|
||||
.expect_json_object()
|
||||
let async_obj = build_obj.get("async_config").unwrap().expect_json_object()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
let async_obj = build_obj.get("async_config").unwrap().as_object().unwrap()
|
||||
|
||||
inspect(
|
||||
build_json.stringify(),
|
||||
@json_parser.stringify(build_json),
|
||||
content="{\"logger\":{\"min_level\":\"INFO\",\"target\":\"\",\"timestamp\":false,\"sink\":{\"kind\":\"console\",\"path\":\"\",\"append\":true,\"auto_flush\":true,\"text_formatter\":{\"show_timestamp\":true,\"show_level\":true,\"show_target\":true,\"show_fields\":true,\"separator\":\" \",\"field_separator\":\" \",\"template\":\"\",\"color_mode\":\"never\",\"color_support\":\"truecolor\",\"style_markup\":\"full\",\"target_style_markup\":\"disabled\",\"fields_style_markup\":\"disabled\"}}},\"async_config\":{\"max_pending\":0,\"max_batch\":1,\"linger_ms\":0,\"overflow\":\"Blocking\",\"flush\":\"Never\"}}",
|
||||
)
|
||||
inspect(
|
||||
logger_obj.get("min_level").unwrap().expect_json_string(),
|
||||
logger_obj.get("min_level").unwrap().as_string().unwrap(),
|
||||
content="INFO",
|
||||
)
|
||||
inspect(logger_obj.get("target").unwrap().expect_json_string(), content="")
|
||||
inspect(logger_obj.get("target").unwrap().as_string().unwrap(), content="")
|
||||
inspect(
|
||||
logger_obj.get("timestamp").unwrap().expect_json_bool(),
|
||||
logger_obj.get("timestamp").unwrap().as_bool().unwrap(),
|
||||
content="false",
|
||||
)
|
||||
inspect(logger_obj.get("queue") is None, content="true")
|
||||
inspect(sink_obj.get("kind").unwrap().expect_json_string(), content="console")
|
||||
inspect(sink_obj.get("path").unwrap().expect_json_string(), content="")
|
||||
inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="true")
|
||||
inspect(sink_obj.get("kind").unwrap().as_string().unwrap(), content="console")
|
||||
inspect(sink_obj.get("path").unwrap().as_string().unwrap(), content="")
|
||||
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="true")
|
||||
inspect(
|
||||
sink_obj.get("auto_flush").unwrap().expect_json_bool(),
|
||||
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
|
||||
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("show_level").unwrap().expect_json_bool(),
|
||||
formatter_obj.get("show_level").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("show_target").unwrap().expect_json_bool(),
|
||||
formatter_obj.get("show_target").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("show_fields").unwrap().expect_json_bool(),
|
||||
formatter_obj.get("show_fields").unwrap().as_bool().unwrap(),
|
||||
content="true",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("separator").unwrap().expect_json_string(),
|
||||
formatter_obj.get("separator").unwrap().as_string().unwrap(),
|
||||
content=" ",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("field_separator").unwrap().expect_json_string(),
|
||||
formatter_obj.get("field_separator").unwrap().as_string().unwrap(),
|
||||
content=" ",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("template").unwrap().expect_json_string(),
|
||||
formatter_obj.get("template").unwrap().as_string().unwrap(),
|
||||
content="",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("color_mode").unwrap().expect_json_string(),
|
||||
formatter_obj.get("color_mode").unwrap().as_string().unwrap(),
|
||||
content="never",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("color_support").unwrap().expect_json_string(),
|
||||
formatter_obj.get("color_support").unwrap().as_string().unwrap(),
|
||||
content="truecolor",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("style_markup").unwrap().expect_json_string(),
|
||||
formatter_obj.get("style_markup").unwrap().as_string().unwrap(),
|
||||
content="full",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("target_style_markup").unwrap().expect_json_string(),
|
||||
formatter_obj.get("target_style_markup").unwrap().as_string().unwrap(),
|
||||
content="disabled",
|
||||
)
|
||||
inspect(
|
||||
formatter_obj.get("fields_style_markup").unwrap().expect_json_string(),
|
||||
formatter_obj.get("fields_style_markup").unwrap().as_string().unwrap(),
|
||||
content="disabled",
|
||||
)
|
||||
inspect(formatter_obj.get("style_tags") is None, content="true")
|
||||
inspect(
|
||||
async_obj.get("max_pending").unwrap().expect_json_number().to_int(),
|
||||
async_obj.get("max_pending").unwrap().as_number().unwrap().to_int(),
|
||||
content="0",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("max_batch").unwrap().expect_json_number().to_int(),
|
||||
async_obj.get("max_batch").unwrap().as_number().unwrap().to_int(),
|
||||
content="1",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
|
||||
async_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(),
|
||||
content="0",
|
||||
)
|
||||
inspect(
|
||||
async_obj.get("overflow").unwrap().expect_json_string(),
|
||||
async_obj.get("overflow").unwrap().as_string().unwrap(),
|
||||
content="Blocking",
|
||||
)
|
||||
inspect(async_obj.get("flush").unwrap().expect_json_string(), content="Never")
|
||||
inspect(async_obj.get("flush").unwrap().as_string().unwrap(), content="Never")
|
||||
}
|
||||
|
||||
///|
|
||||
@@ -1090,7 +1068,7 @@ test "async config parsers reject malformed input" {
|
||||
})() catch {
|
||||
err => err.to_string()
|
||||
}
|
||||
inspect(invalid_json_error != "no error", content="true")
|
||||
inspect(invalid_json_error.contains("UnexpectedToken"), content="true")
|
||||
|
||||
let wrong_type_error = (fn() -> String raise {
|
||||
ignore(parse_async_logger_config_text("{\"max_pending\":\"many\"}"))
|
||||
@@ -1166,7 +1144,7 @@ test "async runtime capability helpers stay consistent" {
|
||||
)
|
||||
inspect(state.background_worker == worker_supported, content="true")
|
||||
inspect(
|
||||
async_runtime_state_to_json(state).stringify(),
|
||||
@json_parser.stringify(async_runtime_state_to_json(state)),
|
||||
content=if worker_supported {
|
||||
"{\"mode\":\"native_worker\",\"background_worker\":true}"
|
||||
} else {
|
||||
@@ -1247,7 +1225,7 @@ test "async logger state snapshot reflects current counters and runtime" {
|
||||
content="Shutdown",
|
||||
)
|
||||
inspect(
|
||||
async_logger_state_to_json(state).stringify(),
|
||||
@json_parser.stringify(async_logger_state_to_json(state)),
|
||||
content=if async_runtime_supports_background_worker() {
|
||||
"{\"runtime\":{\"mode\":\"native_worker\",\"background_worker\":true},\"phase\":\"ready\",\"pending_count\":0,\"dropped_count\":0,\"is_closed\":false,\"is_running\":false,\"has_failed\":false,\"backlog_retained\":false,\"can_rerun\":true,\"terminal\":false,\"last_error\":\"\",\"flush_policy\":\"Shutdown\"}"
|
||||
} else {
|
||||
@@ -2426,7 +2404,7 @@ async test "library async parse-build unwrap matches parsed direct async builder
|
||||
|
||||
///|
|
||||
async test "library async parse-build unwrap preserves file-backed runtime helpers" {
|
||||
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.lib.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"logs/async-lib-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
|
||||
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.lib.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"async-lib-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
|
||||
let logger = parse_and_build_library_async_logger(raw)
|
||||
let full = logger.to_async_logger()
|
||||
let direct = build_async_logger(parse_async_logger_build_config_text(raw))
|
||||
@@ -2942,7 +2920,7 @@ test "library async text logger ignores sink kind and still uses text formatter"
|
||||
target="async.lib.text.kind",
|
||||
sink=@bitlogger.SinkConfig::new(
|
||||
kind=@bitlogger.SinkKind::File,
|
||||
path="logs/ignored.log",
|
||||
path="ignored.log",
|
||||
text_formatter=@bitlogger.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
separator=" | ",
|
||||
@@ -3255,7 +3233,7 @@ async test "async logger projection preserves file-backed runtime helpers throug
|
||||
target="async.projected.file",
|
||||
sink=@bitlogger.SinkConfig::new(
|
||||
kind=@bitlogger.SinkKind::File,
|
||||
path="logs/async-projected-file.log",
|
||||
path="async-projected-file.log",
|
||||
),
|
||||
queue=Some(
|
||||
@bitlogger.QueueConfig::new(
|
||||
@@ -3552,7 +3530,7 @@ async test "application async builder preserves file-backed runtime helpers" {
|
||||
target="async.app.file.same-build",
|
||||
sink=@bitlogger.SinkConfig::new(
|
||||
kind=@bitlogger.SinkKind::File,
|
||||
path="logs/async-app-file-same-build.log",
|
||||
path="async-app-file-same-build.log",
|
||||
),
|
||||
queue=Some(
|
||||
@bitlogger.QueueConfig::new(
|
||||
@@ -4406,7 +4384,7 @@ test "application text async logger ignores sink kind and still uses text format
|
||||
target="async.app.text.kind.alias",
|
||||
sink=@bitlogger.SinkConfig::new(
|
||||
kind=@bitlogger.SinkKind::File,
|
||||
path="logs/ignored.log",
|
||||
path="ignored.log",
|
||||
text_formatter=@bitlogger.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
separator=" | ",
|
||||
@@ -4484,7 +4462,7 @@ test "build async text logger ignores sink kind and still uses text formatter" {
|
||||
target="async.text.kind",
|
||||
sink=@bitlogger.SinkConfig::new(
|
||||
kind=@bitlogger.SinkKind::File,
|
||||
path="logs/ignored.log",
|
||||
path="ignored.log",
|
||||
text_formatter=@bitlogger.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
separator=" | ",
|
||||
@@ -4981,7 +4959,7 @@ async test "application async parse-build matches parsed direct async builder be
|
||||
|
||||
///|
|
||||
async test "application async parse-build preserves file-backed runtime helpers" {
|
||||
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.app.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"logs/async-app-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
|
||||
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.app.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"async-app-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
|
||||
let application = parse_and_build_application_async_logger(raw)
|
||||
let direct = build_async_logger(parse_async_logger_build_config_text(raw))
|
||||
|
||||
|
||||
@@ -48,7 +48,9 @@ pub fn async_runtime_state() -> AsyncRuntimeState {
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {
|
||||
pub fn async_runtime_state_to_json(
|
||||
state : AsyncRuntimeState,
|
||||
) -> @json_parser.JsonValue {
|
||||
@utils.async_runtime_state_to_json(state)
|
||||
}
|
||||
|
||||
@@ -61,7 +63,9 @@ pub fn stringify_async_runtime_state(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {
|
||||
pub fn async_logger_state_to_json(
|
||||
state : AsyncLoggerState,
|
||||
) -> @json_parser.JsonValue {
|
||||
@utils.async_logger_state_to_json(state)
|
||||
}
|
||||
|
||||
@@ -84,7 +88,9 @@ pub fn parse_async_logger_config_text(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {
|
||||
pub fn async_logger_config_to_json(
|
||||
config : AsyncLoggerConfig,
|
||||
) -> @json_parser.JsonValue {
|
||||
@utils.async_logger_config_to_json(config)
|
||||
}
|
||||
|
||||
@@ -109,7 +115,7 @@ pub fn parse_async_logger_build_config_text(
|
||||
///|
|
||||
pub fn async_logger_build_config_to_json(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> Json {
|
||||
) -> @json_parser.JsonValue {
|
||||
@utils.async_logger_build_config_to_json(config)
|
||||
}
|
||||
|
||||
@@ -274,14 +280,6 @@ pub fn[S] AsyncLogger::with_context_fields(
|
||||
{ ..self, context_fields: fields }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::with_trace_context(
|
||||
self : AsyncLogger[S],
|
||||
context : @bitlogger.TraceContext,
|
||||
) -> AsyncLogger[S] {
|
||||
self.with_context_fields(context.as_fields())
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::with_filter(
|
||||
self : AsyncLogger[S],
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
///|
|
||||
async test "native async file lifecycle flushes rotates and restarts" {
|
||||
if !@bitlogger.native_files_supported() {
|
||||
inspect(@bitlogger.native_files_supported(), content="false")
|
||||
} else {
|
||||
let path = "logs/bitlogger-async-native-e2e.log"
|
||||
let first_flushes : Ref[Int] = Ref(0)
|
||||
let first_sink = @bitlogger.file_sink(
|
||||
path,
|
||||
append=false,
|
||||
auto_flush=false,
|
||||
rotation=Some(@bitlogger.file_rotation(8, max_backups=1)),
|
||||
formatter=fn(rec) { rec.message },
|
||||
)
|
||||
let first = async_logger(
|
||||
first_sink,
|
||||
config=AsyncLoggerConfig::new(
|
||||
max_pending=4,
|
||||
overflow=AsyncOverflowPolicy::Blocking,
|
||||
max_batch=4,
|
||||
linger_ms=0,
|
||||
flush=AsyncFlushPolicy::Batch,
|
||||
),
|
||||
min_level=@bitlogger.Level::Info,
|
||||
target="async.file.e2e",
|
||||
flush=fn(sink) {
|
||||
first_flushes.val += 1
|
||||
ignore(sink.flush())
|
||||
},
|
||||
)
|
||||
|
||||
@async.with_task_group(group => {
|
||||
group.spawn_bg(() => first.run())
|
||||
first.info("first-record")
|
||||
first.shutdown()
|
||||
})
|
||||
|
||||
inspect(first.is_closed(), content="true")
|
||||
inspect(first.has_failed(), content="false")
|
||||
inspect(first.pending_count(), content="0")
|
||||
inspect(first_flushes.val > 0, content="true")
|
||||
inspect(first_sink.path(), content="logs/bitlogger-async-native-e2e.log")
|
||||
inspect(first_sink.rotation_enabled(), content="true")
|
||||
inspect(first_sink.rotation_failures(), content="0")
|
||||
inspect(first_sink.write_failures(), content="0")
|
||||
inspect(first_sink.flush_failures(), content="0")
|
||||
inspect(first_sink.close(), content="true")
|
||||
|
||||
let restart_flushes : Ref[Int] = Ref(0)
|
||||
let restarted_sink = @bitlogger.file_sink(
|
||||
path,
|
||||
append=true,
|
||||
auto_flush=false,
|
||||
rotation=Some(@bitlogger.file_rotation(8, max_backups=1)),
|
||||
formatter=fn(rec) { rec.message },
|
||||
)
|
||||
let restarted = async_logger(
|
||||
restarted_sink,
|
||||
config=AsyncLoggerConfig::new(
|
||||
max_pending=4,
|
||||
overflow=AsyncOverflowPolicy::Blocking,
|
||||
max_batch=4,
|
||||
linger_ms=0,
|
||||
flush=AsyncFlushPolicy::Shutdown,
|
||||
),
|
||||
min_level=@bitlogger.Level::Info,
|
||||
target="async.file.e2e",
|
||||
flush=fn(sink) {
|
||||
restart_flushes.val += 1
|
||||
ignore(sink.flush())
|
||||
},
|
||||
)
|
||||
|
||||
@async.with_task_group(group => {
|
||||
group.spawn_bg(() => restarted.run())
|
||||
restarted.info("second-record")
|
||||
restarted.shutdown()
|
||||
})
|
||||
|
||||
inspect(restarted.is_closed(), content="true")
|
||||
inspect(restarted.has_failed(), content="false")
|
||||
inspect(restarted.pending_count(), content="0")
|
||||
inspect(restart_flushes.val, content="1")
|
||||
inspect(restarted_sink.rotation_enabled(), content="true")
|
||||
inspect(restarted_sink.rotation_failures(), content="0")
|
||||
inspect(restarted_sink.write_failures(), content="0")
|
||||
inspect(restarted_sink.flush_failures(), content="0")
|
||||
inspect(restarted_sink.close(), content="true")
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
///|
|
||||
fn Json::expect_json_object(self : Json) -> Map[String, Json] {
|
||||
match self {
|
||||
Json::Object(value) => value
|
||||
_ => abort("Expected JSON object")
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn Json::expect_json_number(self : Json) -> Double {
|
||||
match self {
|
||||
Json::Number(value, ..) => value
|
||||
_ => abort("Expected JSON number")
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn Json::expect_json_string(self : Json) -> String {
|
||||
match self {
|
||||
Json::String(value) => value
|
||||
_ => abort("Expected JSON string")
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn Json::expect_json_bool(self : Json) -> Bool {
|
||||
match self {
|
||||
Json::True => true
|
||||
Json::False => false
|
||||
_ => abort("Expected JSON bool")
|
||||
}
|
||||
}
|
||||
@@ -93,14 +93,6 @@ pub fn[S] LibraryAsyncLogger::bind(
|
||||
self.with_context_fields(fields)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::with_trace_context(
|
||||
self : LibraryAsyncLogger[S],
|
||||
context : @bitlogger.TraceContext,
|
||||
) -> LibraryAsyncLogger[S] {
|
||||
library_async_logger(self.inner.with_trace_context(context))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::is_enabled(
|
||||
self : LibraryAsyncLogger[S],
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @bitlogger,
|
||||
"Nanaloveyuki/BitLogger/src-async/utils",
|
||||
"moonbitlang/core/json",
|
||||
"maria/json_parser",
|
||||
"moonbitlang/async",
|
||||
"moonbitlang/async/aqueue",
|
||||
"moonbitlang/core/env",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
"Nanaloveyuki/BitLogger/src/core",
|
||||
"Nanaloveyuki/BitLogger/src/runtime",
|
||||
"Nanaloveyuki/BitLogger/src/sink_graph",
|
||||
"maria/json_parser",
|
||||
"moonbitlang/async/aqueue",
|
||||
"moonbitlang/core/ref",
|
||||
}
|
||||
@@ -13,11 +14,11 @@ import {
|
||||
// Values
|
||||
pub fn[S] async_logger(S, config? : @utils.AsyncLoggerConfig, min_level? : @core.Level, target? : String, flush? : (S) -> Unit raise) -> AsyncLogger[S]
|
||||
|
||||
pub fn async_logger_build_config_to_json(@utils.AsyncLoggerBuildConfig) -> Json
|
||||
pub fn async_logger_build_config_to_json(@utils.AsyncLoggerBuildConfig) -> @json_parser.JsonValue
|
||||
|
||||
pub fn async_logger_config_to_json(@utils.AsyncLoggerConfig) -> Json
|
||||
pub fn async_logger_config_to_json(@utils.AsyncLoggerConfig) -> @json_parser.JsonValue
|
||||
|
||||
pub fn async_logger_state_to_json(@utils.AsyncLoggerState) -> Json
|
||||
pub fn async_logger_state_to_json(@utils.AsyncLoggerState) -> @json_parser.JsonValue
|
||||
|
||||
pub fn async_runtime_mode() -> @utils.AsyncRuntimeMode
|
||||
|
||||
@@ -25,7 +26,7 @@ pub fn async_runtime_mode_label(@utils.AsyncRuntimeMode) -> String
|
||||
|
||||
pub fn async_runtime_state() -> @utils.AsyncRuntimeState
|
||||
|
||||
pub fn async_runtime_state_to_json(@utils.AsyncRuntimeState) -> Json
|
||||
pub fn async_runtime_state_to_json(@utils.AsyncRuntimeState) -> @json_parser.JsonValue
|
||||
|
||||
pub fn async_runtime_supports_background_worker() -> Bool
|
||||
|
||||
@@ -114,7 +115,6 @@ pub fn[S] AsyncLogger::with_min_level(Self[S], @core.Level) -> Self[S]
|
||||
pub fn[S] AsyncLogger::with_patch(Self[S], (@core.Record) -> @core.Record) -> Self[S]
|
||||
pub fn[S] AsyncLogger::with_target(Self[S], String) -> Self[S]
|
||||
pub fn[S] AsyncLogger::with_timestamp(Self[S], enabled? : Bool) -> Self[S]
|
||||
pub fn[S] AsyncLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[S]
|
||||
|
||||
pub struct LibraryAsyncLogger[S] {
|
||||
inner : AsyncLogger[S]
|
||||
@@ -132,7 +132,6 @@ pub fn[S] LibraryAsyncLogger::to_async_logger(Self[S]) -> AsyncLogger[S]
|
||||
pub async fn[S] LibraryAsyncLogger::warn(Self[S], String, fields? : Array[@core.Field]) -> Unit
|
||||
pub fn[S] LibraryAsyncLogger::with_context_fields(Self[S], Array[@core.Field]) -> Self[S]
|
||||
pub fn[S] LibraryAsyncLogger::with_target(Self[S], String) -> Self[S]
|
||||
pub fn[S] LibraryAsyncLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[S]
|
||||
|
||||
// Type aliases
|
||||
pub type ApplicationAsyncLogger = AsyncLogger[@runtime.RuntimeSink]
|
||||
|
||||
@@ -154,10 +154,12 @@ pub fn AsyncLoggerState::new(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {
|
||||
Json::object({
|
||||
"mode": Json::string(async_runtime_mode_label(state.mode)),
|
||||
"background_worker": Json::boolean(state.background_worker),
|
||||
pub fn async_runtime_state_to_json(
|
||||
state : AsyncRuntimeState,
|
||||
) -> @json_parser.JsonValue {
|
||||
@json_parser.JsonValue::Object({
|
||||
"mode": @json_parser.JsonValue::String(async_runtime_mode_label(state.mode)),
|
||||
"background_worker": @json_parser.JsonValue::Bool(state.background_worker),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,9 +170,9 @@ pub fn stringify_async_runtime_state(
|
||||
) -> String {
|
||||
let value = async_runtime_state_to_json(state)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,25 +186,37 @@ fn async_flush_policy_label(policy : AsyncFlushPolicy) -> String {
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_logger_state_to_json_value(state : AsyncLoggerState) -> Json {
|
||||
Json::object({
|
||||
fn async_logger_state_to_json_value(
|
||||
state : AsyncLoggerState,
|
||||
) -> @json_parser.JsonValue {
|
||||
@json_parser.JsonValue::Object({
|
||||
"runtime": async_runtime_state_to_json(state.runtime),
|
||||
"phase": Json::string(async_lifecycle_phase_label(state.phase)),
|
||||
"pending_count": Json::number(state.pending_count.to_double()),
|
||||
"dropped_count": Json::number(state.dropped_count.to_double()),
|
||||
"is_closed": Json::boolean(state.is_closed),
|
||||
"is_running": Json::boolean(state.is_running),
|
||||
"has_failed": Json::boolean(state.has_failed),
|
||||
"backlog_retained": Json::boolean(state.backlog_retained),
|
||||
"can_rerun": Json::boolean(state.can_rerun),
|
||||
"terminal": Json::boolean(state.terminal),
|
||||
"last_error": Json::string(state.last_error),
|
||||
"flush_policy": Json::string(async_flush_policy_label(state.flush_policy)),
|
||||
"phase": @json_parser.JsonValue::String(
|
||||
async_lifecycle_phase_label(state.phase),
|
||||
),
|
||||
"pending_count": @json_parser.JsonValue::Number(
|
||||
state.pending_count.to_double(),
|
||||
),
|
||||
"dropped_count": @json_parser.JsonValue::Number(
|
||||
state.dropped_count.to_double(),
|
||||
),
|
||||
"is_closed": @json_parser.JsonValue::Bool(state.is_closed),
|
||||
"is_running": @json_parser.JsonValue::Bool(state.is_running),
|
||||
"has_failed": @json_parser.JsonValue::Bool(state.has_failed),
|
||||
"backlog_retained": @json_parser.JsonValue::Bool(state.backlog_retained),
|
||||
"can_rerun": @json_parser.JsonValue::Bool(state.can_rerun),
|
||||
"terminal": @json_parser.JsonValue::Bool(state.terminal),
|
||||
"last_error": @json_parser.JsonValue::String(state.last_error),
|
||||
"flush_policy": @json_parser.JsonValue::String(
|
||||
async_flush_policy_label(state.flush_policy),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {
|
||||
pub fn async_logger_state_to_json(
|
||||
state : AsyncLoggerState,
|
||||
) -> @json_parser.JsonValue {
|
||||
async_logger_state_to_json_value(state)
|
||||
}
|
||||
|
||||
@@ -213,9 +227,9 @@ pub fn stringify_async_logger_state(
|
||||
) -> String {
|
||||
let value = async_logger_state_to_json_value(state)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,38 +293,53 @@ fn parse_async_flush(name : String) -> AsyncFlushPolicy raise {
|
||||
pub fn parse_async_logger_config_text(
|
||||
input : String,
|
||||
) -> AsyncLoggerConfig raise {
|
||||
let root = @json.parse(input)
|
||||
let obj = match root {
|
||||
Json::Object(obj) => obj
|
||||
_ => raise Failure::Failure("Expected object for async logger config")
|
||||
let root = @json_parser.parse(input)
|
||||
let obj = match root.as_object() {
|
||||
Some(obj) => obj
|
||||
None => raise Failure::Failure("Expected object for async logger config")
|
||||
}
|
||||
let max_pending = match obj.get("max_pending") {
|
||||
Some(Json::Number(number, ..)) => number.to_int()
|
||||
Some(_) =>
|
||||
raise Failure::Failure("Expected number at async_config.max_pending")
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise Failure::Failure("Expected number at async_config.max_pending")
|
||||
}
|
||||
None => 0
|
||||
}
|
||||
let overflow = match obj.get("overflow") {
|
||||
Some(Json::String(text)) => parse_async_overflow(text)
|
||||
Some(_) =>
|
||||
raise Failure::Failure("Expected string at async_config.overflow")
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => parse_async_overflow(text)
|
||||
None =>
|
||||
raise Failure::Failure("Expected string at async_config.overflow")
|
||||
}
|
||||
None => AsyncOverflowPolicy::Blocking
|
||||
}
|
||||
let max_batch = match obj.get("max_batch") {
|
||||
Some(Json::Number(number, ..)) => number.to_int()
|
||||
Some(_) =>
|
||||
raise Failure::Failure("Expected number at async_config.max_batch")
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise Failure::Failure("Expected number at async_config.max_batch")
|
||||
}
|
||||
None => 1
|
||||
}
|
||||
let linger_ms = match obj.get("linger_ms") {
|
||||
Some(Json::Number(number, ..)) => number.to_int()
|
||||
Some(_) =>
|
||||
raise Failure::Failure("Expected number at async_config.linger_ms")
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise Failure::Failure("Expected number at async_config.linger_ms")
|
||||
}
|
||||
None => 0
|
||||
}
|
||||
let flush = match obj.get("flush") {
|
||||
Some(Json::String(text)) => parse_async_flush(text)
|
||||
Some(_) => raise Failure::Failure("Expected string at async_config.flush")
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => parse_async_flush(text)
|
||||
None => raise Failure::Failure("Expected string at async_config.flush")
|
||||
}
|
||||
None => AsyncFlushPolicy::Never
|
||||
}
|
||||
AsyncLoggerConfig::new(
|
||||
@@ -323,19 +352,23 @@ pub fn parse_async_logger_config_text(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {
|
||||
Json::object({
|
||||
"max_pending": Json::number(config.max_pending.to_double()),
|
||||
"max_batch": Json::number(config.max_batch.to_double()),
|
||||
"linger_ms": Json::number(config.linger_ms.to_double()),
|
||||
"overflow": Json::string(
|
||||
pub fn async_logger_config_to_json(
|
||||
config : AsyncLoggerConfig,
|
||||
) -> @json_parser.JsonValue {
|
||||
@json_parser.JsonValue::Object({
|
||||
"max_pending": @json_parser.JsonValue::Number(
|
||||
config.max_pending.to_double(),
|
||||
),
|
||||
"max_batch": @json_parser.JsonValue::Number(config.max_batch.to_double()),
|
||||
"linger_ms": @json_parser.JsonValue::Number(config.linger_ms.to_double()),
|
||||
"overflow": @json_parser.JsonValue::String(
|
||||
match config.overflow {
|
||||
AsyncOverflowPolicy::Blocking => "Blocking"
|
||||
AsyncOverflowPolicy::DropOldest => "DropOldest"
|
||||
AsyncOverflowPolicy::DropNewest => "DropNewest"
|
||||
},
|
||||
),
|
||||
"flush": Json::string(
|
||||
"flush": @json_parser.JsonValue::String(
|
||||
match config.flush {
|
||||
AsyncFlushPolicy::Never => "Never"
|
||||
AsyncFlushPolicy::Batch => "Batch"
|
||||
@@ -352,9 +385,9 @@ pub fn stringify_async_logger_config(
|
||||
) -> String {
|
||||
let value = async_logger_config_to_json(config)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,20 +409,21 @@ pub fn AsyncLoggerBuildConfig::new(
|
||||
pub fn parse_async_logger_build_config_text(
|
||||
input : String,
|
||||
) -> AsyncLoggerBuildConfig raise {
|
||||
let root = @json.parse(input)
|
||||
let obj = match root {
|
||||
Json::Object(obj) => obj
|
||||
_ =>
|
||||
let root = @json_parser.parse(input)
|
||||
let obj = match root.as_object() {
|
||||
Some(obj) => obj
|
||||
None =>
|
||||
raise Failure::Failure(
|
||||
"Expected object at async logger build config root",
|
||||
)
|
||||
}
|
||||
let logger = match obj.get("logger") {
|
||||
Some(value) => @bitlogger.parse_logger_config_text(value.stringify())
|
||||
Some(value) =>
|
||||
@bitlogger.parse_logger_config_text(@json_parser.stringify(value))
|
||||
None => @bitlogger.default_logger_config()
|
||||
}
|
||||
let async_config = match obj.get("async_config") {
|
||||
Some(value) => parse_async_logger_config_text(value.stringify())
|
||||
Some(value) => parse_async_logger_config_text(@json_parser.stringify(value))
|
||||
None => AsyncLoggerConfig::new()
|
||||
}
|
||||
AsyncLoggerBuildConfig::new(logger~, async_config~)
|
||||
@@ -398,8 +432,8 @@ pub fn parse_async_logger_build_config_text(
|
||||
///|
|
||||
pub fn async_logger_build_config_to_json(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> Json {
|
||||
Json::object({
|
||||
) -> @json_parser.JsonValue {
|
||||
@json_parser.JsonValue::Object({
|
||||
"logger": @bitlogger.logger_config_to_json(config.logger),
|
||||
"async_config": async_logger_config_to_json(config.async_config),
|
||||
})
|
||||
@@ -412,8 +446,8 @@ pub fn stringify_async_logger_build_config(
|
||||
) -> String {
|
||||
let value = async_logger_build_config_to_json(config)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @bitlogger,
|
||||
"moonbitlang/core/json",
|
||||
"maria/json_parser",
|
||||
}
|
||||
|
||||
@@ -3,18 +3,19 @@ package "Nanaloveyuki/BitLogger/src-async/utils"
|
||||
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src/config_model",
|
||||
"maria/json_parser",
|
||||
}
|
||||
|
||||
// Values
|
||||
pub fn async_logger_build_config_to_json(AsyncLoggerBuildConfig) -> Json
|
||||
pub fn async_logger_build_config_to_json(AsyncLoggerBuildConfig) -> @json_parser.JsonValue
|
||||
|
||||
pub fn async_logger_config_to_json(AsyncLoggerConfig) -> Json
|
||||
pub fn async_logger_config_to_json(AsyncLoggerConfig) -> @json_parser.JsonValue
|
||||
|
||||
pub fn async_logger_state_to_json(AsyncLoggerState) -> Json
|
||||
pub fn async_logger_state_to_json(AsyncLoggerState) -> @json_parser.JsonValue
|
||||
|
||||
pub fn async_runtime_mode_label(AsyncRuntimeMode) -> String
|
||||
|
||||
pub fn async_runtime_state_to_json(AsyncRuntimeState) -> Json
|
||||
pub fn async_runtime_state_to_json(AsyncRuntimeState) -> @json_parser.JsonValue
|
||||
|
||||
pub fn compatibility_async_runtime_mode() -> AsyncRuntimeMode
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
///|
|
||||
test "file sink tracks rotation failures on unavailable backend" {
|
||||
let path = "logs/bitlogger-rotate.log"
|
||||
let path = "bitlogger-rotate.log"
|
||||
ignore(@utils.remove_file_internal(path))
|
||||
ignore(@utils.remove_file_internal(path + ".1"))
|
||||
let sink = file_sink(path, rotation=Some(file_rotation(1, max_backups=1)))
|
||||
@@ -19,7 +19,7 @@ test "file sink tracks rotation failures on unavailable backend" {
|
||||
|
||||
///|
|
||||
test "file sink reopen and failure counters reflect backend state" {
|
||||
let sink = file_sink("logs/bitlogger-reopen.log")
|
||||
let sink = file_sink("bitlogger-reopen.log")
|
||||
if sink.is_available() {
|
||||
inspect(sink.append_mode(), content="true")
|
||||
inspect(sink.open_failures(), content="0")
|
||||
@@ -45,7 +45,7 @@ test "file sink reopen and failure counters reflect backend state" {
|
||||
inspect(sink.flush_failures(), content="0")
|
||||
inspect(sink.rotation_failures(), content="0")
|
||||
inspect(sink.close(), content="true")
|
||||
ignore(@utils.remove_file_internal("logs/bitlogger-reopen.log"))
|
||||
ignore(@utils.remove_file_internal("bitlogger-reopen.log"))
|
||||
} else {
|
||||
inspect(sink.append_mode(), content="true")
|
||||
inspect(sink.open_failures(), content="1")
|
||||
@@ -71,7 +71,7 @@ test "file sink reopen and failure counters reflect backend state" {
|
||||
|
||||
///|
|
||||
test "file sink can rotate on native backend" {
|
||||
let path = "logs/bitlogger-rotate-native.log"
|
||||
let path = "bitlogger-rotate-native.log"
|
||||
ignore(@utils.remove_file_internal(path))
|
||||
ignore(@utils.remove_file_internal(path + ".1"))
|
||||
ignore(@utils.remove_file_internal(path + ".2"))
|
||||
@@ -105,3 +105,57 @@ test "file sink can rotate on native backend" {
|
||||
inspect(sink.rotation_failures(), content="0")
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
#cfg(platform="windows")
|
||||
test "file sink rotation failure counts partial backup-chain failure on native backend" {
|
||||
let path = "bitlogger-rotate-partial-failure.log"
|
||||
ignore(@utils.remove_file_internal(path))
|
||||
ignore(@utils.remove_file_internal(path + ".1"))
|
||||
ignore(@utils.remove_file_internal(path + ".2"))
|
||||
|
||||
let seed = file_sink(
|
||||
path,
|
||||
auto_flush=true,
|
||||
rotation=Some(file_rotation(20, max_backups=2)),
|
||||
formatter=fn(rec) { rec.message },
|
||||
)
|
||||
if seed.is_available() {
|
||||
seed.write(record(Level::Info, "1234567890"))
|
||||
seed.write(record(Level::Info, "abcdefghij"))
|
||||
inspect(seed.rotation_failures(), content="0")
|
||||
inspect(seed.close(), content="true")
|
||||
|
||||
let locked = @utils.open_file_handle_internal(path + ".2", true)
|
||||
inspect(locked is Some(_), content="true")
|
||||
|
||||
let sink = file_sink(
|
||||
path,
|
||||
auto_flush=true,
|
||||
rotation=Some(file_rotation(20, max_backups=2)),
|
||||
formatter=fn(rec) { rec.message },
|
||||
)
|
||||
inspect(sink.is_available(), content="true")
|
||||
sink.write(record(Level::Info, "klmnopqrst"))
|
||||
|
||||
inspect(sink.rotation_failures(), content="1")
|
||||
inspect(sink.state().rotation_failures, content="1")
|
||||
inspect(sink.write_failures(), content="1")
|
||||
inspect(sink.is_available(), content="false")
|
||||
|
||||
match locked {
|
||||
None => ()
|
||||
Some(handle) => ignore(@utils.close_file_handle_internal(handle))
|
||||
}
|
||||
|
||||
inspect(sink.reopen_append(), content="true")
|
||||
inspect(sink.is_available(), content="true")
|
||||
inspect(sink.close(), content="true")
|
||||
|
||||
ignore(@utils.remove_file_internal(path))
|
||||
ignore(@utils.remove_file_internal(path + ".1"))
|
||||
ignore(@utils.remove_file_internal(path + ".2"))
|
||||
} else {
|
||||
inspect(native_files_supported(), content="false")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ test "application logger keeps configured runtime helper surface" {
|
||||
LoggerConfig::new(
|
||||
min_level=Level::Warn,
|
||||
target="app.alias.helpers",
|
||||
sink=SinkConfig::new(kind=SinkKind::File, path="logs/app-alias.log"),
|
||||
sink=SinkConfig::new(kind=SinkKind::File, path="app-alias.log"),
|
||||
queue=Some(QueueConfig::new(2, overflow=QueueOverflowPolicy::DropOldest)),
|
||||
),
|
||||
)
|
||||
@@ -229,10 +229,7 @@ test "application logger file helpers match direct configured builder behavior"
|
||||
let config = LoggerConfig::new(
|
||||
min_level=Level::Warn,
|
||||
target="app.file.same-build",
|
||||
sink=SinkConfig::new(
|
||||
kind=SinkKind::File,
|
||||
path="logs/app-file-same-build.log",
|
||||
),
|
||||
sink=SinkConfig::new(kind=SinkKind::File, path="app-file-same-build.log"),
|
||||
queue=Some(QueueConfig::new(2, overflow=QueueOverflowPolicy::DropOldest)),
|
||||
)
|
||||
let application = build_application_logger(config)
|
||||
@@ -347,7 +344,7 @@ test "application logger file controls match direct configured builder behavior"
|
||||
target="app.file.controls.same-build",
|
||||
sink=SinkConfig::new(
|
||||
kind=SinkKind::File,
|
||||
path="logs/app-file-controls-same-build.log",
|
||||
path="app-file-controls-same-build.log",
|
||||
),
|
||||
)
|
||||
let application = build_application_logger(config)
|
||||
|
||||
+9
-5
@@ -39,7 +39,7 @@ pub fn parse_logger_config_text(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn queue_config_to_json(queue : QueueConfig) -> Json {
|
||||
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
|
||||
@config_model.queue_config_to_json(queue)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ pub fn stringify_queue_config(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {
|
||||
pub fn text_formatter_config_to_json(
|
||||
config : TextFormatterConfig,
|
||||
) -> @json_parser.JsonValue {
|
||||
@config_model.text_formatter_config_to_json(config)
|
||||
}
|
||||
|
||||
@@ -65,12 +67,14 @@ pub fn stringify_text_formatter_config(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn file_rotation_config_to_json(config : FileRotation) -> Json {
|
||||
pub fn file_rotation_config_to_json(
|
||||
config : FileRotation,
|
||||
) -> @json_parser.JsonValue {
|
||||
@file_model.file_rotation_config_to_json(config)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn sink_config_to_json(config : SinkConfig) -> Json {
|
||||
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
|
||||
@config_model.sink_config_to_json(config)
|
||||
}
|
||||
|
||||
@@ -83,7 +87,7 @@ pub fn stringify_sink_config(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn logger_config_to_json(config : LoggerConfig) -> Json {
|
||||
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
|
||||
@config_model.logger_config_to_json(config)
|
||||
}
|
||||
|
||||
|
||||
+119
-91
@@ -42,7 +42,7 @@ pub fn TextFormatterConfig::new(
|
||||
style_markup? : @formatting.StyleMarkupMode = @formatting.StyleMarkupMode::Full,
|
||||
target_style_markup? : @formatting.StyleMarkupMode = @formatting.StyleMarkupMode::Disabled,
|
||||
fields_style_markup? : @formatting.StyleMarkupMode = @formatting.StyleMarkupMode::Disabled,
|
||||
style_tags? : Map[String, @formatting.TextStyle] = Map([]),
|
||||
style_tags? : Map[String, @formatting.TextStyle] = {},
|
||||
) -> TextFormatterConfig {
|
||||
{
|
||||
show_timestamp,
|
||||
@@ -170,84 +170,102 @@ pub fn default_logger_config() -> LoggerConfig {
|
||||
|
||||
///|
|
||||
fn expect_object(
|
||||
value : Json,
|
||||
value : @json_parser.JsonValue,
|
||||
context : String,
|
||||
) -> Map[String, Json] raise ConfigError {
|
||||
match value {
|
||||
Json::Object(obj) => obj
|
||||
_ => raise ConfigError::InvalidConfig("Expected object at " + context)
|
||||
) -> Map[String, @json_parser.JsonValue] raise ConfigError {
|
||||
match value.as_object() {
|
||||
Some(obj) => obj
|
||||
None => raise ConfigError::InvalidConfig("Expected object at " + context)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_string(
|
||||
obj : Map[String, Json],
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
default? : String = "",
|
||||
) -> String raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => default
|
||||
Some(Json::String(text)) => text
|
||||
Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => text
|
||||
None =>
|
||||
raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_optional_string(
|
||||
obj : Map[String, Json],
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
) -> String? raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => None
|
||||
Some(Json::String(text)) => Some(text)
|
||||
Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => Some(text)
|
||||
None =>
|
||||
raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_bool(
|
||||
obj : Map[String, Json],
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
default~ : Bool,
|
||||
) -> Bool raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => default
|
||||
Some(Json::True) => true
|
||||
Some(Json::False) => false
|
||||
Some(_) => raise ConfigError::InvalidConfig("Expected bool at key " + key)
|
||||
Some(value) =>
|
||||
match value.as_bool() {
|
||||
Some(flag) => flag
|
||||
None => raise ConfigError::InvalidConfig("Expected bool at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_int(
|
||||
obj : Map[String, Json],
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
default~ : Int,
|
||||
) -> Int raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => default
|
||||
Some(Json::Number(number, ..)) => number.to_int()
|
||||
Some(_) => raise ConfigError::InvalidConfig("Expected number at key " + key)
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise ConfigError::InvalidConfig("Expected number at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_optional_int64_string(
|
||||
obj : Map[String, Json],
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
) -> Int64? raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => None
|
||||
Some(Json::String(text)) =>
|
||||
Some(
|
||||
@string.parse_int64(text) catch {
|
||||
_ =>
|
||||
raise ConfigError::InvalidConfig(
|
||||
"Expected int64 string at key " + key,
|
||||
)
|
||||
},
|
||||
)
|
||||
Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) =>
|
||||
Some(
|
||||
@string.parse_int64(text) catch {
|
||||
_ =>
|
||||
raise ConfigError::InvalidConfig(
|
||||
"Expected int64 string at key " + key,
|
||||
)
|
||||
},
|
||||
)
|
||||
None =>
|
||||
raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +355,7 @@ fn sink_kind_label(kind : SinkKind) -> String {
|
||||
|
||||
///|
|
||||
fn parse_text_formatter_config(
|
||||
value : Json,
|
||||
value : @json_parser.JsonValue,
|
||||
) -> TextFormatterConfig raise ConfigError {
|
||||
let obj = expect_object(value, "text_formatter")
|
||||
TextFormatterConfig::new(
|
||||
@@ -362,7 +380,7 @@ fn parse_text_formatter_config(
|
||||
get_string(obj, "fields_style_markup", default="disabled"),
|
||||
),
|
||||
style_tags=match obj.get("style_tags") {
|
||||
None => Map([])
|
||||
None => {}
|
||||
Some(inner) => parse_style_tags_config(inner)
|
||||
},
|
||||
)
|
||||
@@ -370,7 +388,7 @@ fn parse_text_formatter_config(
|
||||
|
||||
///|
|
||||
fn parse_text_style_config(
|
||||
value : Json,
|
||||
value : @json_parser.JsonValue,
|
||||
context : String,
|
||||
) -> @formatting.TextStyle raise ConfigError {
|
||||
let obj = expect_object(value, context)
|
||||
@@ -386,10 +404,10 @@ fn parse_text_style_config(
|
||||
|
||||
///|
|
||||
fn parse_style_tags_config(
|
||||
value : Json,
|
||||
value : @json_parser.JsonValue,
|
||||
) -> Map[String, @formatting.TextStyle] raise ConfigError {
|
||||
let obj = expect_object(value, "text_formatter.style_tags")
|
||||
let style_tags : Map[String, @formatting.TextStyle] = Map([])
|
||||
let style_tags : Map[String, @formatting.TextStyle] = {}
|
||||
for name, item in obj {
|
||||
style_tags[name] = parse_text_style_config(
|
||||
item,
|
||||
@@ -400,7 +418,9 @@ fn parse_style_tags_config(
|
||||
}
|
||||
|
||||
///|
|
||||
fn parse_queue_config(value : Json) -> QueueConfig raise ConfigError {
|
||||
fn parse_queue_config(
|
||||
value : @json_parser.JsonValue,
|
||||
) -> QueueConfig raise ConfigError {
|
||||
let obj = expect_object(value, "queue")
|
||||
QueueConfig::new(
|
||||
get_int(obj, "max_pending", default=0),
|
||||
@@ -410,7 +430,7 @@ fn parse_queue_config(value : Json) -> QueueConfig raise ConfigError {
|
||||
|
||||
///|
|
||||
fn parse_file_rotation_config(
|
||||
value : Json,
|
||||
value : @json_parser.JsonValue,
|
||||
) -> @file_model.FileRotation raise ConfigError {
|
||||
let obj = expect_object(value, "sink.rotation")
|
||||
let max_backups = get_int(obj, "max_backups", default=1)
|
||||
@@ -426,7 +446,9 @@ fn parse_file_rotation_config(
|
||||
}
|
||||
|
||||
///|
|
||||
fn parse_sink_config(value : Json) -> SinkConfig raise ConfigError {
|
||||
fn parse_sink_config(
|
||||
value : @json_parser.JsonValue,
|
||||
) -> SinkConfig raise ConfigError {
|
||||
let obj = expect_object(value, "sink")
|
||||
let kind = parse_sink_kind(get_string(obj, "kind", default="console"))
|
||||
let formatter = match obj.get("text_formatter") {
|
||||
@@ -458,7 +480,7 @@ fn parse_sink_config(value : Json) -> SinkConfig raise ConfigError {
|
||||
pub fn parse_logger_config_text(
|
||||
input : String,
|
||||
) -> LoggerConfig raise ConfigError {
|
||||
let root = @json.parse(input) catch {
|
||||
let root = @json_parser.parse(input) catch {
|
||||
e => raise ConfigError::InvalidConfig("Invalid JSON: " + e.to_string())
|
||||
}
|
||||
let obj = expect_object(root, "root")
|
||||
@@ -478,10 +500,10 @@ pub fn parse_logger_config_text(
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn queue_config_to_json(queue : QueueConfig) -> Json {
|
||||
Json::object({
|
||||
"max_pending": Json::number(queue.max_pending.to_double()),
|
||||
"overflow": Json::string(
|
||||
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
|
||||
@json_parser.JsonValue::Object({
|
||||
"max_pending": @json_parser.JsonValue::Number(queue.max_pending.to_double()),
|
||||
"overflow": @json_parser.JsonValue::String(
|
||||
match queue.overflow {
|
||||
@queue_model.QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
@queue_model.QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
@@ -497,70 +519,76 @@ pub fn stringify_queue_config(
|
||||
) -> String {
|
||||
let value = queue_config_to_json(queue)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {
|
||||
let obj : Map[String, Json] = {
|
||||
"show_timestamp": Json::boolean(config.show_timestamp),
|
||||
"show_level": Json::boolean(config.show_level),
|
||||
"show_target": Json::boolean(config.show_target),
|
||||
"show_fields": Json::boolean(config.show_fields),
|
||||
"separator": Json::string(config.separator),
|
||||
"field_separator": Json::string(config.field_separator),
|
||||
"template": Json::string(config.template),
|
||||
"color_mode": Json::string(@formatting.color_mode_label(config.color_mode)),
|
||||
"color_support": Json::string(
|
||||
pub fn text_formatter_config_to_json(
|
||||
config : TextFormatterConfig,
|
||||
) -> @json_parser.JsonValue {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {
|
||||
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp),
|
||||
"show_level": @json_parser.JsonValue::Bool(config.show_level),
|
||||
"show_target": @json_parser.JsonValue::Bool(config.show_target),
|
||||
"show_fields": @json_parser.JsonValue::Bool(config.show_fields),
|
||||
"separator": @json_parser.JsonValue::String(config.separator),
|
||||
"field_separator": @json_parser.JsonValue::String(config.field_separator),
|
||||
"template": @json_parser.JsonValue::String(config.template),
|
||||
"color_mode": @json_parser.JsonValue::String(
|
||||
@formatting.color_mode_label(config.color_mode),
|
||||
),
|
||||
"color_support": @json_parser.JsonValue::String(
|
||||
@formatting.color_support_label(config.color_support),
|
||||
),
|
||||
"style_markup": Json::string(
|
||||
"style_markup": @json_parser.JsonValue::String(
|
||||
@formatting.style_markup_mode_label(config.style_markup),
|
||||
),
|
||||
"target_style_markup": Json::string(
|
||||
"target_style_markup": @json_parser.JsonValue::String(
|
||||
@formatting.style_markup_mode_label(config.target_style_markup),
|
||||
),
|
||||
"fields_style_markup": Json::string(
|
||||
"fields_style_markup": @json_parser.JsonValue::String(
|
||||
@formatting.style_markup_mode_label(config.fields_style_markup),
|
||||
),
|
||||
}
|
||||
if config.style_tags.length() != 0 {
|
||||
obj["style_tags"] = style_tags_config_to_json(config.style_tags)
|
||||
}
|
||||
Json::object(obj)
|
||||
@json_parser.JsonValue::Object(obj)
|
||||
}
|
||||
|
||||
///|
|
||||
fn text_style_config_to_json(style : @formatting.TextStyle) -> Json {
|
||||
let obj : Map[String, Json] = {
|
||||
"bold": Json::boolean(style.bold),
|
||||
"dim": Json::boolean(style.dim),
|
||||
"italic": Json::boolean(style.italic),
|
||||
"underline": Json::boolean(style.underline),
|
||||
fn text_style_config_to_json(
|
||||
style : @formatting.TextStyle,
|
||||
) -> @json_parser.JsonValue {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {
|
||||
"bold": @json_parser.JsonValue::Bool(style.bold),
|
||||
"dim": @json_parser.JsonValue::Bool(style.dim),
|
||||
"italic": @json_parser.JsonValue::Bool(style.italic),
|
||||
"underline": @json_parser.JsonValue::Bool(style.underline),
|
||||
}
|
||||
match style.fg {
|
||||
Some(value) => obj["fg"] = Json::string(value)
|
||||
Some(value) => obj["fg"] = @json_parser.JsonValue::String(value)
|
||||
None => ()
|
||||
}
|
||||
match style.bg {
|
||||
Some(value) => obj["bg"] = Json::string(value)
|
||||
Some(value) => obj["bg"] = @json_parser.JsonValue::String(value)
|
||||
None => ()
|
||||
}
|
||||
Json::object(obj)
|
||||
@json_parser.JsonValue::Object(obj)
|
||||
}
|
||||
|
||||
///|
|
||||
fn style_tags_config_to_json(
|
||||
style_tags : Map[String, @formatting.TextStyle],
|
||||
) -> Json {
|
||||
let obj : Map[String, Json] = Map([])
|
||||
) -> @json_parser.JsonValue {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {}
|
||||
for name, style in style_tags {
|
||||
obj[name] = text_style_config_to_json(style)
|
||||
}
|
||||
Json::object(obj)
|
||||
@json_parser.JsonValue::Object(obj)
|
||||
}
|
||||
|
||||
///|
|
||||
@@ -570,19 +598,19 @@ pub fn stringify_text_formatter_config(
|
||||
) -> String {
|
||||
let value = text_formatter_config_to_json(config)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn sink_config_to_json(config : SinkConfig) -> Json {
|
||||
let obj : Map[String, Json] = {
|
||||
"kind": Json::string(sink_kind_label(config.kind)),
|
||||
"path": Json::string(config.path),
|
||||
"append": Json::boolean(config.append),
|
||||
"auto_flush": Json::boolean(config.auto_flush),
|
||||
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {
|
||||
"kind": @json_parser.JsonValue::String(sink_kind_label(config.kind)),
|
||||
"path": @json_parser.JsonValue::String(config.path),
|
||||
"append": @json_parser.JsonValue::Bool(config.append),
|
||||
"auto_flush": @json_parser.JsonValue::Bool(config.auto_flush),
|
||||
"text_formatter": text_formatter_config_to_json(config.text_formatter),
|
||||
}
|
||||
match config.rotation {
|
||||
@@ -590,7 +618,7 @@ pub fn sink_config_to_json(config : SinkConfig) -> Json {
|
||||
Some(rotation) =>
|
||||
obj["rotation"] = @file_model.file_rotation_config_to_json(rotation)
|
||||
}
|
||||
Json::object(obj)
|
||||
@json_parser.JsonValue::Object(obj)
|
||||
}
|
||||
|
||||
///|
|
||||
@@ -600,25 +628,25 @@ pub fn stringify_sink_config(
|
||||
) -> String {
|
||||
let value = sink_config_to_json(config)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn logger_config_to_json(config : LoggerConfig) -> Json {
|
||||
let obj : Map[String, Json] = {
|
||||
"min_level": Json::string(config.min_level.label()),
|
||||
"target": Json::string(config.target),
|
||||
"timestamp": Json::boolean(config.timestamp),
|
||||
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {
|
||||
"min_level": @json_parser.JsonValue::String(config.min_level.label()),
|
||||
"target": @json_parser.JsonValue::String(config.target),
|
||||
"timestamp": @json_parser.JsonValue::Bool(config.timestamp),
|
||||
"sink": sink_config_to_json(config.sink),
|
||||
}
|
||||
match config.queue {
|
||||
None => ()
|
||||
Some(queue) => obj["queue"] = queue_config_to_json(queue)
|
||||
}
|
||||
Json::object(obj)
|
||||
@json_parser.JsonValue::Object(obj)
|
||||
}
|
||||
|
||||
///|
|
||||
@@ -628,8 +656,8 @@ pub fn stringify_logger_config(
|
||||
) -> String {
|
||||
let value = logger_config_to_json(config)
|
||||
if pretty {
|
||||
value.stringify(indent=2)
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
} else {
|
||||
value.stringify()
|
||||
@json_parser.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user