From a26ec6399ca1914e8df8441687685742ee395248 Mon Sep 17 00:00:00 2001 From: Nanaloveyuki Date: Sun, 10 May 2026 12:12:11 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20bind-style=20context=20helper?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++++ bitlogger/BitLogger_test.mbt | 9 +++++++++ bitlogger/BitLogger_wbtest.mbt | 25 +++++++++++++++++++++++++ bitlogger/README.mbt.md | 10 ++++++++++ bitlogger/logger.mbt | 4 ++++ bitlogger/record.mbt | 6 ++++++ docs/README-en.md | 10 ++++++++++ docs/changes/0.3.0.md | 3 +++ examples/basic/main.mbt | 7 +++++++ 9 files changed, 86 insertions(+) diff --git a/README.md b/README.md index d6c035f..a1ea4a2 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ BitLogger 是一个基于 MoonBit 编写的结构化日志库。 - 🧱 可组合:支持 `buffered_sink(...)` 和 `filter_sink(...)`,可以在不引入复杂 runtime 的前提下组合输出策略。 - 🔎 可复用过滤:提供 `target_has_prefix(...)`、`message_contains(...)`、`level_at_least(...)`、`field_equals(...)` 等过滤辅助函数。 - 🩹 可变换 Record:支持 `with_patch(...)`、`patch_sink(...)` 与脱敏/补字段/message 变换 helper。 +- 🧷 可绑定上下文:支持 `bind(...)` 与 `fields(...)`,更方便地封装复用字段上下文。 - 📮 显式队列:支持 `queued_sink(...)` / `with_queue(...)`、有界积压与溢出策略,作为后续 async sink 的 runtime-safe 基础。 - 🧾 可配置文本格式:支持 `text_formatter(...)`、`format_text(...)`、`text_console_sink(...)`、`formatted_callback_sink(...)` 与模板化 `template` 输出。 - 💾 Native 文件输出:支持 `file_sink(...)`,并已提供基础 size rotation / backup retention;当前仅保证 `native/llvm` backend 可用。 @@ -115,6 +116,17 @@ logger.info("login", fields=[field("user", "alice"), field("token", "secret")]) ``` +
bind 上下文示例 + +```moonbit +let logger = Logger::new(console_sink(), target="audit") + .bind(fields([("service", "bitlogger"), ("scope", "login")])) + +logger.info("accepted", fields=[field("user", "alice")]) +``` + +
+
显式 queue sink 示例 ```moonbit diff --git a/bitlogger/BitLogger_test.mbt b/bitlogger/BitLogger_test.mbt index 3a1d9e8..55c80fd 100644 --- a/bitlogger/BitLogger_test.mbt +++ b/bitlogger/BitLogger_test.mbt @@ -14,6 +14,15 @@ test "context sink merges fields" { logger.info("hello", fields=[field("mode", "test")]) } +test "fields helper builds field arrays ergonomically" { + let items = fields([("service", "bitlogger"), ("mode", "test")]) + inspect(items.length(), content="2") + inspect(items[0].key, content="service") + inspect(items[0].value, content="bitlogger") + inspect(items[1].key, content="mode") + inspect(items[1].value, content="test") +} + test "fanout sink can write to plain and json outputs" { let logger = Logger::new( fanout_sink(console_sink(), json_console_sink()), diff --git a/bitlogger/BitLogger_wbtest.mbt b/bitlogger/BitLogger_wbtest.mbt index de2827f..f69745e 100644 --- a/bitlogger/BitLogger_wbtest.mbt +++ b/bitlogger/BitLogger_wbtest.mbt @@ -259,6 +259,31 @@ test "callback sink sees child target and context logger shape" { inspect(captured_timestamp.val > 0UL, content="true") } +test "bind aliases context fields ergonomically" { + let captured_target : Ref[String] = Ref::new("") + let captured_message : Ref[String] = Ref::new("") + let captured_fields : Ref[Array[Field]] = Ref::new([]) + let logger = Logger::new( + callback_sink(fn(rec) { + captured_target.val = rec.target + captured_message.val = rec.message + captured_fields.val = rec.fields + }), + min_level=Level::Info, + target="bind", + ).bind(fields([("service", "bitlogger"), ("scope", "audit")])) + logger.info("ready", fields=[field("mode", "test")]) + inspect(captured_target.val, content="bind") + inspect(captured_message.val, content="ready") + inspect(captured_fields.val.length(), content="3") + inspect(captured_fields.val[0].key, content="service") + inspect(captured_fields.val[0].value, content="bitlogger") + inspect(captured_fields.val[1].key, content="scope") + inspect(captured_fields.val[1].value, content="audit") + inspect(captured_fields.val[2].key, content="mode") + inspect(captured_fields.val[2].value, content="test") +} + test "buffered sink flushes manually" { let flushed_messages : Ref[Array[String]] = Ref::new([]) let sink = buffered_sink( diff --git a/bitlogger/README.mbt.md b/bitlogger/README.mbt.md index 9d7469e..7316b65 100644 --- a/bitlogger/README.mbt.md +++ b/bitlogger/README.mbt.md @@ -36,6 +36,8 @@ BitLogger 是一个基于 MoonBit 的结构化日志库。 - 提供 `target_has_prefix(...)`、`message_contains(...)`、`field_equals(...)` 等可复用过滤辅助函数 - record patching via `with_patch(...)` and `patch_sink(...)` - 支持 `with_patch(...)`、`patch_sink(...)` 以及常见 record patch helper +- context binding via `bind(...)` and `fields(...)` +- 支持 `bind(...)`、`fields(...)`,更顺手地封装上下文字段 - explicit queued delivery via `queued_sink(...)` and `with_queue(...)` - 支持 `queued_sink(...)`、`with_queue(...)`、有界积压与溢出策略 - configurable text formatting via `text_formatter(...)`, `format_text(...)`, `text_console_sink(...)`, and template-driven `template` output @@ -133,6 +135,14 @@ test { } ``` +```mbt check +test { + let logger = Logger::new(console_sink(), target="audit") + .bind(fields([("service", "bitlogger"), ("scope", "login")])) + logger.info("accepted", fields=[field("user", "alice")]) +} +``` + ```mbt check test { let logger = Logger::new(console_sink(), target="queue") diff --git a/bitlogger/logger.mbt b/bitlogger/logger.mbt index 2305b67..617bdeb 100644 --- a/bitlogger/logger.mbt +++ b/bitlogger/logger.mbt @@ -36,6 +36,10 @@ pub fn[S] Logger::with_context_fields(self : Logger[S], fields : Array[Field]) - } } +pub fn[S] Logger::bind(self : Logger[S], fields : Array[Field]) -> Logger[ContextSink[S]] { + self.with_context_fields(fields) +} + pub fn[S] Logger::with_filter(self : Logger[S], predicate : (Record) -> Bool) -> Logger[FilterSink[S]] { { min_level: self.min_level, diff --git a/bitlogger/record.mbt b/bitlogger/record.mbt index 027b37e..7502e9a 100644 --- a/bitlogger/record.mbt +++ b/bitlogger/record.mbt @@ -7,6 +7,12 @@ pub fn field(key : String, value : String) -> Field { { key, value } } +pub fn fields(entries : Array[(String, String)]) -> Array[Field] { + entries.map(fn(entry) { + field(entry.0, entry.1) + }) +} + pub struct Record { level : Level timestamp_ms : UInt64 diff --git a/docs/README-en.md b/docs/README-en.md index 64fe8a7..f0b1668 100644 --- a/docs/README-en.md +++ b/docs/README-en.md @@ -21,6 +21,7 @@ BitLogger currently provides: - reusable filter helpers such as `target_has_prefix(...)`, `message_contains(...)`, `level_at_least(...)`, and `field_equals(...)` - record patching via `with_patch(...)` and `patch_sink(...)` - patch helpers such as `prefix_message(...)`, `append_fields(...)`, and `redact_fields(...)` +- context binding via `bind(...)` and `fields(...)` - explicit queued delivery via `queued_sink(...)` and `with_queue(...)` - bounded backlog with `QueueOverflowPolicy::DropNewest` and `QueueOverflowPolicy::DropOldest` - configurable text formatting via `text_formatter(...)`, `format_text(...)`, `text_console_sink(...)`, and template-driven `template` output @@ -110,6 +111,15 @@ let logger = Logger::new(console_sink(), target="auth") logger.info("login", fields=[field("user", "alice"), field("token", "secret")]) ``` +Context binding: + +```moonbit +let logger = Logger::new(console_sink(), target="audit") + .bind(fields([("service", "bitlogger"), ("scope", "login")])) + +logger.info("accepted", fields=[field("user", "alice")]) +``` + Explicit queued sink: ```moonbit diff --git a/docs/changes/0.3.0.md b/docs/changes/0.3.0.md index 9dec585..6c4ef38 100644 --- a/docs/changes/0.3.0.md +++ b/docs/changes/0.3.0.md @@ -30,6 +30,7 @@ version 0.3.0 - feat: add `FileRotation`, `file_rotation(...)`, and size-based file rotation with retained backups for `file_sink(...)` - feat: support `sink.rotation.max_bytes` and `sink.rotation.max_backups` in JSON logger config - feat: add `SplitSink`, `split_sink(...)`, and `split_by_level(...)` for routing records into different sinks by predicate or level +- feat: add `Logger::bind(...)` as an ergonomic context-binding alias and `fields(...)` helper for tuple-based field construction ### Test @@ -42,6 +43,7 @@ version 0.3.0 - test: cover template-based formatter rendering and disabled token behavior - test: cover file rotation config parsing, config roundtrip, and native rotation behavior - test: cover split sink predicate routing and level-based routing behavior +- test: cover `bind(...)` context composition and `fields(...)` helper behavior - test: add async logger lifecycle, config roundtrip, and batching/flush policy test seeds - build: verify `bitlogger_async --target native` and `examples/async_basic --target native` compile successfully @@ -55,6 +57,7 @@ version 0.3.0 - docs: update formatter examples to demonstrate template-based text rendering - docs: update file sink examples to demonstrate rotation and backup retention - docs: add split sink examples for level-based routing +- docs: add `bind(...)` examples for reusable context binding - docs: update root README and English README with async adapter notes and current scope - chore: ignore local `.mooncakes/` cache directory in git diff --git a/examples/basic/main.mbt b/examples/basic/main.mbt index de9ab64..86d479d 100644 --- a/examples/basic/main.mbt +++ b/examples/basic/main.mbt @@ -7,6 +7,13 @@ fn main { .with_context_fields([@lib.field("service", "bitlogger")]) logger.debug("custom logger ready", fields=[@lib.field("sink", "console")]) + let bound_logger = @lib.Logger::new( + @lib.console_sink(), + min_level=@lib.Level::Info, + target="bound", + ).bind(@lib.fields([("service", "bitlogger"), ("scope", "audit")])) + bound_logger.info("bound logger ready", fields=[@lib.field("mode", "demo")]) + let json_logger = @lib.Logger::new(@lib.json_console_sink(), min_level=@lib.Level::Info, target="json") json_logger.info("json output", fields=[@lib.field("kind", "example")])