diff --git a/README.md b/README.md index 6df1cdd..e76cb4e 100644 --- a/README.md +++ b/README.md @@ -6,28 +6,51 @@ BitLogger 是一个使用 MoonBit 编写的结构化日志库,适合命令行 - [English README](./docs/README-en.md) - [Wiki](https://bitlogger.naloveyuki.top) -## 介绍 +## 从第一个日志到扩展能力 -BitLogger 提供统一的日志级别、目标名、结构化字段和可定制格式,既可以直接输出到控制台,也可以在 native 环境写入文件,并提供异步日志版本。 +BitLogger 的文档按使用路径组织:先完成一个可运行的日志输出,再按需进入文件、配置、异步和组合能力。API 文档保留为精确参考,不需要从 API 文件名开始阅读。 -## 快速开始 +### 1. 安装 -```moonbit -let logger = build_logger( - text_console( - min_level=Level::Info, - target="demo", - text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "), - ), -) +从一个空项目开始: -logger.info("starting", fields=[field("port", "8080")]) -ignore(logger.flush()) +```bash +moon new log-demo +cd log-demo +moon add Nanaloveyuki/BitLogger@0.7.0 ``` -推荐从 `console(...)`、`json_console(...)`、`text_console(...)`、`file(...)` 这几个入口开始,再按需配合 `with_queue(...)` 或 `with_file_rotation(...)`。 +### 2. 写入第一条结构化日志 -如果你需要自己组合 sink,比如 `fanout`、`split`、`callback`,再使用 `Logger::new(...)`。 +在应用 package 的 `moon.pkg` 中导入库: + +```moonbit +import { + "Nanaloveyuki/BitLogger/src" @log, +} +``` + +然后在 `main.mbt` 中创建控制台 logger: + +```moonbit +fn main { + let logger = @log.build_logger( + @log.console(min_level=@log.Level::Info, target="app"), + ) + + logger.info("server started", fields=[ + @log.field("port", "8080"), + @log.field("environment", "development"), + ]) +} +``` + +运行 `moon run` 后,记录会携带 level、target、message 和 fields。下一步按实际需求选择: + +- [控制台与结构化字段](./docs/examples/console.md) +- [文件输出与轮转](./docs/examples/file-rotation.md) +- [JSON 配置构建](./docs/examples/config.md) +- [异步日志生命周期](./docs/examples/async.md) ## 支持情况 @@ -47,19 +70,11 @@ ignore(logger.flush()) - 组合能力:queue、filter、patch、fanout、split、callback - 异步日志:独立 `src-async` package -## 示例 - -- `examples/console_basic/`:最小 console / json console 示例 -- `examples/text_formatter/`:文本格式与模板示例 -- `examples/style_tags/`:style tag 与彩色输出示例 -- `examples/config_build/`:配置构建示例 -- `examples/presets/`:常用预设组合示例 -- `examples/file_rotation/`:文件输出与轮转示例,仅适用于 native -- `examples/async_basic/`:异步日志示例 - ## 文档 -- [API 索引](./docs/api/index.md) +- [Examples:完整使用流程](./docs/examples/index.md) +- [Extend:队列、组合、格式化和跨端边界](./docs/extend/index.md) +- [API 索引:按公开符号查询](./docs/api/index.md) - [src package README](./src/README.mbt.md) -常用入口:`text_console(...)`、`file(...)`、`with_queue(...)`、`build_logger(...)`、`build_async_logger(...)` +仓库内的 `examples/` 目录与 Examples 文档一一对应;文档解释选择、前提、运行命令和后续扩展,源码保持为可执行参考。 diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 5f1410e..a4731d4 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -104,6 +104,26 @@ function buildChangesSidebar(): DefaultTheme.SidebarItem[] { ] } +function buildExamplesSidebar(): DefaultTheme.SidebarItem[] { + return [ + { text: 'Overview', link: '/examples/' }, + { text: 'Console And Fields', link: '/examples/console' }, + { text: 'File Rotation', link: '/examples/file-rotation' }, + { text: 'Configuration', link: '/examples/config' }, + { text: 'Async Lifecycle', link: '/examples/async' }, + ] +} + +function buildExtendSidebar(): DefaultTheme.SidebarItem[] { + return [ + { text: 'Overview', link: '/extend/' }, + { text: 'Queueing', link: '/extend/queue' }, + { text: 'Sink Composition', link: '/extend/composition' }, + { text: 'Text Formatting', link: '/extend/formatting' }, + { text: 'Target Boundaries', link: '/extend/targets' }, + ] +} + export default defineConfig({ title: 'BitLogger', description: 'Structured logging library docs for MoonBit.', @@ -118,6 +138,8 @@ export default defineConfig({ siteTitle: 'BitLogger', nav: [ { text: 'Home', link: '/' }, + { text: 'Examples', link: '/examples/' }, + { text: 'Extend', link: '/extend/' }, { text: 'API', link: '/api/' }, { text: 'Changes', link: '/changes/' }, { text: 'Mooncake', link: 'https://mooncakes.io/docs/Nanaloveyuki/BitLogger' }, @@ -131,6 +153,8 @@ export default defineConfig({ text: 'Edit this page on GitHub', }, sidebar: { + '/examples/': buildExamplesSidebar(), + '/extend/': buildExtendSidebar(), '/api/': buildApiSidebar(), '/changes/': buildChangesSidebar(), }, diff --git a/docs/README-en.md b/docs/README-en.md index a42a843..a209fb0 100644 --- a/docs/README-en.md +++ b/docs/README-en.md @@ -9,24 +9,38 @@ BitLogger is a structured logging library for MoonBit projects. BitLogger gives you consistent levels, targets, structured fields, configurable text output, native file logging, and an async logging layer. -## Quick Start +## Start With A Working Flow -```moonbit -let logger = build_logger( - text_console( - min_level=Level::Info, - target="demo", - text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "), - ), -) +The documentation is organized around complete usage flows. Start with an executable logger, then move into file output, configuration, async lifecycle, and extensions. The API reference remains the precise symbol-level reference. -logger.info("starting", fields=[field("port", "8080")]) -ignore(logger.flush()) +### 1. Install + +```bash +moon new log-demo +cd log-demo +moon add Nanaloveyuki/BitLogger@0.7.0 ``` -Start with `console(...)`, `json_console(...)`, `text_console(...)`, or `file(...)`, then add `with_queue(...)` or `with_file_rotation(...)` only when needed. +### 2. Import And Log -Use `Logger::new(...)` when you want to assemble custom sink graphs directly. +Add the package import to your application's `moon.pkg`: + +```moonbit +import { + "Nanaloveyuki/BitLogger/src" @log, +} +``` + +```moonbit +fn main { + let logger = @log.build_logger( + @log.console(min_level=@log.Level::Info, target="app"), + ) + logger.info("server started", fields=[@log.field("port", "8080")]) +} +``` + +Continue with the [Examples guide](./examples/index.md): console fields, file rotation, JSON configuration, and async lifecycle are each documented as a complete path. ## Support Status @@ -45,18 +59,10 @@ Use `Logger::new(...)` when you want to assemble custom sink graphs directly. - Composition helpers: queue, filter, patch, fanout, split, callback - Separate async package under `src-async` -## Examples - -- `examples/console_basic/`: minimal console and JSON console example -- `examples/text_formatter/`: text formatting and template example -- `examples/style_tags/`: style tags and colored output example -- `examples/config_build/`: config-based build example -- `examples/presets/`: common preset combinations -- `examples/file_rotation/`: native file logging and rotation example -- `examples/async_basic/`: async logging example - ## Documentation +- [Examples](./examples/index.md): complete application-facing usage flows +- [Extend](./extend/index.md): queueing, sink composition, formatting, and target boundaries - [API index](./api/index.md): canonical API reference, organized as one public API per file - [src package README](https://github.com/Nanaloveyuki/BitLogger/blob/main/src/README.mbt.md): package-level usage notes and target reminders - [`docs/changes/`](./changes/): versioned release notes and publish-facing change summaries diff --git a/docs/examples/async.md b/docs/examples/async.md new file mode 100644 index 0000000..e9aea1e --- /dev/null +++ b/docs/examples/async.md @@ -0,0 +1,47 @@ +# Async Logger Lifecycle + +Use the async package when a native application needs logging to run through an async queue. The library supports broader targets, but the shipped `async fn main` example is native-focused. + +## Import Both Packages + +```moonbit +import { + "Nanaloveyuki/BitLogger/src" @log, + "Nanaloveyuki/BitLogger/src-async" @async_log, + "moonbitlang/async", +} +``` + +## Build, Run, Then Shut Down + +```moonbit +async fn main { + let raw = "{\"logger\":{\"min_level\":\"info\",\"target\":\"service.async\",\"sink\":{\"kind\":\"text_console\"}},\"async_config\":{\"max_pending\":64,\"overflow\":\"DropOldest\",\"max_batch\":16,\"linger_ms\":5,\"flush\":\"Batch\"}}" + let config = @async_log.parse_async_logger_build_config_text(raw) catch { + err => { + ignore(err) + return + } + } + let logger = @async_log.build_async_logger(config) + + @async.with_task_group(group => { + group.spawn_bg(allow_failure=true, () => logger.run()) + logger.info("worker started", fields=[@log.field("mode", "async")]) + logger.shutdown() + }) +} +``` + +`run()` owns the worker loop. Start it before producing records, then call `shutdown()` so pending work can follow the configured flush policy. + +## Run The Repository Example + +```bash +moon run examples/async_basic --target native +``` + +## Next Steps + +- Inspect queue policy and flush choices in [Queueing](../extend/queue.md). +- Reference: [`parse_async_logger_build_config_text(...)`](../api/parse-async-logger-build-config-text.md), [`build_async_logger(...)`](../api/build-async-logger.md), [`run()`](../api/async-logger-run.md), and [`shutdown()`](../api/async-logger-shutdown.md). diff --git a/docs/examples/config.md b/docs/examples/config.md new file mode 100644 index 0000000..38ed53d --- /dev/null +++ b/docs/examples/config.md @@ -0,0 +1,35 @@ +# Configuration-Driven Logging + +Use configuration when deployment should select levels, output, formatting, or queue behavior without changing application code. + +## Parse, Build, Then Log + +```moonbit +let raw = "{\"min_level\":\"info\",\"target\":\"service\",\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \"}},\"queue\":{\"max_pending\":64,\"overflow\":\"DropOldest\"}}" + +let config = @log.parse_logger_config_text(raw) catch { + err => { + ignore(err) + println("invalid logger configuration") + return + } +} + +let logger = @log.build_logger(config) +logger.info("configured logger ready") +ignore(logger.flush()) +``` + +Parsing validates the data before runtime construction. Keep the raw configuration outside application code in a real deployment, but handle parsing errors at the boundary where configuration enters the process. + +## Run The Repository Example + +```bash +moon run examples/config_build +``` + +## Next Steps + +- Use [Queueing](../extend/queue.md) to choose overflow behavior deliberately. +- Use [Async lifecycle](./async.md) when the queue needs a background worker. +- Reference: [`parse_logger_config_text(...)`](../api/parse-logger-config-text.md), [`build_logger(...)`](../api/build-logger.md), and [`with_queue(...)`](../api/with-queue.md). diff --git a/docs/examples/console.md b/docs/examples/console.md new file mode 100644 index 0000000..86a02b6 --- /dev/null +++ b/docs/examples/console.md @@ -0,0 +1,51 @@ +# Console And Structured Fields + +Use this flow for command-line tools and services that need a human-readable starting point. A record always has a level, target, message, and optional fields. + +## Install And Import + +```bash +moon add Nanaloveyuki/BitLogger@0.7.0 +``` + +```moonbit +import { + "Nanaloveyuki/BitLogger/src" @log, +} +``` + +## Build A Console Logger + +```moonbit +fn main { + let logger = @log.build_logger( + @log.console(min_level=@log.Level::Info, target="service.http"), + ) + + logger.info("listening", fields=[ + @log.field("port", "8080"), + @log.field("environment", "development"), + ]) + logger.warn("slow request", fields=[@log.field("path", "/health")]) +} +``` + +`min_level` filters records before they reach the sink. `target` identifies the subsystem, while fields carry queryable context without interpolating it into the message. + +## Switch To JSON + +Keep the logging calls unchanged and replace the preset: + +```moonbit +let logger = @log.build_logger( + @log.json_console(min_level=@log.Level::Info, target="service.http"), +) +``` + +This is the preferred output for a collector that consumes one structured record per line. + +## Next Steps + +- Use [Text formatting](../extend/formatting.md) for a custom terminal format. +- Use [Configuration](./config.md) when levels, targets, and sinks come from JSON. +- Reference: [`console(...)`](../api/console.md), [`json_console(...)`](../api/json-console.md), [`field(...)`](../api/field.md), and [`build_logger(...)`](../api/build-logger.md). diff --git a/docs/examples/file-rotation.md b/docs/examples/file-rotation.md new file mode 100644 index 0000000..a201b29 --- /dev/null +++ b/docs/examples/file-rotation.md @@ -0,0 +1,51 @@ +# File Output And Rotation + +File output is for native applications that need local persistence. Treat it as a native-only capability and keep a console fallback for portable code. + +## Check The Capability + +```moonbit +if !@log.native_files_supported() { + println("file logging is unavailable on this target") + return +} +``` + +## Build A Rotating File Logger + +```moonbit +let config = @log.with_file_rotation( + @log.file( + "service.log", + min_level=@log.Level::Info, + target="service.file", + auto_flush=true, + ) catch { + err => { + ignore(err) + return + } + }, + 1024 * 1024, + max_backups=3, +) + +let logger = @log.build_logger(config) +logger.info("file logger ready") +ignore(logger.flush()) +ignore(logger.file_close()) +``` + +The rotation limit is the size of the active file in bytes. `max_backups=3` retains the rotated backup chain. Always close a file-backed logger during orderly application shutdown. + +## Run The Repository Example + +```bash +moon run examples/file_rotation --target native +``` + +## Next Steps + +- Add a bounded queue with [Queueing](../extend/queue.md) when bursts should not write synchronously. +- Read [Target boundaries](../extend/targets.md) before sharing the same logging setup with web targets. +- Reference: [`file(...)`](../api/file.md), [`with_file_rotation(...)`](../api/with-file-rotation.md), and [`file_close(...)`](../api/configured-logger-file-close.md). diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..9312908 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,24 @@ +# Examples + +Examples are the primary BitLogger learning path. Each page starts from an application concern, shows the complete minimum code, and then points to the API reference for exact contracts. + +## Choose A Flow + +| Need | Start here | Then extend with | +| --- | --- | --- | +| Print structured logs to a terminal | [Console and fields](./console.md) | [Text formatting](../extend/formatting.md) | +| Keep local logs with bounded disk use | [File rotation](./file-rotation.md) | [Target boundaries](../extend/targets.md) | +| Build logging from app configuration | [Configuration](./config.md) | [Queueing](../extend/queue.md) | +| Avoid blocking a native application on writes | [Async lifecycle](./async.md) | [Sink composition](../extend/composition.md) | + +## Repository Examples + +The runnable sources live under `examples/`. The guides below explain their intended use instead of duplicating every API detail: + +- `examples/console_basic` for terminal and JSON records +- `examples/config_build` for parsed configuration +- `examples/file_rotation` for native file output +- `examples/async_basic` for the async lifecycle +- `examples/presets`, `examples/text_formatter`, and `examples/style_tags` for follow-on customization + +Use the [API reference](../api/index.md) when a guide links to a symbol and you need its full signature or edge-case behavior. diff --git a/docs/extend/composition.md b/docs/extend/composition.md new file mode 100644 index 0000000..7264b32 --- /dev/null +++ b/docs/extend/composition.md @@ -0,0 +1,42 @@ +# Sink Composition + +Presets cover common console and file configurations. Build a `Logger::new(...)` directly when records need routing or multiple outputs. + +## Send A Record To Two Destinations + +```moonbit +let logger = @log.Logger::new( + @log.fanout_sink(@log.console_sink(), @log.json_console_sink()), + min_level=@log.Level::Info, + target="service", +) + +logger.info("request complete", fields=[@log.field("status", "200")]) +``` + +`fanout_sink(...)` writes the same record to every child sink. This keeps human-oriented terminal output and machine-oriented JSON output in one application path. + +## Route Warnings Separately + +```moonbit +let logger = @log.Logger::new( + @log.split_by_level( + @log.callback_sink(fn(rec) { + println("alert: \{rec.message}") + }), + @log.console_sink(), + min_level=@log.Level::Warn, + ), + min_level=@log.Level::Trace, + target="service", +) +``` + +Use direct sink composition only when the routing behavior is part of the application design. A preset remains easier to audit when one sink is sufficient. + +## API Reference + +- [`Logger::new(...)`](../api/logger-new.md) +- [`fanout_sink(...)`](../api/fanout-sink.md) +- [`split_by_level(...)`](../api/split-by-level.md) +- [`callback_sink(...)`](../api/callback-sink.md) diff --git a/docs/extend/formatting.md b/docs/extend/formatting.md new file mode 100644 index 0000000..82cdf57 --- /dev/null +++ b/docs/extend/formatting.md @@ -0,0 +1,44 @@ +# Text Formatting And Style Tags + +Formatting changes presentation while keeping the record's level, target, message, and fields intact. Use text output for people; use JSON output when another system parses the record. + +## Customize The Text Shape + +```moonbit +let logger = @log.build_logger( + @log.text_console( + min_level=@log.Level::Info, + target="service", + text_formatter=@log.TextFormatterConfig::new( + show_timestamp=false, + field_separator=",", + template="[{level}] {target} {message} :: {fields}", + ), + ), +) + +logger.info("ready", fields=[@log.field("port", "8080")]) +``` + +The template controls the visible order. Keep fields separate from the message so they can still be rendered consistently across sinks. + +## Add Named Styles + +```moonbit +let formatter = @log.text_formatter( + show_timestamp=false, + color_mode=@log.ColorMode::Always, +).with_style_tags( + @log.default_style_tag_registry() + .set_tag("accent", fg=Some("#4cc9f0"), bold=true), +) +``` + +Style tags are terminal presentation metadata. Do not use them as the only way to encode operational meaning; preserve that meaning in level, target, and fields. + +## API Reference + +- [`text_console(...)`](../api/text-console.md) +- [`TextFormatterConfig`](../api/text-formatter-config.md) +- [`text_formatter(...)`](../api/text-formatter.md) +- [`ColorMode`](../api/color-mode.md) diff --git a/docs/extend/index.md b/docs/extend/index.md new file mode 100644 index 0000000..f65157c --- /dev/null +++ b/docs/extend/index.md @@ -0,0 +1,14 @@ +# Extend A Logger + +Start from an [Example flow](../examples/index.md), then add one extension at a time. These pages explain when an abstraction is useful and which operational boundary it introduces. + +## Extension Map + +| Need | Extension | Main trade-off | +| --- | --- | --- | +| Absorb short output bursts | [Queueing](./queue.md) | You must choose overflow and flush behavior. | +| Send records to multiple destinations or route by level | [Sink composition](./composition.md) | More explicit construction than presets. | +| Control terminal appearance | [Text formatting](./formatting.md) | Formatting affects presentation, not record structure. | +| Share code across native and web targets | [Target boundaries](./targets.md) | File and async runtime behavior differ by backend. | + +For exact function signatures, follow each page's links into the [API reference](../api/index.md). diff --git a/docs/extend/queue.md b/docs/extend/queue.md new file mode 100644 index 0000000..8e15c40 --- /dev/null +++ b/docs/extend/queue.md @@ -0,0 +1,30 @@ +# Queueing And Overflow + +Use a queue when a short burst of log writes should not immediately reach the output sink. Queueing is explicit because loss behavior is an application decision. + +## Add A Synchronous Queue + +```moonbit +let config = @log.with_queue( + @log.text_console(target="service"), + max_pending=128, + overflow=@log.QueueOverflowPolicy::DropOldest, +) +let logger = @log.build_logger(config) + +logger.info("queued record") +ignore(logger.flush()) +``` + +`DropOldest` preserves the newest operational information when the queue is full. Use `DropNewest` when preserving earlier records matters more. Calling `flush()` at a shutdown boundary makes the pending-record policy visible in application code. + +## When To Use Async Instead + +The synchronous queue is configuration around a normal runtime logger. When a native async application needs a worker lifecycle and batching, follow [Async logger lifecycle](../examples/async.md) instead. + +## API Reference + +- [`with_queue(...)`](../api/with-queue.md) +- [`QueueConfig`](../api/queue-config.md) +- [`QueueOverflowPolicy`](../api/queue-overflow-policy.md) +- [`flush()`](../api/configured-logger-flush.md) diff --git a/docs/extend/targets.md b/docs/extend/targets.md new file mode 100644 index 0000000..1c72372 --- /dev/null +++ b/docs/extend/targets.md @@ -0,0 +1,33 @@ +# Target Boundaries + +BitLogger keeps a portable structured logging surface, but native file output and async worker behavior are target-sensitive. Keep those boundaries in application code instead of assuming every sink behaves identically everywhere. + +## Portable Default + +`console(...)`, `json_console(...)`, structured fields, filters, patches, and the ordinary logger surface are the appropriate starting point for code shared across targets. + +## Native-Only File Output + +```moonbit +if @log.native_files_supported() { + let logger = @log.build_logger(@log.file("service.log") catch { + err => { + ignore(err) + return + } + }) + logger.info("file output enabled") +} +``` + +File sinks need native filesystem support. Do not construct a file-only configuration unconditionally in code intended for web targets. + +## Async Library Versus Async Entry Point + +The async library exposes a compatibility surface across declared targets, while an executable `async fn main` still has stricter entry-point support. Keep a native-only executable example separate from portable library code. + +## API Reference + +- [`native_files_supported()`](../api/native-files-supported.md) +- [Target verification](../api/target-verification.md) +- [Async logger lifecycle](../examples/async.md) diff --git a/docs/index.md b/docs/index.md index 895040a..59d518f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,31 +4,29 @@ layout: home hero: name: BitLogger text: Structured logging for MoonBit - tagline: API reference, examples, and release notes for BitLogger. + tagline: Start with a runnable logging flow, then extend it with precise API reference. actions: - theme: brand - text: Open API Reference - link: /api/ + text: Start Examples + link: /examples/ - theme: alt - text: Read Release Notes - link: /changes/ + text: Extend A Logger + link: /extend/ - theme: alt text: Mooncake Package link: https://mooncakes.io/docs/Nanaloveyuki/BitLogger features: - - title: Structured logging - details: Levels, targets, message fields, and configurable text or JSON output. - - title: Sync and async paths - details: API reference covers both the main package and the async package surface. - - title: Config and runtime helpers - details: Browse builders, presets, file helpers, queue helpers, and verification notes quickly. + - title: Examples first + details: Follow complete console, file, config, and async flows before looking up individual symbols. + - title: Extend deliberately + details: Add queues, custom sink graphs, formatting, and target-specific behavior only when the base flow needs them. + - title: API as reference + details: Use the one-symbol-per-page API reference for exact signatures, contracts, and edge cases. --- ## Start Here - - -- Use [API Reference](./api/index.md) when you want the canonical one-file-per-public-API docs. -- Use [Release Notes](./changes/index.md) when you want version-scoped changes and publish-facing summaries. -- Use [Chinese README](https://github.com/Nanaloveyuki/BitLogger/blob/main/README.md) or [English README](./README-en.md) when you want a shorter project overview first. +1. Read [Examples](./examples/index.md) for an end-to-end path from installation to a running logger. +2. Read [Extend](./extend/index.md) when the base console flow needs queueing, custom routing, formatting, files, or target-aware behavior. +3. Use [API Reference](./api/index.md) for exact contracts and [Release Notes](./changes/index.md) for version history.