5 Commits

Author SHA1 Message Date
Nanaloveyuki 0b05ee0c94 🔖 release 0.7.3 2026-07-19 17:12:02 +08:00
Nanaloveyuki f9227890bd add wasm coverage and trace context 2026-07-19 17:04:27 +08:00
Nanaloveyuki 06db039a03 add release version helper 2026-07-17 21:26:59 +08:00
Nanaloveyuki 5ed02ec563 🔖 release 0.7.2 2026-07-17 21:22:04 +08:00
Nanaloveyuki a58a0747bb ♻️ migrate to core json 2026-07-17 21:19:11 +08:00
82 changed files with 1286 additions and 612 deletions
+10 -2
View File
@@ -67,9 +67,15 @@ jobs:
moon check --deny-warn moon check --deny-warn
moon test --deny-warn moon test --deny-warn
moon check --target native --deny-warn moon check --target native --deny-warn
moon check --target wasm --deny-warn
moon check --target wasm-gc --deny-warn moon check --target wasm-gc --deny-warn
moon check --target js --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 - name: Run sync example
shell: bash shell: bash
run: | run: |
@@ -105,15 +111,17 @@ jobs:
run: | run: |
moon update moon update
- name: Check async package on wasm-gc and js - name: Check async package on wasm, wasm-gc and js
shell: bash shell: bash
run: | run: |
moon check src-async --target wasm --deny-warn
moon check src-async --target wasm-gc --deny-warn moon check src-async --target wasm-gc --deny-warn
moon check src-async --target js --deny-warn moon check src-async --target js --deny-warn
- name: Test async package on wasm-gc and js - name: Test async package on wasm, wasm-gc and js
shell: bash shell: bash
run: | run: |
moon test src-async --target wasm --deny-warn
moon test src-async --target wasm-gc --deny-warn moon test src-async --target wasm-gc --deny-warn
moon test src-async --target js --deny-warn moon test src-async --target js --deny-warn
+3
View File
@@ -26,5 +26,8 @@ docs/.vitepress/cache/
docs/.vitepress/dist/ docs/.vitepress/dist/
docs/.vitepress/generated/ docs/.vitepress/generated/
# Benchmark package has no public API; moon info emits an empty snapshot.
benchmarks/pkg.generated.mbti
# vibecoding # vibecoding
AGENTS/ AGENTS/
+26
View File
@@ -0,0 +1,26 @@
# 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.
+4 -4
View File
@@ -17,7 +17,7 @@ BitLogger 的文档按使用路径组织:先完成一个可运行的日志输
```bash ```bash
moon new log-demo moon new log-demo
cd log-demo cd log-demo
moon add Nanaloveyuki/BitLogger@0.7.1 moon add Nanaloveyuki/BitLogger@0.7.3
``` ```
### 2. 写入第一条结构化日志 ### 2. 写入第一条结构化日志
@@ -54,9 +54,8 @@ fn main {
## 支持情况 ## 支持情况
- `BitLogger` 当前在 CI 中检查/验证的目标是 `native``js``wasm-gc` - `BitLogger` 当前在 CI 中检查/验证的目标是 `native``wasm``js``wasm-gc`
- `bitlogger_async` 当前在 CI 中检查 `native``js``wasm-gc`,测试覆盖 `native``js``wasm-gc` - `bitlogger_async` 当前在 CI 中检查和测试 `native``wasm``js``wasm-gc`
- `wasm` 目标在源码 `moon.pkg` 中保留声明,但当前未纳入 CI 验证口径
- `llvm` 目前按实验性目标处理,当前环境未完成验证 - `llvm` 目前按实验性目标处理,当前环境未完成验证
- 文件输出是 native 能力;跨端代码里建议先判断 `native_files_supported()` - 文件输出是 native 能力;跨端代码里建议先判断 `native_files_supported()`
- `src-async` 可用,但示例 `examples/async_basic` 目前仍按 native 入口提供 - `src-async` 可用,但示例 `examples/async_basic` 目前仍按 native 入口提供
@@ -69,6 +68,7 @@ fn main {
- 配置驱动构建:`build_logger(...)``build_async_logger(...)` - 配置驱动构建:`build_logger(...)``build_async_logger(...)`
- 组合能力:queue、filter、patch、fanout、split、callback - 组合能力:queue、filter、patch、fanout、split、callback
- 异步日志:独立 `src-async` package - 异步日志:独立 `src-async` package
- Trace context:标准 `trace_id``span_id``trace_flags``trace_state` 字段绑定
## 文档 ## 文档
+11
View File
@@ -0,0 +1,11 @@
# 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.
+61
View File
@@ -0,0 +1,61 @@
///|
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())
}
}
+4
View File
@@ -0,0 +1,4 @@
import {
"Nanaloveyuki/BitLogger/src" @log,
"moonbitlang/core/bench",
}
+2
View File
@@ -121,6 +121,7 @@ function buildExtendSidebar(): DefaultTheme.SidebarItem[] {
{ text: 'Sink Composition', link: '/extend/composition' }, { text: 'Sink Composition', link: '/extend/composition' },
{ text: 'Text Formatting', link: '/extend/formatting' }, { text: 'Text Formatting', link: '/extend/formatting' },
{ text: 'Target Boundaries', link: '/extend/targets' }, { text: 'Target Boundaries', link: '/extend/targets' },
{ text: 'Trace Context', link: '/extend/observability' },
] ]
} }
@@ -141,6 +142,7 @@ function buildChineseExtendSidebar(): DefaultTheme.SidebarItem[] {
{ text: 'Sink 组合', link: '/zh/extend/composition' }, { text: 'Sink 组合', link: '/zh/extend/composition' },
{ text: '文本格式与样式', link: '/zh/extend/formatting' }, { text: '文本格式与样式', link: '/zh/extend/formatting' },
{ text: '目标平台边界', link: '/zh/extend/targets' }, { text: '目标平台边界', link: '/zh/extend/targets' },
{ text: 'Trace Context', link: '/zh/extend/observability' },
] ]
} }
+3 -2
View File
@@ -18,7 +18,7 @@ The documentation is organized around complete usage flows. Start with an execut
```bash ```bash
moon new log-demo moon new log-demo
cd log-demo cd log-demo
moon add Nanaloveyuki/BitLogger@0.7.1 moon add Nanaloveyuki/BitLogger@0.7.3
``` ```
### 2. Import And Log ### 2. Import And Log
@@ -44,7 +44,7 @@ Continue with the [Examples guide](./examples/index.md): console fields, file ro
## Support Status ## Support Status
- Current local verification covers `native`, `js`, `wasm`, and `wasm-gc` for the main `src` package and `src-async` - CI checks and tests `native`, `wasm`, `js`, 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 - `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 - 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 - File output is a native capability; check `native_files_supported()` in cross-target code
@@ -58,6 +58,7 @@ Continue with the [Examples guide](./examples/index.md): console fields, file ro
- Config-based builders: `build_logger(...)` and `build_async_logger(...)` - Config-based builders: `build_logger(...)` and `build_async_logger(...)`
- Composition helpers: queue, filter, patch, fanout, split, callback - Composition helpers: queue, filter, patch, fanout, split, callback
- Separate async package under `src-async` - Separate async package under `src-async`
- Trace-context binding through standard `trace_id`, `span_id`, `trace_flags`, and `trace_state` fields
## Documentation ## Documentation
@@ -13,14 +13,14 @@ key-word:
## Async-logger-build-config-to-json ## Async-logger-build-config-to-json
Convert `AsyncLoggerBuildConfig` into a `JsonValue`. This helper exports both the base synchronous logger config and the async runtime config as one structured payload. Convert `AsyncLoggerBuildConfig` into a `Json`. This helper exports both the base synchronous logger config and the async runtime config as one structured payload.
### Interface ### Interface
```moonbit ```moonbit
pub fn async_logger_build_config_to_json( pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> @json_parser.JsonValue {} ) -> Json {}
``` ```
#### input #### input
@@ -29,7 +29,7 @@ pub fn async_logger_build_config_to_json(
#### output #### output
- `JsonValue` - Structured JSON representation of the full async build config. - `Json` - Structured JSON representation of the full async build config.
### Explanation ### Explanation
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Async-logger-config-to-json ## Async-logger-config-to-json
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. 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.
### Interface ### Interface
```moonbit ```moonbit
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> @json_parser.JsonValue {} pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> @json_parser.J
#### output #### output
- `JsonValue` - Structured JSON representation of the async config. - `Json` - Structured JSON representation of the async config.
### Explanation ### Explanation
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## Async-logger-state-to-json ## Async-logger-state-to-json
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. 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.
### Interface ### Interface
```moonbit ```moonbit
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.JsonValue {} pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.Json
#### output #### output
- `JsonValue` - Structured JSON representation of the async logger snapshot. - `Json` - Structured JSON representation of the async logger snapshot.
### Explanation ### 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: When another serializer or pipeline expects a JSON value:
```moonbit ```moonbit
let payload = async_logger_state_to_json(logger.state()) let payload = async_logger_state_to_json(logger.state())
println(@json_parser.stringify(payload)) println(payload.stringify())
``` ```
In this example, the helper stays useful even outside the built-in stringify wrapper. In this example, the helper stays useful even outside the built-in stringify wrapper.
@@ -0,0 +1,21 @@
---
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.
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Async-runtime-state-to-json ## Async-runtime-state-to-json
Convert `AsyncRuntimeState` into a `JsonValue`. This helper exports the async runtime mode and background worker capability in a structured form. Convert `AsyncRuntimeState` into a `Json`. This helper exports the async runtime mode and background worker capability in a structured form.
### Interface ### Interface
```moonbit ```moonbit
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> @json_parser.JsonValue {} pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> @json_parser.Js
#### output #### output
- `JsonValue` - Structured JSON representation of the runtime state. - `Json` - Structured JSON representation of the runtime state.
### Explanation ### Explanation
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## File-rotation-config-to-json ## File-rotation-config-to-json
Convert `FileRotation` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`. Convert `FileRotation` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface ### Interface
```moonbit ```moonbit
pub fn file_rotation_config_to_json(config : FileRotation) -> @json_parser.JsonValue {} pub fn file_rotation_config_to_json(config : FileRotation) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn file_rotation_config_to_json(config : FileRotation) -> @json_parser.JsonV
#### output #### output
- `JsonValue` - Structured JSON representation of the rotation policy. - `Json` - Structured JSON representation of the rotation policy.
### Explanation ### Explanation
@@ -82,7 +82,7 @@ e.g.:
### Notes ### Notes
1. Use this helper when downstream code expects `JsonValue` rather than a typed `FileRotation` value. 1. Use this helper when downstream code expects `Json` rather than a typed `FileRotation` value.
2. Pair it with `file_rotation(...)` when building rotation policy in code before export. 2. Pair it with `file_rotation(...)` when building rotation policy in code before export.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## File-sink-policy-to-json ## File-sink-policy-to-json
Convert `FileSinkPolicy` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`. Convert `FileSinkPolicy` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface ### Interface
```moonbit ```moonbit
pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> @json_parser.JsonValue {} pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> @json_parser.JsonVal
#### output #### output
- `JsonValue` - Structured JSON representation of the file policy. - `Json` - Structured JSON representation of the file policy.
### Explanation ### Explanation
@@ -73,7 +73,7 @@ e.g.:
### Notes ### Notes
1. Use this helper when downstream code expects `JsonValue` rather than text. 1. Use this helper when downstream code expects `Json` rather than text.
2. It pairs naturally with `FileSink::policy()`, `RuntimeSink::file_policy()`, and related default-policy accessors. 2. It pairs naturally with `FileSink::policy()`, `RuntimeSink::file_policy()`, and related default-policy accessors.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## File-sink-state-to-json ## File-sink-state-to-json
Convert `FileSinkState` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`. Convert `FileSinkState` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface ### Interface
```moonbit ```moonbit
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue {} pub fn file_sink_state_to_json(state : FileSinkState) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue
#### output #### output
- `JsonValue` - Structured JSON representation of the file sink state. - `Json` - Structured JSON representation of the file sink state.
### Explanation ### Explanation
@@ -72,7 +72,7 @@ e.g.:
### Notes ### Notes
1. Use this helper when diagnostics consumers expect `JsonValue`. 1. Use this helper when diagnostics consumers expect `Json`.
2. It pairs naturally with `FileSink::state()`, `RuntimeSink::file_state()`, and `ConfiguredLogger::file_state()`. 2. It pairs naturally with `FileSink::state()`, `RuntimeSink::file_state()`, and `ConfiguredLogger::file_state()`.
+8
View File
@@ -31,6 +31,7 @@ BitLogger API navigation.
- [logger-with-timestamp.md](./logger-with-timestamp.md) - [logger-with-timestamp.md](./logger-with-timestamp.md)
- [logger-with-context-fields.md](./logger-with-context-fields.md) - [logger-with-context-fields.md](./logger-with-context-fields.md)
- [logger-bind.md](./logger-bind.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-filter.md](./logger-with-filter.md)
- [logger-with-patch.md](./logger-with-patch.md) - [logger-with-patch.md](./logger-with-patch.md)
- [logger-with-queue.md](./logger-with-queue.md) - [logger-with-queue.md](./logger-with-queue.md)
@@ -99,6 +100,10 @@ BitLogger API navigation.
- [fields.md](./fields.md) - [fields.md](./fields.md)
- [text-formatter-config-type.md](./text-formatter-config-type.md) - [text-formatter-config-type.md](./text-formatter-config-type.md)
## Observability
- [trace-context.md](./trace-context.md)
## Record and level ## Record and level
- [record.md](./record.md) - [record.md](./record.md)
@@ -266,6 +271,7 @@ BitLogger API navigation.
- [async-logger-with-min-level.md](./async-logger-with-min-level.md) - [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-timestamp.md](./async-logger-with-timestamp.md)
- [async-logger-with-context-fields.md](./async-logger-with-context-fields.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-filter.md](./async-logger-with-filter.md)
- [async-logger-with-patch.md](./async-logger-with-patch.md) - [async-logger-with-patch.md](./async-logger-with-patch.md)
@@ -322,6 +328,7 @@ BitLogger API navigation.
- [library-logger-child.md](./library-logger-child.md) - [library-logger-child.md](./library-logger-child.md)
- [library-logger-with-context-fields.md](./library-logger-with-context-fields.md) - [library-logger-with-context-fields.md](./library-logger-with-context-fields.md)
- [library-logger-bind.md](./library-logger-bind.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-is-enabled.md](./library-logger-is-enabled.md)
- [library-logger-log.md](./library-logger-log.md) - [library-logger-log.md](./library-logger-log.md)
- [library-logger-info.md](./library-logger-info.md) - [library-logger-info.md](./library-logger-info.md)
@@ -341,6 +348,7 @@ BitLogger API navigation.
- [library-async-logger-child.md](./library-async-logger-child.md) - [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-with-context-fields.md](./library-async-logger-with-context-fields.md)
- [library-async-logger-bind.md](./library-async-logger-bind.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-is-enabled.md](./library-async-logger-is-enabled.md)
- [library-async-logger-log.md](./library-async-logger-log.md) - [library-async-logger-log.md](./library-async-logger-log.md)
- [library-async-logger-info.md](./library-async-logger-info.md) - [library-async-logger-info.md](./library-async-logger-info.md)
@@ -0,0 +1,15 @@
---
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.
@@ -0,0 +1,20 @@
---
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.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## Logger-config-to-json ## Logger-config-to-json
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. 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.
### Interface ### Interface
```moonbit ```moonbit
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {} pub fn logger_config_to_json(config : LoggerConfig) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {}
#### output #### output
- `JsonValue` - JSON representation of the logger config. - `Json` - JSON representation of the logger config.
### Explanation ### Explanation
@@ -73,5 +73,5 @@ e.g.:
1. Use this helper when you need a reusable JSON value rather than a final JSON string. 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 `JsonValue`. 2. Use `stringify_logger_config(...)` when the next consumer expects JSON text instead of `Json`.
+22
View File
@@ -0,0 +1,22 @@
---
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.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## Queue-config-to-json ## Queue-config-to-json
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. 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.
### Interface ### Interface
```moonbit ```moonbit
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {} pub fn queue_config_to_json(queue : QueueConfig) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {}
#### output #### output
- `JsonValue` - Structured JSON representation of the queue config. - `Json` - Structured JSON representation of the queue config.
### Explanation ### Explanation
@@ -74,5 +74,5 @@ e.g.:
1. Use this helper when you need a reusable JSON value rather than a final JSON string. 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 `JsonValue`. 2. Use `stringify_queue_config(...)` when the next consumer expects text instead of `Json`.
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Runtime-file-state-to-json ## Runtime-file-state-to-json
Convert `RuntimeFileState` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`. Convert `RuntimeFileState` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface ### Interface
```moonbit ```moonbit
pub fn runtime_file_state_to_json(state : RuntimeFileState) -> @json_parser.JsonValue {} pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn runtime_file_state_to_json(state : RuntimeFileState) -> @json_parser.Json
#### output #### output
- `JsonValue` - Structured JSON representation of the runtime file state. - `Json` - Structured JSON representation of the runtime file state.
### Explanation ### Explanation
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Sink-config-to-json ## Sink-config-to-json
Convert `SinkConfig` into a `JsonValue`. This helper is used directly for sink export and indirectly when exporting `LoggerConfig`. Convert `SinkConfig` into a `Json`. This helper is used directly for sink export and indirectly when exporting `LoggerConfig`.
### Interface ### Interface
```moonbit ```moonbit
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {} pub fn sink_config_to_json(config : SinkConfig) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {}
#### output #### output
- `JsonValue` - JSON representation of the sink configuration. - `Json` - JSON representation of the sink configuration.
### Explanation ### Explanation
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON. - `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON for human inspection. - `pretty=true` returns indented JSON for human inspection.
- This helper is built on top of `async_logger_build_config_to_json(...)`. - This helper is built on top of `async_logger_build_config_to_json(...)`.
- 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. - 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.
- The output keeps `logger` and `async_config` as separate sections, matching supported parser input. - 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. - 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`. - 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 ### Error Case
e.g.: e.g.:
- If callers need a `JsonValue` for further composition, they should use `async_logger_build_config_to_json(...)` instead. - If callers need a `Json` 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. - 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. 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 `JsonValue` for composition before final stringification. 5. Use `async_logger_build_config_to_json(...)` when the next consumer still needs a `Json` 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. 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.
+3 -3
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON suitable for transport and snapshots. - `pretty=false` returns compact JSON suitable for transport and snapshots.
- `pretty=true` returns indented JSON for humans. - `pretty=true` returns indented JSON for humans.
- This helper is built on top of `async_logger_config_to_json(...)`. - This helper is built on top of `async_logger_config_to_json(...)`.
- 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. - 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.
- The exported text follows the supported async config schema rather than internal queue implementation details. - 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`. - 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 ### Error Case
e.g.: e.g.:
- If callers need a `JsonValue` for composition, they should use `async_logger_config_to_json(...)` instead. - If callers need a `Json` 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. - 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`. 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 `JsonValue` for composition before final stringification. 3. Use `async_logger_config_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
+2 -2
View File
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON. - `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON suitable for human diagnostics. - `pretty=true` returns indented JSON suitable for human diagnostics.
- This helper is built on top of `async_logger_state_to_json(...)`. - 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 `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`. - Internally it stringifies the same JSON snapshot shape returned by the public export helper, using `value.stringify(...)` or `value.stringify(indent=2)`.
- The compact form matches snapshots such as `{"runtime":{"mode":"native_worker","background_worker":true},"phase":"ready","pending_count":0,...}`. - 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. - 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. - 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. 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 `JsonValue` for composition before final stringification. 3. Use `async_logger_state_to_json(...)` when the next consumer still needs a `Json` 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. 4. Pair it with `AsyncLogger::state()` for current logger diagnostics, or with `AsyncLoggerState::new(...)` when exporting a manual snapshot built by tests or adapters.
+2 -2
View File
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON. - `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON for human diagnostics. - `pretty=true` returns indented JSON for human diagnostics.
- This helper is built on top of `async_runtime_state_to_json(...)`. - This helper is built on top of `async_runtime_state_to_json(...)`.
- 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. - 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.
- The compact form matches the tested snapshot shape such as `{"mode":"native_worker","background_worker":true}`. - 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. - 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(...)`. 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 `JsonValue` for composition before final stringification. 3. Use `async_runtime_state_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
+1 -1
View File
@@ -66,7 +66,7 @@ In this example, compact JSON is produced without extra formatting logic.
### Error Case ### Error Case
e.g.: e.g.:
- If callers need a `JsonValue` for composition rather than text, `file_sink_policy_to_json(...)` is the better API. - If callers need a `Json` 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`. - If rotation is disabled, the output still includes `rotation` as `null`.
+2 -2
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` produces compact JSON. - `pretty=false` produces compact JSON.
- `pretty=true` produces indented human-readable JSON. - `pretty=true` produces indented human-readable JSON.
- This helper builds on top of `logger_config_to_json(...)`. - This helper builds on top of `logger_config_to_json(...)`.
- 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. - 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.
- Output is stable and suited for roundtrip config workflows. - Output is stable and suited for roundtrip config workflows.
### How to Use ### How to Use
@@ -71,7 +71,7 @@ e.g.:
### Notes ### Notes
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`. 1. Use this helper when the next consumer expects JSON text instead of `Json`.
2. Use `logger_config_to_json(...)` when you still need to embed the config inside a larger JSON object before final stringification. 2. Use `logger_config_to_json(...)` when you still need to embed the config inside a larger JSON object before final stringification.
+3 -3
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON suitable for logs and snapshots. - `pretty=false` returns compact JSON suitable for logs and snapshots.
- `pretty=true` returns indented JSON for human inspection. - `pretty=true` returns indented JSON for human inspection.
- This helper is built on top of `queue_config_to_json(...)`. - This helper is built on top of `queue_config_to_json(...)`.
- 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. - 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.
- The resulting text follows the same queue schema accepted by config parsing flows. - The resulting text follows the same queue schema accepted by config parsing flows.
### How to Use ### How to Use
@@ -67,13 +67,13 @@ In this example, compact JSON is returned without extra formatting.
### Error Case ### Error Case
e.g.: e.g.:
- If callers need a `JsonValue` for further composition, this API is the wrong layer and `queue_config_to_json(...)` should be used instead. - If callers need a `Json` 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. - If queue policy is too aggressive for workload burst size, serialization still succeeds because this helper only exports config.
### Notes ### Notes
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`. 1. Use this helper when the next consumer expects JSON text instead of `Json`.
2. Use `queue_config_to_json(...)` when you still need to embed the queue config inside a larger JSON object before final stringification. 2. Use `queue_config_to_json(...)` when you still need to embed the queue config inside a larger JSON object before final stringification.
+1 -1
View File
@@ -69,7 +69,7 @@ In this example, compact JSON is returned without extra formatting logic.
### Error Case ### Error Case
e.g.: e.g.:
- If callers need a `JsonValue` rather than text, `runtime_file_state_to_json(...)` is the better API. - If callers need a `Json` 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. - If queue wrapping is not relevant, `stringify_file_sink_state(...)` may be the simpler API.
+2 -2
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` gives compact JSON. - `pretty=false` gives compact JSON.
- `pretty=true` gives indented output. - `pretty=true` gives indented output.
- This helper builds on top of `sink_config_to_json(...)`. - This helper builds on top of `sink_config_to_json(...)`.
- 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. - 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.
- It is useful when examples or generated docs want to show only sink-specific config. - It is useful when examples or generated docs want to show only sink-specific config.
### How to Use ### How to Use
@@ -71,7 +71,7 @@ e.g.:
### Notes ### Notes
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`. 1. Use this helper when the next consumer expects JSON text instead of `Json`.
2. Use `sink_config_to_json(...)` when you still need to embed the sink config inside a larger JSON object before final stringification. 2. Use `sink_config_to_json(...)` when you still need to embed the sink config inside a larger JSON object before final stringification.
+2 -2
View File
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON. - `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON for humans. - `pretty=true` returns indented JSON for humans.
- This helper is built on top of `text_formatter_config_to_json(...)`. - This helper is built on top of `text_formatter_config_to_json(...)`.
- 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. - 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.
- The output preserves the supported formatter config schema instead of any runtime-only formatter instance details. - The output preserves the supported formatter config schema instead of any runtime-only formatter instance details.
### How to Use ### How to Use
@@ -76,7 +76,7 @@ e.g.:
### Notes ### Notes
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`. 1. Use this helper when the next consumer expects JSON text instead of `Json`.
2. Use `text_formatter_config_to_json(...)` when you still need to embed formatter config inside a larger JSON object before final stringification. 2. Use `text_formatter_config_to_json(...)` when you still need to embed formatter config inside a larger JSON object before final stringification.
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Text-formatter-config-to-json ## Text-formatter-config-to-json
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. 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.
### Interface ### Interface
```moonbit ```moonbit
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_parser.JsonValue {} pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {}
``` ```
#### input #### input
@@ -27,7 +27,7 @@ pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_pars
#### output #### output
- `JsonValue` - Structured JSON representation of the formatter config. - `Json` - Structured JSON representation of the formatter config.
### Explanation ### Explanation
+25
View File
@@ -0,0 +1,25 @@
---
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.
+21
View File
@@ -0,0 +1,21 @@
## 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
+19
View File
@@ -0,0 +1,19 @@
## 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
View File
@@ -2,6 +2,8 @@
Versioned BitLogger change summaries. 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.1](./1.1.1(0.7.1).md)
- [1.1.0](./1.1.0(0.7.0).md) - [1.1.0](./1.1.0(0.7.0).md)
- [1.0.1](./1.0.1(0.6.1).md) - [1.0.1](./1.0.1(0.6.1).md)
+1 -1
View File
@@ -5,7 +5,7 @@ Use this flow for command-line tools and services that need a human-readable sta
## Install And Import ## Install And Import
```bash ```bash
moon add Nanaloveyuki/BitLogger@0.7.1 moon add Nanaloveyuki/BitLogger@0.7.3
``` ```
```moonbit ```moonbit
+1
View File
@@ -10,5 +10,6 @@ Start from an [Example flow](../examples/index.md), then add one extension at a
| Send records to multiple destinations or route by level | [Sink composition](./composition.md) | More explicit construction than presets. | | 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. | | 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. | | 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). For exact function signatures, follow each page's links into the [API reference](../api/index.md).
+18
View File
@@ -0,0 +1,18 @@
# 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 -1
View File
@@ -7,7 +7,7 @@
```bash ```bash
moon new log-demo moon new log-demo
cd log-demo cd log-demo
moon add Nanaloveyuki/BitLogger@0.7.1 moon add Nanaloveyuki/BitLogger@0.7.3
``` ```
在应用的 `moon.pkg` 中加入: 在应用的 `moon.pkg` 中加入:
+1
View File
@@ -10,5 +10,6 @@
| 同时发送到多个位置或按 level 路由 | [Sink 组合](./composition.md) | 比 preset 更需要显式构造。 | | 同时发送到多个位置或按 level 路由 | [Sink 组合](./composition.md) | 比 preset 更需要显式构造。 |
| 调整终端可读性 | [文本格式](./formatting.md) | 格式只改变呈现,不改变记录结构。 | | 调整终端可读性 | [文本格式](./formatting.md) | 格式只改变呈现,不改变记录结构。 |
| 在 native 与 web 目标之间共享代码 | [目标平台边界](./targets.md) | 文件与异步运行时行为因后端而异。 | | 在 native 与 web 目标之间共享代码 | [目标平台边界](./targets.md) | 文件与异步运行时行为因后端而异。 |
| 将日志关联到分布式请求 | [Trace context](./observability.md) | 传播与导出仍由应用负责。 |
需要精确函数签名时,沿页面链接进入[英文 API 参考](../../api/index.md)。 需要精确函数签名时,沿页面链接进入[英文 API 参考](../../api/index.md)。
+13
View File
@@ -0,0 +1,13 @@
# 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 -2
View File
@@ -1,9 +1,8 @@
name = "Nanaloveyuki/BitLogger" name = "Nanaloveyuki/BitLogger"
version = "0.7.1" version = "0.7.3"
import { import {
"maria/json_parser@0.1.1",
"moonbitlang/async@0.20.2", "moonbitlang/async@0.20.2",
} }
+184
View File
@@ -0,0 +1,184 @@
<#
.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"
+88 -66
View File
@@ -455,6 +455,30 @@ async test "async logger with_context_fields prepends shared fields without muta
inspect(written_fields.val[2].value, content="test") 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" { async test "async logger child composes target and preserves other state" {
let written_target : Ref[String] = Ref("") let written_target : Ref[String] = Ref("")
@@ -831,29 +855,29 @@ test "async json helpers export stable structured shapes" {
flush=AsyncFlushPolicy::Batch, flush=AsyncFlushPolicy::Batch,
), ),
) )
let config_obj = config_json.as_object().unwrap() let config_obj = config_json.expect_json_object()
inspect( inspect(
@json_parser.stringify(config_json), config_json.stringify(),
content="{\"max_pending\":8,\"max_batch\":3,\"linger_ms\":25,\"overflow\":\"DropOldest\",\"flush\":\"Batch\"}", content="{\"max_pending\":8,\"max_batch\":3,\"linger_ms\":25,\"overflow\":\"DropOldest\",\"flush\":\"Batch\"}",
) )
inspect( inspect(
config_obj.get("max_pending").unwrap().as_number().unwrap().to_int(), config_obj.get("max_pending").unwrap().expect_json_number().to_int(),
content="8", content="8",
) )
inspect( inspect(
config_obj.get("max_batch").unwrap().as_number().unwrap().to_int(), config_obj.get("max_batch").unwrap().expect_json_number().to_int(),
content="3", content="3",
) )
inspect( inspect(
config_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(), config_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
content="25", content="25",
) )
inspect( inspect(
config_obj.get("overflow").unwrap().as_string().unwrap(), config_obj.get("overflow").unwrap().expect_json_string(),
content="DropOldest", content="DropOldest",
) )
inspect( inspect(
config_obj.get("flush").unwrap().as_string().unwrap(), config_obj.get("flush").unwrap().expect_json_string(),
content="Batch", content="Batch",
) )
@@ -874,85 +898,84 @@ test "async json helpers export stable structured shapes" {
), ),
), ),
) )
let build_obj = build_json.as_object().unwrap() let build_obj = build_json.expect_json_object()
let logger_obj = build_obj.get("logger").unwrap().as_object().unwrap() let logger_obj = build_obj.get("logger").unwrap().expect_json_object()
let sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap() let sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
let formatter_obj = sink_obj let formatter_obj = sink_obj
.get("text_formatter") .get("text_formatter")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap() let async_obj = build_obj.get("async_config").unwrap().expect_json_object()
let async_obj = build_obj.get("async_config").unwrap().as_object().unwrap()
inspect( inspect(
logger_obj.get("min_level").unwrap().as_string().unwrap(), logger_obj.get("min_level").unwrap().expect_json_string(),
content="WARN", content="WARN",
) )
inspect( inspect(
logger_obj.get("target").unwrap().as_string().unwrap(), logger_obj.get("target").unwrap().expect_json_string(),
content="async.roundtrip", content="async.roundtrip",
) )
inspect( inspect(
logger_obj.get("timestamp").unwrap().as_bool().unwrap(), logger_obj.get("timestamp").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect(logger_obj.get("queue") is None, content="true") inspect(logger_obj.get("queue") is None, content="true")
inspect( inspect(
sink_obj.get("kind").unwrap().as_string().unwrap(), sink_obj.get("kind").unwrap().expect_json_string(),
content="text_console", content="text_console",
) )
inspect(sink_obj.get("path").unwrap().as_string().unwrap(), content="") inspect(sink_obj.get("path").unwrap().expect_json_string(), content="")
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="true") inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="true")
inspect( inspect(
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(), sink_obj.get("auto_flush").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(), formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("separator").unwrap().as_string().unwrap(), formatter_obj.get("separator").unwrap().expect_json_string(),
content=" ", content=" ",
) )
inspect( inspect(
formatter_obj.get("field_separator").unwrap().as_string().unwrap(), formatter_obj.get("field_separator").unwrap().expect_json_string(),
content=" ", content=" ",
) )
inspect( inspect(
formatter_obj.get("template").unwrap().as_string().unwrap(), formatter_obj.get("template").unwrap().expect_json_string(),
content="", content="",
) )
inspect( inspect(
formatter_obj.get("color_mode").unwrap().as_string().unwrap(), formatter_obj.get("color_mode").unwrap().expect_json_string(),
content="never", content="never",
) )
inspect( inspect(
formatter_obj.get("color_support").unwrap().as_string().unwrap(), formatter_obj.get("color_support").unwrap().expect_json_string(),
content="truecolor", content="truecolor",
) )
inspect( inspect(
formatter_obj.get("style_markup").unwrap().as_string().unwrap(), formatter_obj.get("style_markup").unwrap().expect_json_string(),
content="full", content="full",
) )
inspect( inspect(
async_obj.get("max_pending").unwrap().as_number().unwrap().to_int(), async_obj.get("max_pending").unwrap().expect_json_number().to_int(),
content="2", content="2",
) )
inspect( inspect(
async_obj.get("max_batch").unwrap().as_number().unwrap().to_int(), async_obj.get("max_batch").unwrap().expect_json_number().to_int(),
content="5", content="5",
) )
inspect( inspect(
async_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(), async_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
content="40", content="40",
) )
inspect( inspect(
async_obj.get("overflow").unwrap().as_string().unwrap(), async_obj.get("overflow").unwrap().expect_json_string(),
content="DropNewest", content="DropNewest",
) )
inspect( inspect(
async_obj.get("flush").unwrap().as_string().unwrap(), async_obj.get("flush").unwrap().expect_json_string(),
content="Shutdown", content="Shutdown",
) )
} }
@@ -961,103 +984,102 @@ test "async json helpers export stable structured shapes" {
test "async build config json export materializes parsed omitted defaults" { test "async build config json export materializes parsed omitted defaults" {
let parsed = parse_async_logger_build_config_text("{}") let parsed = parse_async_logger_build_config_text("{}")
let build_json = async_logger_build_config_to_json(parsed) let build_json = async_logger_build_config_to_json(parsed)
let build_obj = build_json.as_object().unwrap() let build_obj = build_json.expect_json_object()
let logger_obj = build_obj.get("logger").unwrap().as_object().unwrap() let logger_obj = build_obj.get("logger").unwrap().expect_json_object()
let sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap() let sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
let formatter_obj = sink_obj let formatter_obj = sink_obj
.get("text_formatter") .get("text_formatter")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap() let async_obj = build_obj.get("async_config").unwrap().expect_json_object()
let async_obj = build_obj.get("async_config").unwrap().as_object().unwrap()
inspect( inspect(
@json_parser.stringify(build_json), build_json.stringify(),
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\"}}", 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( inspect(
logger_obj.get("min_level").unwrap().as_string().unwrap(), logger_obj.get("min_level").unwrap().expect_json_string(),
content="INFO", content="INFO",
) )
inspect(logger_obj.get("target").unwrap().as_string().unwrap(), content="") inspect(logger_obj.get("target").unwrap().expect_json_string(), content="")
inspect( inspect(
logger_obj.get("timestamp").unwrap().as_bool().unwrap(), logger_obj.get("timestamp").unwrap().expect_json_bool(),
content="false", content="false",
) )
inspect(logger_obj.get("queue") is None, content="true") inspect(logger_obj.get("queue") is None, content="true")
inspect(sink_obj.get("kind").unwrap().as_string().unwrap(), content="console") inspect(sink_obj.get("kind").unwrap().expect_json_string(), content="console")
inspect(sink_obj.get("path").unwrap().as_string().unwrap(), content="") inspect(sink_obj.get("path").unwrap().expect_json_string(), content="")
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="true") inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="true")
inspect( inspect(
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(), sink_obj.get("auto_flush").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(), formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_level").unwrap().as_bool().unwrap(), formatter_obj.get("show_level").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_target").unwrap().as_bool().unwrap(), formatter_obj.get("show_target").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_fields").unwrap().as_bool().unwrap(), formatter_obj.get("show_fields").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("separator").unwrap().as_string().unwrap(), formatter_obj.get("separator").unwrap().expect_json_string(),
content=" ", content=" ",
) )
inspect( inspect(
formatter_obj.get("field_separator").unwrap().as_string().unwrap(), formatter_obj.get("field_separator").unwrap().expect_json_string(),
content=" ", content=" ",
) )
inspect( inspect(
formatter_obj.get("template").unwrap().as_string().unwrap(), formatter_obj.get("template").unwrap().expect_json_string(),
content="", content="",
) )
inspect( inspect(
formatter_obj.get("color_mode").unwrap().as_string().unwrap(), formatter_obj.get("color_mode").unwrap().expect_json_string(),
content="never", content="never",
) )
inspect( inspect(
formatter_obj.get("color_support").unwrap().as_string().unwrap(), formatter_obj.get("color_support").unwrap().expect_json_string(),
content="truecolor", content="truecolor",
) )
inspect( inspect(
formatter_obj.get("style_markup").unwrap().as_string().unwrap(), formatter_obj.get("style_markup").unwrap().expect_json_string(),
content="full", content="full",
) )
inspect( inspect(
formatter_obj.get("target_style_markup").unwrap().as_string().unwrap(), formatter_obj.get("target_style_markup").unwrap().expect_json_string(),
content="disabled", content="disabled",
) )
inspect( inspect(
formatter_obj.get("fields_style_markup").unwrap().as_string().unwrap(), formatter_obj.get("fields_style_markup").unwrap().expect_json_string(),
content="disabled", content="disabled",
) )
inspect(formatter_obj.get("style_tags") is None, content="true") inspect(formatter_obj.get("style_tags") is None, content="true")
inspect( inspect(
async_obj.get("max_pending").unwrap().as_number().unwrap().to_int(), async_obj.get("max_pending").unwrap().expect_json_number().to_int(),
content="0", content="0",
) )
inspect( inspect(
async_obj.get("max_batch").unwrap().as_number().unwrap().to_int(), async_obj.get("max_batch").unwrap().expect_json_number().to_int(),
content="1", content="1",
) )
inspect( inspect(
async_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(), async_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
content="0", content="0",
) )
inspect( inspect(
async_obj.get("overflow").unwrap().as_string().unwrap(), async_obj.get("overflow").unwrap().expect_json_string(),
content="Blocking", content="Blocking",
) )
inspect(async_obj.get("flush").unwrap().as_string().unwrap(), content="Never") inspect(async_obj.get("flush").unwrap().expect_json_string(), content="Never")
} }
///| ///|
@@ -1068,7 +1090,7 @@ test "async config parsers reject malformed input" {
})() catch { })() catch {
err => err.to_string() err => err.to_string()
} }
inspect(invalid_json_error.contains("UnexpectedToken"), content="true") inspect(invalid_json_error != "no error", content="true")
let wrong_type_error = (fn() -> String raise { let wrong_type_error = (fn() -> String raise {
ignore(parse_async_logger_config_text("{\"max_pending\":\"many\"}")) ignore(parse_async_logger_config_text("{\"max_pending\":\"many\"}"))
@@ -1144,7 +1166,7 @@ test "async runtime capability helpers stay consistent" {
) )
inspect(state.background_worker == worker_supported, content="true") inspect(state.background_worker == worker_supported, content="true")
inspect( inspect(
@json_parser.stringify(async_runtime_state_to_json(state)), async_runtime_state_to_json(state).stringify(),
content=if worker_supported { content=if worker_supported {
"{\"mode\":\"native_worker\",\"background_worker\":true}" "{\"mode\":\"native_worker\",\"background_worker\":true}"
} else { } else {
@@ -1225,7 +1247,7 @@ test "async logger state snapshot reflects current counters and runtime" {
content="Shutdown", content="Shutdown",
) )
inspect( inspect(
@json_parser.stringify(async_logger_state_to_json(state)), async_logger_state_to_json(state).stringify(),
content=if async_runtime_supports_background_worker() { 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\"}" "{\"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 { } else {
+12 -10
View File
@@ -48,9 +48,7 @@ pub fn async_runtime_state() -> AsyncRuntimeState {
} }
///| ///|
pub fn async_runtime_state_to_json( pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {
state : AsyncRuntimeState,
) -> @json_parser.JsonValue {
@utils.async_runtime_state_to_json(state) @utils.async_runtime_state_to_json(state)
} }
@@ -63,9 +61,7 @@ pub fn stringify_async_runtime_state(
} }
///| ///|
pub fn async_logger_state_to_json( pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
@utils.async_logger_state_to_json(state) @utils.async_logger_state_to_json(state)
} }
@@ -88,9 +84,7 @@ pub fn parse_async_logger_config_text(
} }
///| ///|
pub fn async_logger_config_to_json( pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {
config : AsyncLoggerConfig,
) -> @json_parser.JsonValue {
@utils.async_logger_config_to_json(config) @utils.async_logger_config_to_json(config)
} }
@@ -115,7 +109,7 @@ pub fn parse_async_logger_build_config_text(
///| ///|
pub fn async_logger_build_config_to_json( pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> @json_parser.JsonValue { ) -> Json {
@utils.async_logger_build_config_to_json(config) @utils.async_logger_build_config_to_json(config)
} }
@@ -280,6 +274,14 @@ pub fn[S] AsyncLogger::with_context_fields(
{ ..self, context_fields: 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( pub fn[S] AsyncLogger::with_filter(
self : AsyncLogger[S], self : AsyncLogger[S],
@@ -0,0 +1,90 @@
///|
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")
}
}
+32
View File
@@ -0,0 +1,32 @@
///|
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")
}
}
+8
View File
@@ -93,6 +93,14 @@ pub fn[S] LibraryAsyncLogger::bind(
self.with_context_fields(fields) 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( pub fn[S] LibraryAsyncLogger::is_enabled(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
+1 -1
View File
@@ -1,7 +1,7 @@
import { import {
"Nanaloveyuki/BitLogger/src" @bitlogger, "Nanaloveyuki/BitLogger/src" @bitlogger,
"Nanaloveyuki/BitLogger/src-async/utils", "Nanaloveyuki/BitLogger/src-async/utils",
"maria/json_parser", "moonbitlang/core/json",
"moonbitlang/async", "moonbitlang/async",
"moonbitlang/async/aqueue", "moonbitlang/async/aqueue",
"moonbitlang/core/env", "moonbitlang/core/env",
+6 -5
View File
@@ -6,7 +6,6 @@ import {
"Nanaloveyuki/BitLogger/src/core", "Nanaloveyuki/BitLogger/src/core",
"Nanaloveyuki/BitLogger/src/runtime", "Nanaloveyuki/BitLogger/src/runtime",
"Nanaloveyuki/BitLogger/src/sink_graph", "Nanaloveyuki/BitLogger/src/sink_graph",
"maria/json_parser",
"moonbitlang/async/aqueue", "moonbitlang/async/aqueue",
"moonbitlang/core/ref", "moonbitlang/core/ref",
} }
@@ -14,11 +13,11 @@ import {
// Values // Values
pub fn[S] async_logger(S, config? : @utils.AsyncLoggerConfig, min_level? : @core.Level, target? : String, flush? : (S) -> Unit raise) -> AsyncLogger[S] 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_parser.JsonValue pub fn async_logger_build_config_to_json(@utils.AsyncLoggerBuildConfig) -> Json
pub fn async_logger_config_to_json(@utils.AsyncLoggerConfig) -> @json_parser.JsonValue pub fn async_logger_config_to_json(@utils.AsyncLoggerConfig) -> Json
pub fn async_logger_state_to_json(@utils.AsyncLoggerState) -> @json_parser.JsonValue pub fn async_logger_state_to_json(@utils.AsyncLoggerState) -> Json
pub fn async_runtime_mode() -> @utils.AsyncRuntimeMode pub fn async_runtime_mode() -> @utils.AsyncRuntimeMode
@@ -26,7 +25,7 @@ pub fn async_runtime_mode_label(@utils.AsyncRuntimeMode) -> String
pub fn async_runtime_state() -> @utils.AsyncRuntimeState pub fn async_runtime_state() -> @utils.AsyncRuntimeState
pub fn async_runtime_state_to_json(@utils.AsyncRuntimeState) -> @json_parser.JsonValue pub fn async_runtime_state_to_json(@utils.AsyncRuntimeState) -> Json
pub fn async_runtime_supports_background_worker() -> Bool pub fn async_runtime_supports_background_worker() -> Bool
@@ -115,6 +114,7 @@ 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_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_target(Self[S], String) -> Self[S]
pub fn[S] AsyncLogger::with_timestamp(Self[S], enabled? : Bool) -> 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] { pub struct LibraryAsyncLogger[S] {
inner : AsyncLogger[S] inner : AsyncLogger[S]
@@ -132,6 +132,7 @@ 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 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_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_target(Self[S], String) -> Self[S]
pub fn[S] LibraryAsyncLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[S]
// Type aliases // Type aliases
pub type ApplicationAsyncLogger = AsyncLogger[@runtime.RuntimeSink] pub type ApplicationAsyncLogger = AsyncLogger[@runtime.RuntimeSink]
+59 -93
View File
@@ -154,12 +154,10 @@ pub fn AsyncLoggerState::new(
} }
///| ///|
pub fn async_runtime_state_to_json( pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {
state : AsyncRuntimeState, Json::object({
) -> @json_parser.JsonValue { "mode": Json::string(async_runtime_mode_label(state.mode)),
@json_parser.JsonValue::Object({ "background_worker": Json::boolean(state.background_worker),
"mode": @json_parser.JsonValue::String(async_runtime_mode_label(state.mode)),
"background_worker": @json_parser.JsonValue::Bool(state.background_worker),
}) })
} }
@@ -170,9 +168,9 @@ pub fn stringify_async_runtime_state(
) -> String { ) -> String {
let value = async_runtime_state_to_json(state) let value = async_runtime_state_to_json(state)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
@@ -186,37 +184,25 @@ fn async_flush_policy_label(policy : AsyncFlushPolicy) -> String {
} }
///| ///|
fn async_logger_state_to_json_value( fn async_logger_state_to_json_value(state : AsyncLoggerState) -> Json {
state : AsyncLoggerState, Json::object({
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({
"runtime": async_runtime_state_to_json(state.runtime), "runtime": async_runtime_state_to_json(state.runtime),
"phase": @json_parser.JsonValue::String( "phase": Json::string(async_lifecycle_phase_label(state.phase)),
async_lifecycle_phase_label(state.phase), "pending_count": Json::number(state.pending_count.to_double()),
), "dropped_count": Json::number(state.dropped_count.to_double()),
"pending_count": @json_parser.JsonValue::Number( "is_closed": Json::boolean(state.is_closed),
state.pending_count.to_double(), "is_running": Json::boolean(state.is_running),
), "has_failed": Json::boolean(state.has_failed),
"dropped_count": @json_parser.JsonValue::Number( "backlog_retained": Json::boolean(state.backlog_retained),
state.dropped_count.to_double(), "can_rerun": Json::boolean(state.can_rerun),
), "terminal": Json::boolean(state.terminal),
"is_closed": @json_parser.JsonValue::Bool(state.is_closed), "last_error": Json::string(state.last_error),
"is_running": @json_parser.JsonValue::Bool(state.is_running), "flush_policy": Json::string(async_flush_policy_label(state.flush_policy)),
"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( pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
async_logger_state_to_json_value(state) async_logger_state_to_json_value(state)
} }
@@ -227,9 +213,9 @@ pub fn stringify_async_logger_state(
) -> String { ) -> String {
let value = async_logger_state_to_json_value(state) let value = async_logger_state_to_json_value(state)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
@@ -293,53 +279,38 @@ fn parse_async_flush(name : String) -> AsyncFlushPolicy raise {
pub fn parse_async_logger_config_text( pub fn parse_async_logger_config_text(
input : String, input : String,
) -> AsyncLoggerConfig raise { ) -> AsyncLoggerConfig raise {
let root = @json_parser.parse(input) let root = @json.parse(input)
let obj = match root.as_object() { let obj = match root {
Some(obj) => obj Json::Object(obj) => obj
None => raise Failure::Failure("Expected object for async logger config") _ => raise Failure::Failure("Expected object for async logger config")
} }
let max_pending = match obj.get("max_pending") { let max_pending = match obj.get("max_pending") {
Some(value) => Some(Json::Number(number, ..)) => number.to_int()
match value.as_number() { Some(_) =>
Some(number) => number.to_int() raise Failure::Failure("Expected number at async_config.max_pending")
None =>
raise Failure::Failure("Expected number at async_config.max_pending")
}
None => 0 None => 0
} }
let overflow = match obj.get("overflow") { let overflow = match obj.get("overflow") {
Some(value) => Some(Json::String(text)) => parse_async_overflow(text)
match value.as_string() { Some(_) =>
Some(text) => parse_async_overflow(text) raise Failure::Failure("Expected string at async_config.overflow")
None =>
raise Failure::Failure("Expected string at async_config.overflow")
}
None => AsyncOverflowPolicy::Blocking None => AsyncOverflowPolicy::Blocking
} }
let max_batch = match obj.get("max_batch") { let max_batch = match obj.get("max_batch") {
Some(value) => Some(Json::Number(number, ..)) => number.to_int()
match value.as_number() { Some(_) =>
Some(number) => number.to_int() raise Failure::Failure("Expected number at async_config.max_batch")
None =>
raise Failure::Failure("Expected number at async_config.max_batch")
}
None => 1 None => 1
} }
let linger_ms = match obj.get("linger_ms") { let linger_ms = match obj.get("linger_ms") {
Some(value) => Some(Json::Number(number, ..)) => number.to_int()
match value.as_number() { Some(_) =>
Some(number) => number.to_int() raise Failure::Failure("Expected number at async_config.linger_ms")
None =>
raise Failure::Failure("Expected number at async_config.linger_ms")
}
None => 0 None => 0
} }
let flush = match obj.get("flush") { let flush = match obj.get("flush") {
Some(value) => Some(Json::String(text)) => parse_async_flush(text)
match value.as_string() { Some(_) => raise Failure::Failure("Expected string at async_config.flush")
Some(text) => parse_async_flush(text)
None => raise Failure::Failure("Expected string at async_config.flush")
}
None => AsyncFlushPolicy::Never None => AsyncFlushPolicy::Never
} }
AsyncLoggerConfig::new( AsyncLoggerConfig::new(
@@ -352,23 +323,19 @@ pub fn parse_async_logger_config_text(
} }
///| ///|
pub fn async_logger_config_to_json( pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {
config : AsyncLoggerConfig, Json::object({
) -> @json_parser.JsonValue { "max_pending": Json::number(config.max_pending.to_double()),
@json_parser.JsonValue::Object({ "max_batch": Json::number(config.max_batch.to_double()),
"max_pending": @json_parser.JsonValue::Number( "linger_ms": Json::number(config.linger_ms.to_double()),
config.max_pending.to_double(), "overflow": Json::string(
),
"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 { match config.overflow {
AsyncOverflowPolicy::Blocking => "Blocking" AsyncOverflowPolicy::Blocking => "Blocking"
AsyncOverflowPolicy::DropOldest => "DropOldest" AsyncOverflowPolicy::DropOldest => "DropOldest"
AsyncOverflowPolicy::DropNewest => "DropNewest" AsyncOverflowPolicy::DropNewest => "DropNewest"
}, },
), ),
"flush": @json_parser.JsonValue::String( "flush": Json::string(
match config.flush { match config.flush {
AsyncFlushPolicy::Never => "Never" AsyncFlushPolicy::Never => "Never"
AsyncFlushPolicy::Batch => "Batch" AsyncFlushPolicy::Batch => "Batch"
@@ -385,9 +352,9 @@ pub fn stringify_async_logger_config(
) -> String { ) -> String {
let value = async_logger_config_to_json(config) let value = async_logger_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
@@ -409,21 +376,20 @@ pub fn AsyncLoggerBuildConfig::new(
pub fn parse_async_logger_build_config_text( pub fn parse_async_logger_build_config_text(
input : String, input : String,
) -> AsyncLoggerBuildConfig raise { ) -> AsyncLoggerBuildConfig raise {
let root = @json_parser.parse(input) let root = @json.parse(input)
let obj = match root.as_object() { let obj = match root {
Some(obj) => obj Json::Object(obj) => obj
None => _ =>
raise Failure::Failure( raise Failure::Failure(
"Expected object at async logger build config root", "Expected object at async logger build config root",
) )
} }
let logger = match obj.get("logger") { let logger = match obj.get("logger") {
Some(value) => Some(value) => @bitlogger.parse_logger_config_text(value.stringify())
@bitlogger.parse_logger_config_text(@json_parser.stringify(value))
None => @bitlogger.default_logger_config() None => @bitlogger.default_logger_config()
} }
let async_config = match obj.get("async_config") { let async_config = match obj.get("async_config") {
Some(value) => parse_async_logger_config_text(@json_parser.stringify(value)) Some(value) => parse_async_logger_config_text(value.stringify())
None => AsyncLoggerConfig::new() None => AsyncLoggerConfig::new()
} }
AsyncLoggerBuildConfig::new(logger~, async_config~) AsyncLoggerBuildConfig::new(logger~, async_config~)
@@ -432,8 +398,8 @@ pub fn parse_async_logger_build_config_text(
///| ///|
pub fn async_logger_build_config_to_json( pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> @json_parser.JsonValue { ) -> Json {
@json_parser.JsonValue::Object({ Json::object({
"logger": @bitlogger.logger_config_to_json(config.logger), "logger": @bitlogger.logger_config_to_json(config.logger),
"async_config": async_logger_config_to_json(config.async_config), "async_config": async_logger_config_to_json(config.async_config),
}) })
@@ -446,8 +412,8 @@ pub fn stringify_async_logger_build_config(
) -> String { ) -> String {
let value = async_logger_build_config_to_json(config) let value = async_logger_build_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { import {
"Nanaloveyuki/BitLogger/src" @bitlogger, "Nanaloveyuki/BitLogger/src" @bitlogger,
"maria/json_parser", "moonbitlang/core/json",
} }
+4 -5
View File
@@ -3,19 +3,18 @@ package "Nanaloveyuki/BitLogger/src-async/utils"
import { import {
"Nanaloveyuki/BitLogger/src/config_model", "Nanaloveyuki/BitLogger/src/config_model",
"maria/json_parser",
} }
// Values // Values
pub fn async_logger_build_config_to_json(AsyncLoggerBuildConfig) -> @json_parser.JsonValue pub fn async_logger_build_config_to_json(AsyncLoggerBuildConfig) -> Json
pub fn async_logger_config_to_json(AsyncLoggerConfig) -> @json_parser.JsonValue pub fn async_logger_config_to_json(AsyncLoggerConfig) -> Json
pub fn async_logger_state_to_json(AsyncLoggerState) -> @json_parser.JsonValue pub fn async_logger_state_to_json(AsyncLoggerState) -> Json
pub fn async_runtime_mode_label(AsyncRuntimeMode) -> String pub fn async_runtime_mode_label(AsyncRuntimeMode) -> String
pub fn async_runtime_state_to_json(AsyncRuntimeState) -> @json_parser.JsonValue pub fn async_runtime_state_to_json(AsyncRuntimeState) -> Json
pub fn compatibility_async_runtime_mode() -> AsyncRuntimeMode pub fn compatibility_async_runtime_mode() -> AsyncRuntimeMode
+5 -9
View File
@@ -39,7 +39,7 @@ pub fn parse_logger_config_text(
} }
///| ///|
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue { pub fn queue_config_to_json(queue : QueueConfig) -> Json {
@config_model.queue_config_to_json(queue) @config_model.queue_config_to_json(queue)
} }
@@ -52,9 +52,7 @@ pub fn stringify_queue_config(
} }
///| ///|
pub fn text_formatter_config_to_json( pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {
config : TextFormatterConfig,
) -> @json_parser.JsonValue {
@config_model.text_formatter_config_to_json(config) @config_model.text_formatter_config_to_json(config)
} }
@@ -67,14 +65,12 @@ pub fn stringify_text_formatter_config(
} }
///| ///|
pub fn file_rotation_config_to_json( pub fn file_rotation_config_to_json(config : FileRotation) -> Json {
config : FileRotation,
) -> @json_parser.JsonValue {
@file_model.file_rotation_config_to_json(config) @file_model.file_rotation_config_to_json(config)
} }
///| ///|
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue { pub fn sink_config_to_json(config : SinkConfig) -> Json {
@config_model.sink_config_to_json(config) @config_model.sink_config_to_json(config)
} }
@@ -87,7 +83,7 @@ pub fn stringify_sink_config(
} }
///| ///|
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue { pub fn logger_config_to_json(config : LoggerConfig) -> Json {
@config_model.logger_config_to_json(config) @config_model.logger_config_to_json(config)
} }
+88 -116
View File
@@ -170,102 +170,84 @@ pub fn default_logger_config() -> LoggerConfig {
///| ///|
fn expect_object( fn expect_object(
value : @json_parser.JsonValue, value : Json,
context : String, context : String,
) -> Map[String, @json_parser.JsonValue] raise ConfigError { ) -> Map[String, Json] raise ConfigError {
match value.as_object() { match value {
Some(obj) => obj Json::Object(obj) => obj
None => raise ConfigError::InvalidConfig("Expected object at " + context) _ => raise ConfigError::InvalidConfig("Expected object at " + context)
} }
} }
///| ///|
fn get_string( fn get_string(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, Json],
key : String, key : String,
default? : String = "", default? : String = "",
) -> String raise ConfigError { ) -> String raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => default None => default
Some(value) => Some(Json::String(text)) => text
match value.as_string() { Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
Some(text) => text
None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
}
} }
} }
///| ///|
fn get_optional_string( fn get_optional_string(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, Json],
key : String, key : String,
) -> String? raise ConfigError { ) -> String? raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => None None => None
Some(value) => Some(Json::String(text)) => Some(text)
match value.as_string() { Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
Some(text) => Some(text)
None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
}
} }
} }
///| ///|
fn get_bool( fn get_bool(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, Json],
key : String, key : String,
default~ : Bool, default~ : Bool,
) -> Bool raise ConfigError { ) -> Bool raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => default None => default
Some(value) => Some(Json::True) => true
match value.as_bool() { Some(Json::False) => false
Some(flag) => flag Some(_) => raise ConfigError::InvalidConfig("Expected bool at key " + key)
None => raise ConfigError::InvalidConfig("Expected bool at key " + key)
}
} }
} }
///| ///|
fn get_int( fn get_int(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, Json],
key : String, key : String,
default~ : Int, default~ : Int,
) -> Int raise ConfigError { ) -> Int raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => default None => default
Some(value) => Some(Json::Number(number, ..)) => number.to_int()
match value.as_number() { Some(_) => raise ConfigError::InvalidConfig("Expected number at key " + key)
Some(number) => number.to_int()
None =>
raise ConfigError::InvalidConfig("Expected number at key " + key)
}
} }
} }
///| ///|
fn get_optional_int64_string( fn get_optional_int64_string(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, Json],
key : String, key : String,
) -> Int64? raise ConfigError { ) -> Int64? raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => None None => None
Some(value) => Some(Json::String(text)) =>
match value.as_string() { Some(
Some(text) => @string.parse_int64(text) catch {
Some( _ =>
@string.parse_int64(text) catch { raise ConfigError::InvalidConfig(
_ => "Expected int64 string at key " + key,
raise ConfigError::InvalidConfig( )
"Expected int64 string at key " + key, },
) )
}, Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
)
None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
}
} }
} }
@@ -355,7 +337,7 @@ fn sink_kind_label(kind : SinkKind) -> String {
///| ///|
fn parse_text_formatter_config( fn parse_text_formatter_config(
value : @json_parser.JsonValue, value : Json,
) -> TextFormatterConfig raise ConfigError { ) -> TextFormatterConfig raise ConfigError {
let obj = expect_object(value, "text_formatter") let obj = expect_object(value, "text_formatter")
TextFormatterConfig::new( TextFormatterConfig::new(
@@ -388,7 +370,7 @@ fn parse_text_formatter_config(
///| ///|
fn parse_text_style_config( fn parse_text_style_config(
value : @json_parser.JsonValue, value : Json,
context : String, context : String,
) -> @formatting.TextStyle raise ConfigError { ) -> @formatting.TextStyle raise ConfigError {
let obj = expect_object(value, context) let obj = expect_object(value, context)
@@ -404,7 +386,7 @@ fn parse_text_style_config(
///| ///|
fn parse_style_tags_config( fn parse_style_tags_config(
value : @json_parser.JsonValue, value : Json,
) -> Map[String, @formatting.TextStyle] raise ConfigError { ) -> Map[String, @formatting.TextStyle] raise ConfigError {
let obj = expect_object(value, "text_formatter.style_tags") let obj = expect_object(value, "text_formatter.style_tags")
let style_tags : Map[String, @formatting.TextStyle] = Map([]) let style_tags : Map[String, @formatting.TextStyle] = Map([])
@@ -418,9 +400,7 @@ fn parse_style_tags_config(
} }
///| ///|
fn parse_queue_config( fn parse_queue_config(value : Json) -> QueueConfig raise ConfigError {
value : @json_parser.JsonValue,
) -> QueueConfig raise ConfigError {
let obj = expect_object(value, "queue") let obj = expect_object(value, "queue")
QueueConfig::new( QueueConfig::new(
get_int(obj, "max_pending", default=0), get_int(obj, "max_pending", default=0),
@@ -430,7 +410,7 @@ fn parse_queue_config(
///| ///|
fn parse_file_rotation_config( fn parse_file_rotation_config(
value : @json_parser.JsonValue, value : Json,
) -> @file_model.FileRotation raise ConfigError { ) -> @file_model.FileRotation raise ConfigError {
let obj = expect_object(value, "sink.rotation") let obj = expect_object(value, "sink.rotation")
let max_backups = get_int(obj, "max_backups", default=1) let max_backups = get_int(obj, "max_backups", default=1)
@@ -446,9 +426,7 @@ fn parse_file_rotation_config(
} }
///| ///|
fn parse_sink_config( fn parse_sink_config(value : Json) -> SinkConfig raise ConfigError {
value : @json_parser.JsonValue,
) -> SinkConfig raise ConfigError {
let obj = expect_object(value, "sink") let obj = expect_object(value, "sink")
let kind = parse_sink_kind(get_string(obj, "kind", default="console")) let kind = parse_sink_kind(get_string(obj, "kind", default="console"))
let formatter = match obj.get("text_formatter") { let formatter = match obj.get("text_formatter") {
@@ -480,7 +458,7 @@ fn parse_sink_config(
pub fn parse_logger_config_text( pub fn parse_logger_config_text(
input : String, input : String,
) -> LoggerConfig raise ConfigError { ) -> LoggerConfig raise ConfigError {
let root = @json_parser.parse(input) catch { let root = @json.parse(input) catch {
e => raise ConfigError::InvalidConfig("Invalid JSON: " + e.to_string()) e => raise ConfigError::InvalidConfig("Invalid JSON: " + e.to_string())
} }
let obj = expect_object(root, "root") let obj = expect_object(root, "root")
@@ -500,10 +478,10 @@ pub fn parse_logger_config_text(
} }
///| ///|
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue { pub fn queue_config_to_json(queue : QueueConfig) -> Json {
@json_parser.JsonValue::Object({ Json::object({
"max_pending": @json_parser.JsonValue::Number(queue.max_pending.to_double()), "max_pending": Json::number(queue.max_pending.to_double()),
"overflow": @json_parser.JsonValue::String( "overflow": Json::string(
match queue.overflow { match queue.overflow {
@queue_model.QueueOverflowPolicy::DropNewest => "DropNewest" @queue_model.QueueOverflowPolicy::DropNewest => "DropNewest"
@queue_model.QueueOverflowPolicy::DropOldest => "DropOldest" @queue_model.QueueOverflowPolicy::DropOldest => "DropOldest"
@@ -519,76 +497,70 @@ pub fn stringify_queue_config(
) -> String { ) -> String {
let value = queue_config_to_json(queue) let value = queue_config_to_json(queue)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
///| ///|
pub fn text_formatter_config_to_json( pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {
config : TextFormatterConfig, let obj : Map[String, Json] = {
) -> @json_parser.JsonValue { "show_timestamp": Json::boolean(config.show_timestamp),
let obj : Map[String, @json_parser.JsonValue] = { "show_level": Json::boolean(config.show_level),
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp), "show_target": Json::boolean(config.show_target),
"show_level": @json_parser.JsonValue::Bool(config.show_level), "show_fields": Json::boolean(config.show_fields),
"show_target": @json_parser.JsonValue::Bool(config.show_target), "separator": Json::string(config.separator),
"show_fields": @json_parser.JsonValue::Bool(config.show_fields), "field_separator": Json::string(config.field_separator),
"separator": @json_parser.JsonValue::String(config.separator), "template": Json::string(config.template),
"field_separator": @json_parser.JsonValue::String(config.field_separator), "color_mode": Json::string(@formatting.color_mode_label(config.color_mode)),
"template": @json_parser.JsonValue::String(config.template), "color_support": Json::string(
"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), @formatting.color_support_label(config.color_support),
), ),
"style_markup": @json_parser.JsonValue::String( "style_markup": Json::string(
@formatting.style_markup_mode_label(config.style_markup), @formatting.style_markup_mode_label(config.style_markup),
), ),
"target_style_markup": @json_parser.JsonValue::String( "target_style_markup": Json::string(
@formatting.style_markup_mode_label(config.target_style_markup), @formatting.style_markup_mode_label(config.target_style_markup),
), ),
"fields_style_markup": @json_parser.JsonValue::String( "fields_style_markup": Json::string(
@formatting.style_markup_mode_label(config.fields_style_markup), @formatting.style_markup_mode_label(config.fields_style_markup),
), ),
} }
if config.style_tags.length() != 0 { if config.style_tags.length() != 0 {
obj["style_tags"] = style_tags_config_to_json(config.style_tags) obj["style_tags"] = style_tags_config_to_json(config.style_tags)
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
fn text_style_config_to_json( fn text_style_config_to_json(style : @formatting.TextStyle) -> Json {
style : @formatting.TextStyle, let obj : Map[String, Json] = {
) -> @json_parser.JsonValue { "bold": Json::boolean(style.bold),
let obj : Map[String, @json_parser.JsonValue] = { "dim": Json::boolean(style.dim),
"bold": @json_parser.JsonValue::Bool(style.bold), "italic": Json::boolean(style.italic),
"dim": @json_parser.JsonValue::Bool(style.dim), "underline": Json::boolean(style.underline),
"italic": @json_parser.JsonValue::Bool(style.italic),
"underline": @json_parser.JsonValue::Bool(style.underline),
} }
match style.fg { match style.fg {
Some(value) => obj["fg"] = @json_parser.JsonValue::String(value) Some(value) => obj["fg"] = Json::string(value)
None => () None => ()
} }
match style.bg { match style.bg {
Some(value) => obj["bg"] = @json_parser.JsonValue::String(value) Some(value) => obj["bg"] = Json::string(value)
None => () None => ()
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
fn style_tags_config_to_json( fn style_tags_config_to_json(
style_tags : Map[String, @formatting.TextStyle], style_tags : Map[String, @formatting.TextStyle],
) -> @json_parser.JsonValue { ) -> Json {
let obj : Map[String, @json_parser.JsonValue] = Map([]) let obj : Map[String, Json] = Map([])
for name, style in style_tags { for name, style in style_tags {
obj[name] = text_style_config_to_json(style) obj[name] = text_style_config_to_json(style)
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
@@ -598,19 +570,19 @@ pub fn stringify_text_formatter_config(
) -> String { ) -> String {
let value = text_formatter_config_to_json(config) let value = text_formatter_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
///| ///|
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue { pub fn sink_config_to_json(config : SinkConfig) -> Json {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, Json] = {
"kind": @json_parser.JsonValue::String(sink_kind_label(config.kind)), "kind": Json::string(sink_kind_label(config.kind)),
"path": @json_parser.JsonValue::String(config.path), "path": Json::string(config.path),
"append": @json_parser.JsonValue::Bool(config.append), "append": Json::boolean(config.append),
"auto_flush": @json_parser.JsonValue::Bool(config.auto_flush), "auto_flush": Json::boolean(config.auto_flush),
"text_formatter": text_formatter_config_to_json(config.text_formatter), "text_formatter": text_formatter_config_to_json(config.text_formatter),
} }
match config.rotation { match config.rotation {
@@ -618,7 +590,7 @@ pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
Some(rotation) => Some(rotation) =>
obj["rotation"] = @file_model.file_rotation_config_to_json(rotation) obj["rotation"] = @file_model.file_rotation_config_to_json(rotation)
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
@@ -628,25 +600,25 @@ pub fn stringify_sink_config(
) -> String { ) -> String {
let value = sink_config_to_json(config) let value = sink_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
///| ///|
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue { pub fn logger_config_to_json(config : LoggerConfig) -> Json {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, Json] = {
"min_level": @json_parser.JsonValue::String(config.min_level.label()), "min_level": Json::string(config.min_level.label()),
"target": @json_parser.JsonValue::String(config.target), "target": Json::string(config.target),
"timestamp": @json_parser.JsonValue::Bool(config.timestamp), "timestamp": Json::boolean(config.timestamp),
"sink": sink_config_to_json(config.sink), "sink": sink_config_to_json(config.sink),
} }
match config.queue { match config.queue {
None => () None => ()
Some(queue) => obj["queue"] = queue_config_to_json(queue) Some(queue) => obj["queue"] = queue_config_to_json(queue)
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
@@ -656,8 +628,8 @@ pub fn stringify_logger_config(
) -> String { ) -> String {
let value = logger_config_to_json(config) let value = logger_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
+1 -1
View File
@@ -3,6 +3,6 @@ import {
"Nanaloveyuki/BitLogger/src/file_model", "Nanaloveyuki/BitLogger/src/file_model",
"Nanaloveyuki/BitLogger/src/formatting", "Nanaloveyuki/BitLogger/src/formatting",
"Nanaloveyuki/BitLogger/src/queue_model", "Nanaloveyuki/BitLogger/src/queue_model",
"maria/json_parser", "moonbitlang/core/json",
"moonbitlang/core/string", "moonbitlang/core/string",
} }
+4 -5
View File
@@ -6,7 +6,6 @@ import {
"Nanaloveyuki/BitLogger/src/file_model", "Nanaloveyuki/BitLogger/src/file_model",
"Nanaloveyuki/BitLogger/src/formatting", "Nanaloveyuki/BitLogger/src/formatting",
"Nanaloveyuki/BitLogger/src/queue_model", "Nanaloveyuki/BitLogger/src/queue_model",
"maria/json_parser",
} }
// Values // Values
@@ -16,13 +15,13 @@ pub fn default_sink_config() -> SinkConfig
pub fn default_text_formatter_config() -> TextFormatterConfig pub fn default_text_formatter_config() -> TextFormatterConfig
pub fn logger_config_to_json(LoggerConfig) -> @json_parser.JsonValue pub fn logger_config_to_json(LoggerConfig) -> Json
pub fn parse_logger_config_text(String) -> LoggerConfig raise ConfigError pub fn parse_logger_config_text(String) -> LoggerConfig raise ConfigError
pub fn queue_config_to_json(QueueConfig) -> @json_parser.JsonValue pub fn queue_config_to_json(QueueConfig) -> Json
pub fn sink_config_to_json(SinkConfig) -> @json_parser.JsonValue pub fn sink_config_to_json(SinkConfig) -> Json
pub fn stringify_logger_config(LoggerConfig, pretty? : Bool) -> String pub fn stringify_logger_config(LoggerConfig, pretty? : Bool) -> String
@@ -32,7 +31,7 @@ pub fn stringify_sink_config(SinkConfig, pretty? : Bool) -> String
pub fn stringify_text_formatter_config(TextFormatterConfig, pretty? : Bool) -> String pub fn stringify_text_formatter_config(TextFormatterConfig, pretty? : Bool) -> String
pub fn text_formatter_config_to_json(TextFormatterConfig) -> @json_parser.JsonValue pub fn text_formatter_config_to_json(TextFormatterConfig) -> Json
// Errors // Errors
pub(all) suberror ConfigError { pub(all) suberror ConfigError {
+9
View File
@@ -39,6 +39,15 @@ pub fn Record::with_fields(Self, Array[Field]) -> Self
pub fn Record::with_message(Self, String) -> Self pub fn Record::with_message(Self, String) -> Self
pub fn Record::with_target(Self, String) -> Self pub fn Record::with_target(Self, String) -> Self
pub struct TraceContext {
trace_id : String
span_id : String
trace_flags : String
trace_state : String?
}
pub fn TraceContext::as_fields(Self) -> Array[Field]
pub fn TraceContext::new(String, String, trace_flags? : String, trace_state? : String) -> Self
// Type aliases // Type aliases
// Traits // Traits
+31
View File
@@ -0,0 +1,31 @@
///|
pub struct TraceContext {
trace_id : String
span_id : String
trace_flags : String
trace_state : String?
}
///|
pub fn TraceContext::new(
trace_id : String,
span_id : String,
trace_flags? : String = "01",
trace_state? : String,
) -> TraceContext {
{ trace_id, span_id, trace_flags, trace_state }
}
///|
pub fn TraceContext::as_fields(self : TraceContext) -> Array[Field] {
let fields = [
field("trace_id", self.trace_id),
field("span_id", self.span_id),
field("trace_flags", self.trace_flags),
]
match self.trace_state {
Some(value) => fields.push(field("trace_state", value))
None => ()
}
fields
}
+28 -43
View File
@@ -1,34 +1,27 @@
///| ///|
pub fn file_rotation_config_to_json( pub fn file_rotation_config_to_json(config : FileRotation) -> Json {
config : FileRotation, let obj : Map[String, Json] = {
) -> @json_parser.JsonValue { "max_bytes": Json::number(config.max_bytes.to_double()),
let obj : Map[String, @json_parser.JsonValue] = { "max_backups": Json::number(config.max_backups.to_double()),
"max_bytes": @json_parser.JsonValue::Number(config.max_bytes.to_double()),
"max_backups": @json_parser.JsonValue::Number(
config.max_backups.to_double(),
),
} }
match config.native_wide_max_bytes { match config.native_wide_max_bytes {
Some(value) => Some(value) => obj["max_bytes_i64"] = Json::string(value.to_string())
obj["max_bytes_i64"] = @json_parser.JsonValue::String(value.to_string())
None => () None => ()
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
pub fn file_sink_policy_to_json( pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> Json {
policy : FileSinkPolicy, let obj : Map[String, Json] = {
) -> @json_parser.JsonValue { "append": Json::boolean(policy.append),
let obj : Map[String, @json_parser.JsonValue] = { "auto_flush": Json::boolean(policy.auto_flush),
"append": @json_parser.JsonValue::Bool(policy.append),
"auto_flush": @json_parser.JsonValue::Bool(policy.auto_flush),
} }
match policy.rotation { match policy.rotation {
None => obj["rotation"] = @json_parser.JsonValue::Null None => obj["rotation"] = Json::null()
Some(rotation) => obj["rotation"] = file_rotation_config_to_json(rotation) Some(rotation) => obj["rotation"] = file_rotation_config_to_json(rotation)
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
@@ -38,37 +31,29 @@ pub fn stringify_file_sink_policy(
) -> String { ) -> String {
let value = file_sink_policy_to_json(policy) let value = file_sink_policy_to_json(policy)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
///| ///|
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue { pub fn file_sink_state_to_json(state : FileSinkState) -> Json {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, Json] = {
"path": @json_parser.JsonValue::String(state.path), "path": Json::string(state.path),
"available": @json_parser.JsonValue::Bool(state.available), "available": Json::boolean(state.available),
"append": @json_parser.JsonValue::Bool(state.append), "append": Json::boolean(state.append),
"auto_flush": @json_parser.JsonValue::Bool(state.auto_flush), "auto_flush": Json::boolean(state.auto_flush),
"open_failures": @json_parser.JsonValue::Number( "open_failures": Json::number(state.open_failures.to_double()),
state.open_failures.to_double(), "write_failures": Json::number(state.write_failures.to_double()),
), "flush_failures": Json::number(state.flush_failures.to_double()),
"write_failures": @json_parser.JsonValue::Number( "rotation_failures": Json::number(state.rotation_failures.to_double()),
state.write_failures.to_double(),
),
"flush_failures": @json_parser.JsonValue::Number(
state.flush_failures.to_double(),
),
"rotation_failures": @json_parser.JsonValue::Number(
state.rotation_failures.to_double(),
),
} }
match state.rotation { match state.rotation {
None => obj["rotation"] = @json_parser.JsonValue::Null None => obj["rotation"] = Json::null()
Some(rotation) => obj["rotation"] = file_rotation_config_to_json(rotation) Some(rotation) => obj["rotation"] = file_rotation_config_to_json(rotation)
} }
@json_parser.JsonValue::Object(obj) Json::object(obj)
} }
///| ///|
@@ -78,8 +63,8 @@ pub fn stringify_file_sink_state(
) -> String { ) -> String {
let value = file_sink_state_to_json(state) let value = file_sink_state_to_json(state)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
+1 -1
View File
@@ -1,3 +1,3 @@
import { import {
"maria/json_parser", "moonbitlang/core/json",
} }
+4 -8
View File
@@ -1,22 +1,18 @@
// Generated using `moon info`, DON'T EDIT IT // Generated using `moon info`, DON'T EDIT IT
package "Nanaloveyuki/BitLogger/src/file_model" package "Nanaloveyuki/BitLogger/src/file_model"
import {
"maria/json_parser",
}
// Values // Values
pub fn file_rotation(Int, max_backups? : Int) -> FileRotation pub fn file_rotation(Int, max_backups? : Int) -> FileRotation
pub fn file_rotation_config_to_json(FileRotation) -> @json_parser.JsonValue pub fn file_rotation_config_to_json(FileRotation) -> Json
pub fn file_rotation_i64(Int64, max_backups? : Int) -> FileRotation pub fn file_rotation_i64(Int64, max_backups? : Int) -> FileRotation
pub fn file_sink_policy_to_json(FileSinkPolicy) -> @json_parser.JsonValue pub fn file_sink_policy_to_json(FileSinkPolicy) -> Json
pub fn file_sink_state_to_json(FileSinkState) -> @json_parser.JsonValue pub fn file_sink_state_to_json(FileSinkState) -> Json
pub fn runtime_file_state_to_json(RuntimeFileState) -> @json_parser.JsonValue pub fn runtime_file_state_to_json(RuntimeFileState) -> Json
pub fn stringify_file_sink_policy(FileSinkPolicy, pretty? : Bool) -> String pub fn stringify_file_sink_policy(FileSinkPolicy, pretty? : Bool) -> String
+7 -13
View File
@@ -17,18 +17,12 @@ pub fn RuntimeFileState::new(
} }
///| ///|
pub fn runtime_file_state_to_json( pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {
state : RuntimeFileState, Json::object({
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({
"file": file_sink_state_to_json(state.file), "file": file_sink_state_to_json(state.file),
"queued": @json_parser.JsonValue::Bool(state.queued), "queued": Json::boolean(state.queued),
"pending_count": @json_parser.JsonValue::Number( "pending_count": Json::number(state.pending_count.to_double()),
state.pending_count.to_double(), "dropped_count": Json::number(state.dropped_count.to_double()),
),
"dropped_count": @json_parser.JsonValue::Number(
state.dropped_count.to_double(),
),
}) })
} }
@@ -39,8 +33,8 @@ pub fn stringify_runtime_file_state(
) -> String { ) -> String {
let value = runtime_file_state_to_json(state) let value = runtime_file_state_to_json(state)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) value.stringify(indent=2)
} else { } else {
@json_parser.stringify(value) value.stringify()
} }
} }
+32
View File
@@ -0,0 +1,32 @@
///|
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")
}
}
+8
View File
@@ -80,6 +80,14 @@ pub fn[S] LibraryLogger::bind(
self.with_context_fields(fields) self.with_context_fields(fields)
} }
///|
pub fn[S] LibraryLogger::with_trace_context(
self : LibraryLogger[S],
context : TraceContext,
) -> LibraryLogger[ContextSink[S]] {
library_logger(self.inner.with_trace_context(context))
}
///| ///|
pub fn[S] LibraryLogger::is_enabled( pub fn[S] LibraryLogger::is_enabled(
self : LibraryLogger[S], self : LibraryLogger[S],
+8
View File
@@ -57,6 +57,14 @@ pub fn[S] Logger::bind(
self.with_context_fields(fields) self.with_context_fields(fields)
} }
///|
pub fn[S] Logger::with_trace_context(
self : Logger[S],
context : TraceContext,
) -> Logger[ContextSink[S]] {
self.with_context_fields(context.as_fields())
}
///| ///|
pub fn[S] Logger::with_filter( pub fn[S] Logger::with_filter(
self : Logger[S], self : Logger[S],
+67 -74
View File
@@ -43,11 +43,7 @@ test "config subtype json helpers stringify stable shapes" {
content="{\"kind\":\"file\",\"path\":\"logs/demo.log\",\"append\":false,\"auto_flush\":false,\"text_formatter\":{\"show_timestamp\":false,\"show_level\":true,\"show_target\":true,\"show_fields\":true,\"separator\":\" \",\"field_separator\":\" \",\"template\":\"\",\"color_mode\":\"auto\",\"color_support\":\"truecolor\",\"style_markup\":\"full\",\"target_style_markup\":\"disabled\",\"fields_style_markup\":\"disabled\"},\"rotation\":{\"max_bytes\":128,\"max_backups\":2}}", content="{\"kind\":\"file\",\"path\":\"logs/demo.log\",\"append\":false,\"auto_flush\":false,\"text_formatter\":{\"show_timestamp\":false,\"show_level\":true,\"show_target\":true,\"show_fields\":true,\"separator\":\" \",\"field_separator\":\" \",\"template\":\"\",\"color_mode\":\"auto\",\"color_support\":\"truecolor\",\"style_markup\":\"full\",\"target_style_markup\":\"disabled\",\"fields_style_markup\":\"disabled\"},\"rotation\":{\"max_bytes\":128,\"max_backups\":2}}",
) )
inspect( inspect(
@json_parser.stringify( file_rotation_config_to_json(file_rotation_i64(4294967296L, max_backups=2)).stringify(),
file_rotation_config_to_json(
file_rotation_i64(4294967296L, max_backups=2),
),
),
content="{\"max_bytes\":2147483647,\"max_backups\":2,\"max_bytes_i64\":\"4294967296\"}", content="{\"max_bytes\":2147483647,\"max_backups\":2,\"max_bytes_i64\":\"4294967296\"}",
) )
} }
@@ -57,17 +53,17 @@ test "config json helpers export stable structured shapes" {
let queue_json = queue_config_to_json( let queue_json = queue_config_to_json(
QueueConfig::new(8, overflow=QueueOverflowPolicy::DropOldest), QueueConfig::new(8, overflow=QueueOverflowPolicy::DropOldest),
) )
let queue_obj = queue_json.as_object().unwrap() let queue_obj = queue_json.expect_json_object()
inspect( inspect(
@json_parser.stringify(queue_json), queue_json.stringify(),
content="{\"max_pending\":8,\"overflow\":\"DropOldest\"}", content="{\"max_pending\":8,\"overflow\":\"DropOldest\"}",
) )
inspect( inspect(
queue_obj.get("max_pending").unwrap().as_number().unwrap().to_int(), queue_obj.get("max_pending").unwrap().expect_json_number().to_int(),
content="8", content="8",
) )
inspect( inspect(
queue_obj.get("overflow").unwrap().as_string().unwrap(), queue_obj.get("overflow").unwrap().expect_json_string(),
content="DropOldest", content="DropOldest",
) )
@@ -88,55 +84,54 @@ test "config json helpers export stable structured shapes" {
style_tags={ "accent": text_style(fg=Some("#4cc9f0"), bold=true) }, style_tags={ "accent": text_style(fg=Some("#4cc9f0"), bold=true) },
), ),
) )
let formatter_obj = formatter_json.as_object().unwrap() let formatter_obj = formatter_json.expect_json_object()
let style_tags_obj = formatter_obj let style_tags_obj = formatter_obj
.get("style_tags") .get("style_tags")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap() let accent_obj = style_tags_obj.get("accent").unwrap().expect_json_object()
let accent_obj = style_tags_obj.get("accent").unwrap().as_object().unwrap()
inspect( inspect(
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(), formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
content="false", content="false",
) )
inspect( inspect(
formatter_obj.get("show_target").unwrap().as_bool().unwrap(), formatter_obj.get("show_target").unwrap().expect_json_bool(),
content="false", content="false",
) )
inspect( inspect(
formatter_obj.get("separator").unwrap().as_string().unwrap(), formatter_obj.get("separator").unwrap().expect_json_string(),
content=" | ", content=" | ",
) )
inspect( inspect(
formatter_obj.get("field_separator").unwrap().as_string().unwrap(), formatter_obj.get("field_separator").unwrap().expect_json_string(),
content=",", content=",",
) )
inspect( inspect(
formatter_obj.get("template").unwrap().as_string().unwrap(), formatter_obj.get("template").unwrap().expect_json_string(),
content="[{level}] {message} :: {fields}", content="[{level}] {message} :: {fields}",
) )
inspect( inspect(
formatter_obj.get("color_mode").unwrap().as_string().unwrap(), formatter_obj.get("color_mode").unwrap().expect_json_string(),
content="always", content="always",
) )
inspect( inspect(
formatter_obj.get("color_support").unwrap().as_string().unwrap(), formatter_obj.get("color_support").unwrap().expect_json_string(),
content="basic", content="basic",
) )
inspect( inspect(
formatter_obj.get("style_markup").unwrap().as_string().unwrap(), formatter_obj.get("style_markup").unwrap().expect_json_string(),
content="builtin", content="builtin",
) )
inspect( inspect(
formatter_obj.get("target_style_markup").unwrap().as_string().unwrap(), formatter_obj.get("target_style_markup").unwrap().expect_json_string(),
content="builtin", content="builtin",
) )
inspect( inspect(
formatter_obj.get("fields_style_markup").unwrap().as_string().unwrap(), formatter_obj.get("fields_style_markup").unwrap().expect_json_string(),
content="disabled", content="disabled",
) )
inspect(accent_obj.get("bold").unwrap().as_bool().unwrap(), content="true") inspect(accent_obj.get("bold").unwrap().expect_json_bool(), content="true")
inspect(accent_obj.get("fg").unwrap().as_string().unwrap(), content="#4cc9f0") inspect(accent_obj.get("fg").unwrap().expect_json_string(), content="#4cc9f0")
let sink_json = sink_config_to_json( let sink_json = sink_config_to_json(
SinkConfig::new( SinkConfig::new(
@@ -151,37 +146,36 @@ test "config json helpers export stable structured shapes" {
), ),
), ),
) )
let sink_obj = sink_json.as_object().unwrap() let sink_obj = sink_json.expect_json_object()
let sink_formatter_obj = sink_obj let sink_formatter_obj = sink_obj
.get("text_formatter") .get("text_formatter")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap() let rotation_obj = sink_obj.get("rotation").unwrap().expect_json_object()
let rotation_obj = sink_obj.get("rotation").unwrap().as_object().unwrap() inspect(sink_obj.get("kind").unwrap().expect_json_string(), content="file")
inspect(sink_obj.get("kind").unwrap().as_string().unwrap(), content="file")
inspect( inspect(
sink_obj.get("path").unwrap().as_string().unwrap(), sink_obj.get("path").unwrap().expect_json_string(),
content="logs/demo.log", content="logs/demo.log",
) )
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="false") inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="false")
inspect( inspect(
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(), sink_obj.get("auto_flush").unwrap().expect_json_bool(),
content="false", content="false",
) )
inspect( inspect(
sink_formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(), sink_formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
content="false", content="false",
) )
inspect( inspect(
sink_formatter_obj.get("color_mode").unwrap().as_string().unwrap(), sink_formatter_obj.get("color_mode").unwrap().expect_json_string(),
content="auto", content="auto",
) )
inspect( inspect(
rotation_obj.get("max_bytes").unwrap().as_number().unwrap().to_int(), rotation_obj.get("max_bytes").unwrap().expect_json_number().to_int(),
content="128", content="128",
) )
inspect( inspect(
rotation_obj.get("max_backups").unwrap().as_number().unwrap().to_int(), rotation_obj.get("max_backups").unwrap().expect_json_number().to_int(),
content="2", content="2",
) )
inspect(rotation_obj.get("max_bytes_i64") is None, content="true") inspect(rotation_obj.get("max_bytes_i64") is None, content="true")
@@ -189,17 +183,17 @@ test "config json helpers export stable structured shapes" {
let wide_rotation_json = file_rotation_config_to_json( let wide_rotation_json = file_rotation_config_to_json(
file_rotation_i64(4294967296L, max_backups=3), file_rotation_i64(4294967296L, max_backups=3),
) )
let wide_rotation_obj = wide_rotation_json.as_object().unwrap() let wide_rotation_obj = wide_rotation_json.expect_json_object()
inspect( inspect(
wide_rotation_obj.get("max_bytes").unwrap().as_number().unwrap().to_int(), wide_rotation_obj.get("max_bytes").unwrap().expect_json_number().to_int(),
content="2147483647", content="2147483647",
) )
inspect( inspect(
wide_rotation_obj.get("max_backups").unwrap().as_number().unwrap().to_int(), wide_rotation_obj.get("max_backups").unwrap().expect_json_number().to_int(),
content="3", content="3",
) )
inspect( inspect(
wide_rotation_obj.get("max_bytes_i64").unwrap().as_string().unwrap(), wide_rotation_obj.get("max_bytes_i64").unwrap().expect_json_string(),
content="4294967296", content="4294967296",
) )
@@ -212,28 +206,28 @@ test "config json helpers export stable structured shapes" {
queue=Some(QueueConfig::new(8, overflow=QueueOverflowPolicy::DropNewest)), queue=Some(QueueConfig::new(8, overflow=QueueOverflowPolicy::DropNewest)),
), ),
) )
let logger_obj = logger_json.as_object().unwrap() let logger_obj = logger_json.expect_json_object()
let logger_sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap() let logger_sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
let logger_queue_obj = logger_obj.get("queue").unwrap().as_object().unwrap() let logger_queue_obj = logger_obj.get("queue").unwrap().expect_json_object()
inspect( inspect(
logger_obj.get("min_level").unwrap().as_string().unwrap(), logger_obj.get("min_level").unwrap().expect_json_string(),
content="WARN", content="WARN",
) )
inspect(logger_obj.get("target").unwrap().as_string().unwrap(), content="api") inspect(logger_obj.get("target").unwrap().expect_json_string(), content="api")
inspect( inspect(
logger_obj.get("timestamp").unwrap().as_bool().unwrap(), logger_obj.get("timestamp").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
logger_sink_obj.get("kind").unwrap().as_string().unwrap(), logger_sink_obj.get("kind").unwrap().expect_json_string(),
content="text_console", content="text_console",
) )
inspect( inspect(
logger_queue_obj.get("max_pending").unwrap().as_number().unwrap().to_int(), logger_queue_obj.get("max_pending").unwrap().expect_json_number().to_int(),
content="8", content="8",
) )
inspect( inspect(
logger_queue_obj.get("overflow").unwrap().as_string().unwrap(), logger_queue_obj.get("overflow").unwrap().expect_json_string(),
content="DropNewest", content="DropNewest",
) )
} }
@@ -242,81 +236,80 @@ test "config json helpers export stable structured shapes" {
test "logger config json export materializes parsed omitted defaults" { test "logger config json export materializes parsed omitted defaults" {
let parsed = parse_logger_config_text("{}") let parsed = parse_logger_config_text("{}")
let logger_json = logger_config_to_json(parsed) let logger_json = logger_config_to_json(parsed)
let logger_obj = logger_json.as_object().unwrap() let logger_obj = logger_json.expect_json_object()
let sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap() let sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
let formatter_obj = sink_obj let formatter_obj = sink_obj
.get("text_formatter") .get("text_formatter")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap()
inspect( inspect(
@json_parser.stringify(logger_json), logger_json.stringify(),
content="{\"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\"}}}", content="{\"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\"}}}",
) )
inspect( inspect(
logger_obj.get("min_level").unwrap().as_string().unwrap(), logger_obj.get("min_level").unwrap().expect_json_string(),
content="INFO", content="INFO",
) )
inspect(logger_obj.get("target").unwrap().as_string().unwrap(), content="") inspect(logger_obj.get("target").unwrap().expect_json_string(), content="")
inspect( inspect(
logger_obj.get("timestamp").unwrap().as_bool().unwrap(), logger_obj.get("timestamp").unwrap().expect_json_bool(),
content="false", content="false",
) )
inspect(logger_obj.get("queue") is None, content="true") inspect(logger_obj.get("queue") is None, content="true")
inspect(sink_obj.get("kind").unwrap().as_string().unwrap(), content="console") inspect(sink_obj.get("kind").unwrap().expect_json_string(), content="console")
inspect(sink_obj.get("path").unwrap().as_string().unwrap(), content="") inspect(sink_obj.get("path").unwrap().expect_json_string(), content="")
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="true") inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="true")
inspect( inspect(
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(), sink_obj.get("auto_flush").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(), formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_level").unwrap().as_bool().unwrap(), formatter_obj.get("show_level").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_target").unwrap().as_bool().unwrap(), formatter_obj.get("show_target").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("show_fields").unwrap().as_bool().unwrap(), formatter_obj.get("show_fields").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
formatter_obj.get("separator").unwrap().as_string().unwrap(), formatter_obj.get("separator").unwrap().expect_json_string(),
content=" ", content=" ",
) )
inspect( inspect(
formatter_obj.get("field_separator").unwrap().as_string().unwrap(), formatter_obj.get("field_separator").unwrap().expect_json_string(),
content=" ", content=" ",
) )
inspect( inspect(
formatter_obj.get("template").unwrap().as_string().unwrap(), formatter_obj.get("template").unwrap().expect_json_string(),
content="", content="",
) )
inspect( inspect(
formatter_obj.get("color_mode").unwrap().as_string().unwrap(), formatter_obj.get("color_mode").unwrap().expect_json_string(),
content="never", content="never",
) )
inspect( inspect(
formatter_obj.get("color_support").unwrap().as_string().unwrap(), formatter_obj.get("color_support").unwrap().expect_json_string(),
content="truecolor", content="truecolor",
) )
inspect( inspect(
formatter_obj.get("style_markup").unwrap().as_string().unwrap(), formatter_obj.get("style_markup").unwrap().expect_json_string(),
content="full", content="full",
) )
inspect( inspect(
formatter_obj.get("target_style_markup").unwrap().as_string().unwrap(), formatter_obj.get("target_style_markup").unwrap().expect_json_string(),
content="disabled", content="disabled",
) )
inspect( inspect(
formatter_obj.get("fields_style_markup").unwrap().as_string().unwrap(), formatter_obj.get("fields_style_markup").unwrap().expect_json_string(),
content="disabled", content="disabled",
) )
inspect(formatter_obj.get("style_tags") is None, content="true") inspect(formatter_obj.get("style_tags") is None, content="true")
-1
View File
@@ -9,7 +9,6 @@ import {
"Nanaloveyuki/BitLogger/src/queue_model", "Nanaloveyuki/BitLogger/src/queue_model",
"Nanaloveyuki/BitLogger/src/runtime", "Nanaloveyuki/BitLogger/src/runtime",
"Nanaloveyuki/BitLogger/src/sink_graph", "Nanaloveyuki/BitLogger/src/sink_graph",
"maria/json_parser",
"moonbitlang/core/array", "moonbitlang/core/array",
"moonbitlang/core/builtin", "moonbitlang/core/builtin",
"moonbitlang/core/env", "moonbitlang/core/env",
+14 -9
View File
@@ -10,7 +10,6 @@ import {
"Nanaloveyuki/BitLogger/src/queue_model", "Nanaloveyuki/BitLogger/src/queue_model",
"Nanaloveyuki/BitLogger/src/runtime", "Nanaloveyuki/BitLogger/src/runtime",
"Nanaloveyuki/BitLogger/src/sink_graph", "Nanaloveyuki/BitLogger/src/sink_graph",
"maria/json_parser",
} }
// Values // Values
@@ -70,15 +69,15 @@ pub fn file(String, min_level? : @core.Level, target? : String, timestamp? : Boo
pub fn file_rotation(Int, max_backups? : Int) -> @file_model.FileRotation pub fn file_rotation(Int, max_backups? : Int) -> @file_model.FileRotation
pub fn file_rotation_config_to_json(@file_model.FileRotation) -> @json_parser.JsonValue pub fn file_rotation_config_to_json(@file_model.FileRotation) -> Json
pub fn file_rotation_i64(Int64, max_backups? : Int) -> @file_model.FileRotation pub fn file_rotation_i64(Int64, max_backups? : Int) -> @file_model.FileRotation
pub fn file_sink(String, append? : Bool, auto_flush? : Bool, rotation? : @file_model.FileRotation?, formatter? : (@core.Record) -> String) -> @file_runtime.FileSink pub fn file_sink(String, append? : Bool, auto_flush? : Bool, rotation? : @file_model.FileRotation?, formatter? : (@core.Record) -> String) -> @file_runtime.FileSink
pub fn file_sink_policy_to_json(@file_model.FileSinkPolicy) -> @json_parser.JsonValue pub fn file_sink_policy_to_json(@file_model.FileSinkPolicy) -> Json
pub fn file_sink_state_to_json(@file_model.FileSinkState) -> @json_parser.JsonValue pub fn file_sink_state_to_json(@file_model.FileSinkState) -> Json
pub fn[S] filter_sink(S, (@core.Record) -> Bool) -> @sink_graph.FilterSink[S] pub fn[S] filter_sink(S, (@core.Record) -> Bool) -> @sink_graph.FilterSink[S]
@@ -106,7 +105,7 @@ pub fn level_at_least(@core.Level) -> (@core.Record) -> Bool
pub fn log(@core.Level, String, fields? : Array[@core.Field]) -> Unit pub fn log(@core.Level, String, fields? : Array[@core.Field]) -> Unit
pub fn logger_config_to_json(@config_model.LoggerConfig) -> @json_parser.JsonValue pub fn logger_config_to_json(@config_model.LoggerConfig) -> Json
pub fn message_contains(String) -> (@core.Record) -> Bool pub fn message_contains(String) -> (@core.Record) -> Bool
@@ -126,7 +125,7 @@ pub fn[S] patch_sink(S, (@core.Record) -> @core.Record) -> @sink_graph.PatchSink
pub fn prefix_message(String) -> (@core.Record) -> @core.Record pub fn prefix_message(String) -> (@core.Record) -> @core.Record
pub fn queue_config_to_json(@config_model.QueueConfig) -> @json_parser.JsonValue pub fn queue_config_to_json(@config_model.QueueConfig) -> Json
pub fn[S] queued_sink(S, max_pending? : Int, overflow? : @queue_model.QueueOverflowPolicy) -> @sink_graph.QueuedSink[S] pub fn[S] queued_sink(S, max_pending? : Int, overflow? : @queue_model.QueueOverflowPolicy) -> @sink_graph.QueuedSink[S]
@@ -136,7 +135,7 @@ pub fn redact_fields(Array[String], placeholder? : String) -> (@core.Record) ->
pub fn reset_global_style_tag_registry() -> Unit pub fn reset_global_style_tag_registry() -> Unit
pub fn runtime_file_state_to_json(@file_model.RuntimeFileState) -> @json_parser.JsonValue pub fn runtime_file_state_to_json(@file_model.RuntimeFileState) -> Json
pub fn set_default_min_level(@core.Level) -> Unit pub fn set_default_min_level(@core.Level) -> Unit
@@ -146,7 +145,7 @@ pub fn set_global_style_tag_registry(@formatting.StyleTagRegistry) -> Unit
pub fn set_target(String) -> (@core.Record) -> @core.Record pub fn set_target(String) -> (@core.Record) -> @core.Record
pub fn sink_config_to_json(@config_model.SinkConfig) -> @json_parser.JsonValue pub fn sink_config_to_json(@config_model.SinkConfig) -> Json
pub fn[A, B] split_by_level(A, B, min_level? : @core.Level) -> @sink_graph.SplitSink[A, B] pub fn[A, B] split_by_level(A, B, min_level? : @core.Level) -> @sink_graph.SplitSink[A, B]
@@ -182,12 +181,14 @@ pub fn text_console_sink(@formatting.TextFormatter) -> @sink_graph.FormattedCons
pub fn text_formatter(show_timestamp? : Bool, show_level? : Bool, show_target? : Bool, show_fields? : Bool, separator? : String, field_separator? : String, template? : String, color_mode? : @formatting.ColorMode, color_support? : @formatting.ColorSupport, style_markup? : @formatting.StyleMarkupMode, target_style_markup? : @formatting.StyleMarkupMode, fields_style_markup? : @formatting.StyleMarkupMode, style_tags? : @formatting.StyleTagRegistry?) -> @formatting.TextFormatter pub fn text_formatter(show_timestamp? : Bool, show_level? : Bool, show_target? : Bool, show_fields? : Bool, separator? : String, field_separator? : String, template? : String, color_mode? : @formatting.ColorMode, color_support? : @formatting.ColorSupport, style_markup? : @formatting.StyleMarkupMode, target_style_markup? : @formatting.StyleMarkupMode, fields_style_markup? : @formatting.StyleMarkupMode, style_tags? : @formatting.StyleTagRegistry?) -> @formatting.TextFormatter
pub fn text_formatter_config_to_json(@config_model.TextFormatterConfig) -> @json_parser.JsonValue pub fn text_formatter_config_to_json(@config_model.TextFormatterConfig) -> Json
pub fn text_style(fg? : String?, bg? : String?, bold? : Bool, dim? : Bool, italic? : Bool, underline? : Bool) -> @formatting.TextStyle pub fn text_style(fg? : String?, bg? : String?, bold? : Bool, dim? : Bool, italic? : Bool, underline? : Bool) -> @formatting.TextStyle
pub fn trace(String, fields? : Array[@core.Field]) -> Unit pub fn trace(String, fields? : Array[@core.Field]) -> Unit
pub fn trace_context(String, String, trace_flags? : String, trace_state? : String) -> @core.TraceContext
pub fn warn(String, fields? : Array[@core.Field]) -> Unit pub fn warn(String, fields? : Array[@core.Field]) -> Unit
pub fn with_file_rotation(@config_model.LoggerConfig, Int, max_backups? : Int) -> @config_model.LoggerConfig pub fn with_file_rotation(@config_model.LoggerConfig, Int, max_backups? : Int) -> @config_model.LoggerConfig
@@ -213,6 +214,7 @@ pub fn[S] LibraryLogger::to_logger(Self[S]) -> Logger[S]
pub fn[S : @sink_graph.Sink] LibraryLogger::warn(Self[S], String, fields? : Array[@core.Field]) -> Unit pub fn[S : @sink_graph.Sink] LibraryLogger::warn(Self[S], String, fields? : Array[@core.Field]) -> Unit
pub fn[S] LibraryLogger::with_context_fields(Self[S], Array[@core.Field]) -> Self[@sink_graph.ContextSink[S]] pub fn[S] LibraryLogger::with_context_fields(Self[S], Array[@core.Field]) -> Self[@sink_graph.ContextSink[S]]
pub fn[S] LibraryLogger::with_target(Self[S], String) -> Self[S] pub fn[S] LibraryLogger::with_target(Self[S], String) -> Self[S]
pub fn[S] LibraryLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[@sink_graph.ContextSink[S]]
pub struct Logger[S] { pub struct Logger[S] {
min_level : @core.Level min_level : @core.Level
@@ -277,6 +279,7 @@ pub fn[S] Logger::with_patch(Self[S], (@core.Record) -> @core.Record) -> Self[@s
pub fn[S] Logger::with_queue(Self[S], max_pending? : Int, overflow? : @queue_model.QueueOverflowPolicy) -> Self[@sink_graph.QueuedSink[S]] pub fn[S] Logger::with_queue(Self[S], max_pending? : Int, overflow? : @queue_model.QueueOverflowPolicy) -> Self[@sink_graph.QueuedSink[S]]
pub fn[S] Logger::with_target(Self[S], String) -> Self[S] pub fn[S] Logger::with_target(Self[S], String) -> Self[S]
pub fn[S] Logger::with_timestamp(Self[S], enabled? : Bool) -> Self[S] pub fn[S] Logger::with_timestamp(Self[S], enabled? : Bool) -> Self[S]
pub fn[S] Logger::with_trace_context(Self[S], @core.TraceContext) -> Self[@sink_graph.ContextSink[S]]
// Type aliases // Type aliases
pub type ApplicationLogger = Logger[@runtime.RuntimeSink] pub type ApplicationLogger = Logger[@runtime.RuntimeSink]
@@ -359,6 +362,8 @@ pub using @config_model {type TextFormatterConfig}
pub using @formatting {type TextStyle} pub using @formatting {type TextStyle}
pub using @core {type TraceContext}
pub using @sink_graph {trait Sink} pub using @sink_graph {trait Sink}
// Traits // Traits
+22
View File
@@ -1,6 +1,9 @@
///| ///|
pub type Field = @core.Field pub type Field = @core.Field
///|
pub type TraceContext = @core.TraceContext
///| ///|
pub fn field(key : String, value : String) -> Field { pub fn field(key : String, value : String) -> Field {
@core.field(key, value) @core.field(key, value)
@@ -11,6 +14,25 @@ pub fn fields(entries : Array[(String, String)]) -> Array[Field] {
@core.fields(entries) @core.fields(entries)
} }
///|
pub fn trace_context(
trace_id : String,
span_id : String,
trace_flags? : String = "01",
trace_state? : String,
) -> TraceContext {
match trace_state {
Some(value) =>
@core.TraceContext::new(
trace_id,
span_id,
trace_flags~,
trace_state=value,
)
None => @core.TraceContext::new(trace_id, span_id, trace_flags~)
}
}
///| ///|
pub type Record = @core.Record pub type Record = @core.Record
+1 -1
View File
@@ -4,5 +4,5 @@ import {
"Nanaloveyuki/BitLogger/src/file_model", "Nanaloveyuki/BitLogger/src/file_model",
"Nanaloveyuki/BitLogger/src/formatting", "Nanaloveyuki/BitLogger/src/formatting",
"Nanaloveyuki/BitLogger/src/sink_graph", "Nanaloveyuki/BitLogger/src/sink_graph",
"maria/json_parser", "moonbitlang/core/json",
} }
+1 -2
View File
@@ -7,7 +7,6 @@ import {
"Nanaloveyuki/BitLogger/src/file_model", "Nanaloveyuki/BitLogger/src/file_model",
"Nanaloveyuki/BitLogger/src/file_runtime", "Nanaloveyuki/BitLogger/src/file_runtime",
"Nanaloveyuki/BitLogger/src/sink_graph", "Nanaloveyuki/BitLogger/src/sink_graph",
"maria/json_parser",
} }
// Values // Values
@@ -15,7 +14,7 @@ pub fn apply_queue_config(RuntimeSink, @config_model.QueueConfig) -> RuntimeSink
pub fn build_runtime_sink(@config_model.SinkConfig) -> RuntimeSink pub fn build_runtime_sink(@config_model.SinkConfig) -> RuntimeSink
pub fn runtime_file_state_to_json(@file_model.RuntimeFileState) -> @json_parser.JsonValue pub fn runtime_file_state_to_json(@file_model.RuntimeFileState) -> Json
pub fn stringify_runtime_file_state(@file_model.RuntimeFileState, pretty? : Bool) -> String pub fn stringify_runtime_file_state(@file_model.RuntimeFileState, pretty? : Bool) -> String
+1 -3
View File
@@ -55,9 +55,7 @@ pub struct RuntimeSinkProgress {
} }
///| ///|
pub fn runtime_file_state_to_json( pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {
state : RuntimeFileState,
) -> @json_parser.JsonValue {
@file_model.runtime_file_state_to_json(state) @file_model.runtime_file_state_to_json(state)
} }
+50 -63
View File
@@ -3,17 +3,17 @@ test "file state json helpers stringify stable snapshots" {
let rotation_json = file_rotation_config_to_json( let rotation_json = file_rotation_config_to_json(
file_rotation(64, max_backups=2), file_rotation(64, max_backups=2),
) )
let rotation_obj = rotation_json.as_object().unwrap() let rotation_obj = rotation_json.expect_json_object()
inspect( inspect(
@json_parser.stringify(rotation_json), rotation_json.stringify(),
content="{\"max_bytes\":64,\"max_backups\":2}", content="{\"max_bytes\":64,\"max_backups\":2}",
) )
inspect( inspect(
rotation_obj.get("max_bytes").unwrap().as_number().unwrap().to_int(), rotation_obj.get("max_bytes").unwrap().expect_json_number().to_int(),
content="64", content="64",
) )
inspect( inspect(
rotation_obj.get("max_backups").unwrap().as_number().unwrap().to_int(), rotation_obj.get("max_backups").unwrap().expect_json_number().to_int(),
content="2", content="2",
) )
inspect(rotation_obj.get("max_bytes_i64") is None, content="true") inspect(rotation_obj.get("max_bytes_i64") is None, content="true")
@@ -21,9 +21,9 @@ test "file state json helpers stringify stable snapshots" {
let wide_rotation_json = file_rotation_config_to_json( let wide_rotation_json = file_rotation_config_to_json(
file_rotation_i64(4294967296L, max_backups=2), file_rotation_i64(4294967296L, max_backups=2),
) )
let wide_rotation_obj = wide_rotation_json.as_object().unwrap() let wide_rotation_obj = wide_rotation_json.expect_json_object()
inspect( inspect(
wide_rotation_obj.get("max_bytes_i64").unwrap().as_string().unwrap(), wide_rotation_obj.get("max_bytes_i64").unwrap().expect_json_string(),
content="4294967296", content="4294967296",
) )
@@ -40,51 +40,50 @@ test "file state json helpers stringify stable snapshots" {
rotation_failures=4, rotation_failures=4,
), ),
) )
let plain_obj = plain.as_object().unwrap() let plain_obj = plain.expect_json_object()
let plain_rotation_obj = plain_obj let plain_rotation_obj = plain_obj
.get("rotation") .get("rotation")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap()
inspect( inspect(
@json_parser.stringify(plain), plain.stringify(),
content="{\"path\":\"logs/demo.log\",\"available\":true,\"append\":false,\"auto_flush\":true,\"open_failures\":1,\"write_failures\":2,\"flush_failures\":3,\"rotation_failures\":4,\"rotation\":{\"max_bytes\":64,\"max_backups\":2}}", content="{\"path\":\"logs/demo.log\",\"available\":true,\"append\":false,\"auto_flush\":true,\"open_failures\":1,\"write_failures\":2,\"flush_failures\":3,\"rotation_failures\":4,\"rotation\":{\"max_bytes\":64,\"max_backups\":2}}",
) )
inspect( inspect(
plain_obj.get("path").unwrap().as_string().unwrap(), plain_obj.get("path").unwrap().expect_json_string(),
content="logs/demo.log", content="logs/demo.log",
) )
inspect( inspect(
plain_obj.get("available").unwrap().as_bool().unwrap(), plain_obj.get("available").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect(plain_obj.get("append").unwrap().as_bool().unwrap(), content="false") inspect(plain_obj.get("append").unwrap().expect_json_bool(), content="false")
inspect( inspect(
plain_obj.get("auto_flush").unwrap().as_bool().unwrap(), plain_obj.get("auto_flush").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
plain_obj.get("open_failures").unwrap().as_number().unwrap().to_int(), plain_obj.get("open_failures").unwrap().expect_json_number().to_int(),
content="1", content="1",
) )
inspect( inspect(
plain_obj.get("write_failures").unwrap().as_number().unwrap().to_int(), plain_obj.get("write_failures").unwrap().expect_json_number().to_int(),
content="2", content="2",
) )
inspect( inspect(
plain_obj.get("flush_failures").unwrap().as_number().unwrap().to_int(), plain_obj.get("flush_failures").unwrap().expect_json_number().to_int(),
content="3", content="3",
) )
inspect( inspect(
plain_obj.get("rotation_failures").unwrap().as_number().unwrap().to_int(), plain_obj.get("rotation_failures").unwrap().expect_json_number().to_int(),
content="4", content="4",
) )
inspect( inspect(
plain_rotation_obj.get("max_bytes").unwrap().as_number().unwrap().to_int(), plain_rotation_obj.get("max_bytes").unwrap().expect_json_number().to_int(),
content="64", content="64",
) )
inspect( inspect(
plain_rotation_obj.get("max_backups").unwrap().as_number().unwrap().to_int(), plain_rotation_obj.get("max_backups").unwrap().expect_json_number().to_int(),
content="2", content="2",
) )
@@ -101,18 +100,16 @@ test "file state json helpers stringify stable snapshots" {
rotation_failures=0, rotation_failures=0,
), ),
) )
let wide_obj = wide.as_object().unwrap() let wide_obj = wide.expect_json_object()
let wide_state_rotation_obj = wide_obj let wide_state_rotation_obj = wide_obj
.get("rotation") .get("rotation")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap()
inspect( inspect(
wide_state_rotation_obj wide_state_rotation_obj
.get("max_bytes") .get("max_bytes")
.unwrap() .unwrap()
.as_number() .expect_json_number()
.unwrap()
.to_int(), .to_int(),
content="2147483647", content="2147483647",
) )
@@ -120,13 +117,12 @@ test "file state json helpers stringify stable snapshots" {
wide_state_rotation_obj wide_state_rotation_obj
.get("max_backups") .get("max_backups")
.unwrap() .unwrap()
.as_number() .expect_json_number()
.unwrap()
.to_int(), .to_int(),
content="3", content="3",
) )
inspect( inspect(
wide_state_rotation_obj.get("max_bytes_i64").unwrap().as_string().unwrap(), wide_state_rotation_obj.get("max_bytes_i64").unwrap().expect_json_string(),
content="4294967296", content="4294967296",
) )
inspect( inspect(
@@ -167,27 +163,27 @@ test "runtime file state json helpers stringify queue snapshots" {
dropped_count=5, dropped_count=5,
), ),
) )
let runtime_obj = runtime_json.as_object().unwrap() let runtime_obj = runtime_json.expect_json_object()
let runtime_file_obj = runtime_obj.get("file").unwrap().as_object().unwrap() let runtime_file_obj = runtime_obj.get("file").unwrap().expect_json_object()
inspect(runtime_obj.get("queued").unwrap().as_bool().unwrap(), content="true") inspect(runtime_obj.get("queued").unwrap().expect_json_bool(), content="true")
inspect( inspect(
runtime_obj.get("pending_count").unwrap().as_number().unwrap().to_int(), runtime_obj.get("pending_count").unwrap().expect_json_number().to_int(),
content="7", content="7",
) )
inspect( inspect(
runtime_obj.get("dropped_count").unwrap().as_number().unwrap().to_int(), runtime_obj.get("dropped_count").unwrap().expect_json_number().to_int(),
content="5", content="5",
) )
inspect( inspect(
runtime_file_obj.get("path").unwrap().as_string().unwrap(), runtime_file_obj.get("path").unwrap().expect_json_string(),
content="logs/queue.log", content="logs/queue.log",
) )
inspect( inspect(
runtime_file_obj.get("available").unwrap().as_bool().unwrap(), runtime_file_obj.get("available").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
runtime_file_obj.get("rotation").unwrap() is @json_parser.JsonValue::Null, runtime_file_obj.get("rotation").unwrap() is Json::Null,
content="true", content="true",
) )
@@ -232,23 +228,20 @@ test "runtime file state json helpers stringify queue snapshots" {
dropped_count=0, dropped_count=0,
), ),
) )
let wide_runtime_obj = wide_runtime_json.as_object().unwrap() let wide_runtime_obj = wide_runtime_json.expect_json_object()
let wide_runtime_file_obj = wide_runtime_obj let wide_runtime_file_obj = wide_runtime_obj
.get("file") .get("file")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap()
let wide_runtime_rotation_obj = wide_runtime_file_obj let wide_runtime_rotation_obj = wide_runtime_file_obj
.get("rotation") .get("rotation")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap()
inspect( inspect(
wide_runtime_rotation_obj wide_runtime_rotation_obj
.get("max_bytes") .get("max_bytes")
.unwrap() .unwrap()
.as_number() .expect_json_number()
.unwrap()
.to_int(), .to_int(),
content="2147483647", content="2147483647",
) )
@@ -256,13 +249,12 @@ test "runtime file state json helpers stringify queue snapshots" {
wide_runtime_rotation_obj wide_runtime_rotation_obj
.get("max_backups") .get("max_backups")
.unwrap() .unwrap()
.as_number() .expect_json_number()
.unwrap()
.to_int(), .to_int(),
content="2", content="2",
) )
inspect( inspect(
wide_runtime_rotation_obj.get("max_bytes_i64").unwrap().as_string().unwrap(), wide_runtime_rotation_obj.get("max_bytes_i64").unwrap().expect_json_string(),
content="4294967296", content="4294967296",
) )
} }
@@ -276,31 +268,29 @@ test "file sink policy json helpers stringify stable policies" {
rotation=Some(file_rotation(96, max_backups=3)), rotation=Some(file_rotation(96, max_backups=3)),
), ),
) )
let policy_obj = policy_json.as_object().unwrap() let policy_obj = policy_json.expect_json_object()
let policy_rotation_obj = policy_obj let policy_rotation_obj = policy_obj
.get("rotation") .get("rotation")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap()
inspect( inspect(
@json_parser.stringify(policy_json), policy_json.stringify(),
content="{\"append\":false,\"auto_flush\":true,\"rotation\":{\"max_bytes\":96,\"max_backups\":3}}", content="{\"append\":false,\"auto_flush\":true,\"rotation\":{\"max_bytes\":96,\"max_backups\":3}}",
) )
inspect(policy_obj.get("append").unwrap().as_bool().unwrap(), content="false") inspect(policy_obj.get("append").unwrap().expect_json_bool(), content="false")
inspect( inspect(
policy_obj.get("auto_flush").unwrap().as_bool().unwrap(), policy_obj.get("auto_flush").unwrap().expect_json_bool(),
content="true", content="true",
) )
inspect( inspect(
policy_rotation_obj.get("max_bytes").unwrap().as_number().unwrap().to_int(), policy_rotation_obj.get("max_bytes").unwrap().expect_json_number().to_int(),
content="96", content="96",
) )
inspect( inspect(
policy_rotation_obj policy_rotation_obj
.get("max_backups") .get("max_backups")
.unwrap() .unwrap()
.as_number() .expect_json_number()
.unwrap()
.to_int(), .to_int(),
content="3", content="3",
) )
@@ -312,18 +302,16 @@ test "file sink policy json helpers stringify stable policies" {
rotation=Some(file_rotation_i64(4294967296L, max_backups=4)), rotation=Some(file_rotation_i64(4294967296L, max_backups=4)),
), ),
) )
let wide_policy_obj = wide_policy_json.as_object().unwrap() let wide_policy_obj = wide_policy_json.expect_json_object()
let wide_policy_rotation_obj = wide_policy_obj let wide_policy_rotation_obj = wide_policy_obj
.get("rotation") .get("rotation")
.unwrap() .unwrap()
.as_object() .expect_json_object()
.unwrap()
inspect( inspect(
wide_policy_rotation_obj wide_policy_rotation_obj
.get("max_bytes") .get("max_bytes")
.unwrap() .unwrap()
.as_number() .expect_json_number()
.unwrap()
.to_int(), .to_int(),
content="2147483647", content="2147483647",
) )
@@ -331,13 +319,12 @@ test "file sink policy json helpers stringify stable policies" {
wide_policy_rotation_obj wide_policy_rotation_obj
.get("max_backups") .get("max_backups")
.unwrap() .unwrap()
.as_number() .expect_json_number()
.unwrap()
.to_int(), .to_int(),
content="4", content="4",
) )
inspect( inspect(
wide_policy_rotation_obj.get("max_bytes_i64").unwrap().as_string().unwrap(), wide_policy_rotation_obj.get("max_bytes_i64").unwrap().expect_json_string(),
content="4294967296", content="4294967296",
) )
inspect( inspect(
+1 -3
View File
@@ -8,9 +8,7 @@ pub type RuntimeFileState = @file_model.RuntimeFileState
pub type RuntimeSinkProgress = @runtime.RuntimeSinkProgress pub type RuntimeSinkProgress = @runtime.RuntimeSinkProgress
///| ///|
pub fn runtime_file_state_to_json( pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {
state : RuntimeFileState,
) -> @json_parser.JsonValue {
@file_model.runtime_file_state_to_json(state) @file_model.runtime_file_state_to_json(state)
} }
+2 -4
View File
@@ -29,9 +29,7 @@ pub fn native_files_supported() -> Bool {
} }
///| ///|
pub fn file_sink_policy_to_json( pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> Json {
policy : FileSinkPolicy,
) -> @json_parser.JsonValue {
@file_model.file_sink_policy_to_json(policy) @file_model.file_sink_policy_to_json(policy)
} }
@@ -44,7 +42,7 @@ pub fn stringify_file_sink_policy(
} }
///| ///|
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue { pub fn file_sink_state_to_json(state : FileSinkState) -> Json {
@file_model.file_sink_state_to_json(state) @file_model.file_sink_state_to_json(state)
} }
+40
View File
@@ -0,0 +1,40 @@
///|
test "trace context maps W3C fields in a stable order" {
let context = trace_context(
"4bf92f3577b34da6a3ce929d0e0e4736",
"00f067aa0ba902b7",
trace_state="vendor=value",
)
let fields = context.as_fields()
inspect(fields.length(), content="4")
inspect(fields[0].key, content="trace_id")
inspect(fields[0].value, content="4bf92f3577b34da6a3ce929d0e0e4736")
inspect(fields[1].key, content="span_id")
inspect(fields[1].value, content="00f067aa0ba902b7")
inspect(fields[2].key, content="trace_flags")
inspect(fields[2].value, content="01")
inspect(fields[3].key, content="trace_state")
inspect(fields[3].value, content="vendor=value")
}
///|
test "sync facades bind trace context without mutating their base logger" {
let captured : Ref[Array[Field]] = Ref([])
let base = Logger::new(callback_sink(fn(rec) { captured.val = rec.fields }))
let context = trace_context("trace-1", "span-1", trace_flags="00")
let derived = base.with_trace_context(context)
base.info("base")
inspect(captured.val.length(), content="0")
derived.info("derived", fields=[field("event", "request")])
inspect(captured.val.length(), content="4")
inspect(captured.val[0].key, content="trace_id")
inspect(captured.val[2].value, content="00")
inspect(captured.val[3].key, content="event")
let library = LibraryLogger::new(
callback_sink(fn(rec) { captured.val = rec.fields }),
).with_trace_context(context)
library.info("library")
inspect(captured.val.length(), content="3")
inspect(captured.val[1].value, content="span-1")
}