mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-07-23 16:32:19 +00:00
✨ add wasm coverage and trace context
This commit is contained in:
@@ -67,9 +67,15 @@ jobs:
|
||||
moon check --deny-warn
|
||||
moon test --deny-warn
|
||||
moon check --target native --deny-warn
|
||||
moon check --target wasm --deny-warn
|
||||
moon check --target wasm-gc --deny-warn
|
||||
moon check --target js --deny-warn
|
||||
|
||||
- name: Test wasm path
|
||||
shell: bash
|
||||
run: |
|
||||
moon test --target wasm --deny-warn
|
||||
|
||||
- name: Run sync example
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -105,15 +111,17 @@ jobs:
|
||||
run: |
|
||||
moon update
|
||||
|
||||
- name: Check async package on wasm-gc and js
|
||||
- name: Check async package on wasm, wasm-gc and js
|
||||
shell: bash
|
||||
run: |
|
||||
moon check src-async --target wasm --deny-warn
|
||||
moon check src-async --target wasm-gc --deny-warn
|
||||
moon check src-async --target js --deny-warn
|
||||
|
||||
- name: Test async package on wasm-gc and js
|
||||
- name: Test async package on wasm, wasm-gc and js
|
||||
shell: bash
|
||||
run: |
|
||||
moon test src-async --target wasm --deny-warn
|
||||
moon test src-async --target wasm-gc --deny-warn
|
||||
moon test src-async --target js --deny-warn
|
||||
|
||||
|
||||
@@ -54,9 +54,8 @@ fn main {
|
||||
|
||||
## 支持情况
|
||||
|
||||
- `BitLogger` 当前在 CI 中检查/验证的目标是 `native`、`js`、`wasm-gc`
|
||||
- `bitlogger_async` 当前在 CI 中检查 `native`、`js`、`wasm-gc`,测试覆盖 `native`、`js`、`wasm-gc`
|
||||
- `wasm` 目标在源码 `moon.pkg` 中保留声明,但当前未纳入 CI 验证口径
|
||||
- `BitLogger` 当前在 CI 中检查/验证的目标是 `native`、`wasm`、`js`、`wasm-gc`
|
||||
- `bitlogger_async` 当前在 CI 中检查和测试 `native`、`wasm`、`js`、`wasm-gc`
|
||||
- `llvm` 目前按实验性目标处理,当前环境未完成验证
|
||||
- 文件输出是 native 能力;跨端代码里建议先判断 `native_files_supported()`
|
||||
- `src-async` 可用,但示例 `examples/async_basic` 目前仍按 native 入口提供
|
||||
@@ -69,6 +68,7 @@ fn main {
|
||||
- 配置驱动构建:`build_logger(...)`、`build_async_logger(...)`
|
||||
- 组合能力:queue、filter、patch、fanout、split、callback
|
||||
- 异步日志:独立 `src-async` package
|
||||
- Trace context:标准 `trace_id`、`span_id`、`trace_flags`、`trace_state` 字段绑定
|
||||
|
||||
## 文档
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @log,
|
||||
"moonbitlang/core/bench",
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Generated using `moon info`, DON'T EDIT IT
|
||||
package "Nanaloveyuki/BitLogger/benchmarks"
|
||||
|
||||
// Values
|
||||
|
||||
// Errors
|
||||
|
||||
// Types and methods
|
||||
|
||||
// Type aliases
|
||||
|
||||
// Traits
|
||||
@@ -121,6 +121,7 @@ function buildExtendSidebar(): DefaultTheme.SidebarItem[] {
|
||||
{ text: 'Sink Composition', link: '/extend/composition' },
|
||||
{ text: 'Text Formatting', link: '/extend/formatting' },
|
||||
{ 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: '文本格式与样式', link: '/zh/extend/formatting' },
|
||||
{ text: '目标平台边界', link: '/zh/extend/targets' },
|
||||
{ text: 'Trace Context', link: '/zh/extend/observability' },
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -44,7 +44,7 @@ Continue with the [Examples guide](./examples/index.md): console fields, file ro
|
||||
|
||||
## 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
|
||||
- The source packages still declare `wasm` support alongside `native`, `llvm`, `js`, and `wasm-gc`; see [`target-verification.md`](./api/target-verification.md) for the current release-facing verification boundary
|
||||
- File output is a native capability; check `native_files_supported()` in cross-target code
|
||||
@@ -58,6 +58,7 @@ Continue with the [Examples guide](./examples/index.md): console fields, file ro
|
||||
- Config-based builders: `build_logger(...)` and `build_async_logger(...)`
|
||||
- Composition helpers: queue, filter, patch, fanout, split, callback
|
||||
- Separate async package under `src-async`
|
||||
- Trace-context binding through standard `trace_id`, `span_id`, `trace_flags`, and `trace_state` fields
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -31,6 +31,7 @@ BitLogger API navigation.
|
||||
- [logger-with-timestamp.md](./logger-with-timestamp.md)
|
||||
- [logger-with-context-fields.md](./logger-with-context-fields.md)
|
||||
- [logger-bind.md](./logger-bind.md)
|
||||
- [logger-with-trace-context.md](./logger-with-trace-context.md)
|
||||
- [logger-with-filter.md](./logger-with-filter.md)
|
||||
- [logger-with-patch.md](./logger-with-patch.md)
|
||||
- [logger-with-queue.md](./logger-with-queue.md)
|
||||
@@ -99,6 +100,10 @@ BitLogger API navigation.
|
||||
- [fields.md](./fields.md)
|
||||
- [text-formatter-config-type.md](./text-formatter-config-type.md)
|
||||
|
||||
## Observability
|
||||
|
||||
- [trace-context.md](./trace-context.md)
|
||||
|
||||
## Record and level
|
||||
|
||||
- [record.md](./record.md)
|
||||
@@ -266,6 +271,7 @@ BitLogger API navigation.
|
||||
- [async-logger-with-min-level.md](./async-logger-with-min-level.md)
|
||||
- [async-logger-with-timestamp.md](./async-logger-with-timestamp.md)
|
||||
- [async-logger-with-context-fields.md](./async-logger-with-context-fields.md)
|
||||
- [async-logger-with-trace-context.md](./async-logger-with-trace-context.md)
|
||||
- [async-logger-with-filter.md](./async-logger-with-filter.md)
|
||||
- [async-logger-with-patch.md](./async-logger-with-patch.md)
|
||||
|
||||
@@ -322,6 +328,7 @@ BitLogger API navigation.
|
||||
- [library-logger-child.md](./library-logger-child.md)
|
||||
- [library-logger-with-context-fields.md](./library-logger-with-context-fields.md)
|
||||
- [library-logger-bind.md](./library-logger-bind.md)
|
||||
- [library-logger-with-trace-context.md](./library-logger-with-trace-context.md)
|
||||
- [library-logger-is-enabled.md](./library-logger-is-enabled.md)
|
||||
- [library-logger-log.md](./library-logger-log.md)
|
||||
- [library-logger-info.md](./library-logger-info.md)
|
||||
@@ -341,6 +348,7 @@ BitLogger API navigation.
|
||||
- [library-async-logger-child.md](./library-async-logger-child.md)
|
||||
- [library-async-logger-with-context-fields.md](./library-async-logger-with-context-fields.md)
|
||||
- [library-async-logger-bind.md](./library-async-logger-bind.md)
|
||||
- [library-async-logger-with-trace-context.md](./library-async-logger-with-trace-context.md)
|
||||
- [library-async-logger-is-enabled.md](./library-async-logger-is-enabled.md)
|
||||
- [library-async-logger-log.md](./library-async-logger-log.md)
|
||||
- [library-async-logger-info.md](./library-async-logger-info.md)
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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. |
|
||||
| Control terminal appearance | [Text formatting](./formatting.md) | Formatting affects presentation, not record structure. |
|
||||
| Share code across native and web targets | [Target boundaries](./targets.md) | File and async runtime behavior differ by backend. |
|
||||
| Correlate logs with distributed requests | [Trace context](./observability.md) | Propagation and export stay application-owned. |
|
||||
|
||||
For exact function signatures, follow each page's links into the [API reference](../api/index.md).
|
||||
|
||||
@@ -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.
|
||||
@@ -10,5 +10,6 @@
|
||||
| 同时发送到多个位置或按 level 路由 | [Sink 组合](./composition.md) | 比 preset 更需要显式构造。 |
|
||||
| 调整终端可读性 | [文本格式](./formatting.md) | 格式只改变呈现,不改变记录结构。 |
|
||||
| 在 native 与 web 目标之间共享代码 | [目标平台边界](./targets.md) | 文件与异步运行时行为因后端而异。 |
|
||||
| 将日志关联到分布式请求 | [Trace context](./observability.md) | 传播与导出仍由应用负责。 |
|
||||
|
||||
需要精确函数签名时,沿页面链接进入[英文 API 参考](../../api/index.md)。
|
||||
|
||||
@@ -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 实现负责。
|
||||
@@ -455,6 +455,30 @@ async test "async logger with_context_fields prepends shared fields without muta
|
||||
inspect(written_fields.val[2].value, content="test")
|
||||
}
|
||||
|
||||
///|
|
||||
test "async facades bind trace context through standard fields" {
|
||||
let context = @bitlogger.trace_context("trace-async", "span-async")
|
||||
let base = async_logger(
|
||||
@bitlogger.callback_sink(fn(_) { }),
|
||||
config=AsyncLoggerConfig::new(max_pending=4),
|
||||
min_level=@bitlogger.Level::Info,
|
||||
)
|
||||
let derived = base.with_trace_context(context)
|
||||
inspect(base.context_fields.length(), content="0")
|
||||
inspect(derived.context_fields.length(), content="3")
|
||||
inspect(derived.context_fields[0].key, content="trace_id")
|
||||
inspect(derived.context_fields[0].value, content="trace-async")
|
||||
inspect(derived.context_fields[1].key, content="span_id")
|
||||
inspect(derived.context_fields[2].value, content="01")
|
||||
|
||||
let library = LibraryAsyncLogger::new(@bitlogger.callback_sink(fn(_) { })).with_trace_context(
|
||||
context,
|
||||
)
|
||||
let full = library.to_async_logger()
|
||||
inspect(full.context_fields.length(), content="3")
|
||||
inspect(full.context_fields[1].value, content="span-async")
|
||||
}
|
||||
|
||||
///|
|
||||
async test "async logger child composes target and preserves other state" {
|
||||
let written_target : Ref[String] = Ref("")
|
||||
|
||||
@@ -274,6 +274,14 @@ pub fn[S] AsyncLogger::with_context_fields(
|
||||
{ ..self, context_fields: fields }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::with_trace_context(
|
||||
self : AsyncLogger[S],
|
||||
context : @bitlogger.TraceContext,
|
||||
) -> AsyncLogger[S] {
|
||||
self.with_context_fields(context.as_fields())
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::with_filter(
|
||||
self : AsyncLogger[S],
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,14 @@ pub fn[S] LibraryAsyncLogger::bind(
|
||||
self.with_context_fields(fields)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::with_trace_context(
|
||||
self : LibraryAsyncLogger[S],
|
||||
context : @bitlogger.TraceContext,
|
||||
) -> LibraryAsyncLogger[S] {
|
||||
library_async_logger(self.inner.with_trace_context(context))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::is_enabled(
|
||||
self : LibraryAsyncLogger[S],
|
||||
|
||||
@@ -114,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_target(Self[S], String) -> Self[S]
|
||||
pub fn[S] AsyncLogger::with_timestamp(Self[S], enabled? : Bool) -> Self[S]
|
||||
pub fn[S] AsyncLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[S]
|
||||
|
||||
pub struct LibraryAsyncLogger[S] {
|
||||
inner : AsyncLogger[S]
|
||||
@@ -131,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 fn[S] LibraryAsyncLogger::with_context_fields(Self[S], Array[@core.Field]) -> Self[S]
|
||||
pub fn[S] LibraryAsyncLogger::with_target(Self[S], String) -> Self[S]
|
||||
pub fn[S] LibraryAsyncLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[S]
|
||||
|
||||
// Type aliases
|
||||
pub type ApplicationAsyncLogger = AsyncLogger[@runtime.RuntimeSink]
|
||||
|
||||
@@ -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_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
|
||||
|
||||
// Traits
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -80,6 +80,14 @@ pub fn[S] LibraryLogger::bind(
|
||||
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(
|
||||
self : LibraryLogger[S],
|
||||
|
||||
@@ -57,6 +57,14 @@ pub fn[S] Logger::bind(
|
||||
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(
|
||||
self : Logger[S],
|
||||
|
||||
@@ -187,6 +187,8 @@ pub fn text_style(fg? : String?, bg? : String?, bold? : Bool, dim? : Bool, itali
|
||||
|
||||
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 with_file_rotation(@config_model.LoggerConfig, Int, max_backups? : Int) -> @config_model.LoggerConfig
|
||||
@@ -212,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] 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_trace_context(Self[S], @core.TraceContext) -> Self[@sink_graph.ContextSink[S]]
|
||||
|
||||
pub struct Logger[S] {
|
||||
min_level : @core.Level
|
||||
@@ -276,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_target(Self[S], String) -> 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
|
||||
pub type ApplicationLogger = Logger[@runtime.RuntimeSink]
|
||||
@@ -358,6 +362,8 @@ pub using @config_model {type TextFormatterConfig}
|
||||
|
||||
pub using @formatting {type TextStyle}
|
||||
|
||||
pub using @core {type TraceContext}
|
||||
|
||||
pub using @sink_graph {trait Sink}
|
||||
|
||||
// Traits
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
///|
|
||||
pub type Field = @core.Field
|
||||
|
||||
///|
|
||||
pub type TraceContext = @core.TraceContext
|
||||
|
||||
///|
|
||||
pub fn field(key : String, value : String) -> Field {
|
||||
@core.field(key, value)
|
||||
@@ -11,6 +14,25 @@ pub fn fields(entries : Array[(String, String)]) -> Array[Field] {
|
||||
@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
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user