mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
15 KiB
15 KiB
BitLogger
BitLogger is a structured logging library written in MoonBit.
Overview
BitLogger currently provides:
- log levels:
Trace,Debug,Info,Warn,Error - structured key-value fields
- plain console output
- JSON console output
- child target composition via
child(...) - context fields via
with_context_fields(...) - optional timestamps via
with_timestamp() - sink fanout via
fanout_sink(...) - sink routing via
split_sink(...)andsplit_by_level(...) - custom integration via
callback_sink(...) - in-memory buffering via
buffered_sink(...) - record filtering via
filter_sink(...) - reusable filter helpers such as
target_has_prefix(...),message_contains(...),level_at_least(...), andfield_equals(...) - record patching via
with_patch(...)andpatch_sink(...) - patch helpers such as
prefix_message(...),append_fields(...), andredact_fields(...) - context binding via
bind(...)andfields(...) - explicit queued delivery via
queued_sink(...)andwith_queue(...) - bounded backlog with
QueueOverflowPolicy::DropNewestandQueueOverflowPolicy::DropOldest - configurable text formatting via
text_formatter(...),format_text(...),text_console_sink(...), and template-driventemplateoutput - lightweight style tags via
color_mode, inline markup,TextStyle,StyleTagRegistry, custom tags, and builtin-tag overrides - formatter-based callback integration via
formatted_callback_sink(...) - native-only file output via
file_sink(...), with basic size rotation, backup retention, explicitreopen()/reopen_with_current_policy()/reopen_append()/reopen_truncate(), and failure counters native_files_supported()for backend capability detection- default global logger helpers
Quick Start
let logger = Logger::new(console_sink(), min_level=Level::Info, target="demo")
.with_timestamp()
.with_context_fields([field("service", "bitlogger")])
logger.info("starting", fields=[field("port", "8080")])
Child target composition:
let worker = Logger::new(console_sink(), target="app").child("worker")
worker.info("job ready")
Custom callback sink:
let hook = Logger::new(
callback_sink(fn(rec) {
println("callback saw [\{rec.target}] \{rec.message}")
}),
target="hook",
)
hook.info("hello")
Basic buffered sink:
let sink = buffered_sink(console_sink(), flush_limit=2)
let logger = Logger::new(sink, target="buffered")
logger.info("one")
logger.info("two")
sink.flush()
Basic filter sink:
let sink = filter_sink(console_sink(), fn(rec) {
rec.target == "kept"
})
let kept = Logger::new(sink, target="kept")
let dropped = Logger::new(sink, target="dropped")
kept.info("visible")
dropped.info("hidden")
Chained logger filter:
let logger = Logger::new(console_sink(), target="service")
.with_filter(all_of([
target_has_prefix("service"),
message_contains("visible"),
]))
logger.info("hidden")
logger.child("api").info("visible")
Record patching:
let logger = Logger::new(console_sink(), target="auth")
.with_patch(compose_patches([
prefix_message("[safe] "),
redact_fields(["token"]),
append_fields([field("service", "bitlogger")]),
]))
logger.info("login", fields=[field("user", "alice"), field("token", "secret")])
Context binding:
let logger = Logger::new(console_sink(), target="audit")
.bind(fields([("service", "bitlogger"), ("scope", "login")]))
logger.info("accepted", fields=[field("user", "alice")])
Explicit queued sink:
let logger = Logger::new(console_sink(), target="queue")
.with_queue(max_pending=2, overflow=QueueOverflowPolicy::DropOldest)
logger.info("one")
logger.info("two")
logger.info("three")
ignore(logger.sink.flush())
Level-based split sink:
let logger = Logger::new(
split_by_level(
callback_sink(fn(rec) {
println("high priority: \{rec.level.label()} \{rec.message}")
}),
console_sink(),
min_level=Level::Warn,
),
min_level=Level::Trace,
target="split",
)
logger.info("normal output")
logger.warn("warning output")
Custom text formatter:
let formatter = text_formatter(
show_timestamp=false,
field_separator=",",
template="[{level}] {target} {message} :: {fields}",
color_mode=ColorMode::Always,
)
let logger = Logger::new(text_console_sink(formatter), target="pretty")
logger.info("hello", fields=[field("mode", "pretty")])
Inline style tags:
let formatter = text_formatter(
show_timestamp=false,
color_mode=ColorMode::Always,
).with_style_tags(
default_style_tag_registry()
.set_tag("accent", fg=Some("#4cc9f0"), bold=true)
.define_alias("danger", "red"),
)
let logger = Logger::new(text_console_sink(formatter), target="styled")
logger.info("<accent>styled</> output and <danger>alert</>")
Disable style markup parsing:
let formatter = text_formatter(
color_mode=ColorMode::Always,
).without_style_markup()
let logger = Logger::new(text_console_sink(formatter), target="raw")
logger.info("<red>kept as raw text</>")
JSON config loading:
let config = parse_logger_config_text(
"{\"min_level\":\"debug\",\"target\":\"config.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"field_separator\":\",\",\"template\":\"[{level}] {target} {message} :: {fields}\",\"color_mode\":\"always\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}",
)
let logger = build_logger(config)
logger.info("configured from json")
ignore(logger.flush())
JSON style_tags:
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true}}}}}",
)
let logger = build_logger(config)
logger.info("<accent>styled from json</>")
JSON style_markup mode:
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"disabled\"}}}",
)
let logger = build_logger(config)
logger.info("<red>still raw</>")
Native file sink:
if native_files_supported() {
let logger = Logger::new(
file_sink("bitlogger.log", rotation=Some(file_rotation(128, max_backups=2))),
target="file",
)
logger.info("hello", fields=[field("kind", "file")])
ignore(logger.sink.flush())
ignore(logger.sink.close())
}
File runtime state dump:
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="bitlogger-runtime.log"),
queue=Some(QueueConfig::new(16)),
),
)
logger.info("queued hello")
match logger.file_runtime_state() {
Some(snapshot) => println(stringify_runtime_file_state(snapshot, pretty=true))
None => ()
}
Repository Layout
bitlogger/: MoonBit library package, tests, and Mooncake package READMEexamples/basic/: runnable example packageexamples/async_basic/: runnable async logger example built onmoonbitlang/async
Links
Config Notes
- BitLogger now includes a JSON config layer via
parse_logger_config_text(...),stringify_logger_config(...), andbuild_logger(...). QueueConfig,TextFormatterConfig, andSinkConfigcan also be exported independently throughqueue_config_to_json(...)/stringify_queue_config(...),text_formatter_config_to_json(...)/stringify_text_formatter_config(...), andsink_config_to_json(...)/stringify_sink_config(...).- Supported keys include
min_level,target,timestamp,sink.kind,sink.path,sink.append,sink.auto_flush,sink.rotation,sink.text_formatter, andqueue. TextFormatterandTextFormatterConfignow includecolor_mode = Never | Auto | Alwaysfor ANSI text coloring control.TextFormatterandTextFormatterConfigalso includestyle_markup = disabled | builtin | fullso callers can choose whether style markup is parsed and whether custom tags are active.messagealso supports lightweight inline style tags such as<red>...</>,<b>...</>,<#ff0000>...</>, and<bg:#202020>...</>.- Runtime style-tag APIs now include
TextStyle,StyleTagRegistry,style_tag_registry(),default_style_tag_registry(),set_tag(...), anddefine_alias(...). - Style-tag lookup priority is formatter-local
style_tags> global style tag registry > builtin tags. sink.text_formatter.style_tagsnow supports a minimal object mapping withfg,bg,bold,dim,italic, andunderline.define_alias(...)remains a runtime-only API and is not yet part of the JSON config schema.sink.rotationcurrently supportsmax_bytesandmax_backupsfor basic size-based rotation and backup retention.file_sink(...)also exposesreopen(),reopen_with_current_policy(),reopen_append(),reopen_truncate(),open_failures(),write_failures(),flush_failures(), androtation_failures()for basic observability.file_sink(...)also exposesappend_mode(). Passingappend=...toreopen(...)updates the current append policy used by later reopen calls,reopen_with_current_policy()makes that stored-policy reopen path explicit, andreopen_append()/reopen_truncate()cover the two common policy switches directly.file_sink(...)also supportsset_append_mode(...)for explicitly changing the append policy that later reopen calls will use.file_sink(...)also exposespath()andauto_flush_enabled()for reading basic file-sink policy state.file_sink(...)also exposesrotation_enabled()androtation_config()for reading whether rotation is active and which parameters are currently in effect.file_sink(...)also exposesstate()for reading a single snapshot that includes path, availability, append policy, auto-flush flag, rotation config, and all current failure counters.file_sink(...)also exposespolicy()anddefault_policy()for reading the current runtime policy and the sink's original default policy separately.file_sink(...)also exposespolicy_matches_default()for explicitly checking whether the current runtime policy has drifted from the original defaults.file_sink(...)also exposesset_policy(...)for applying append, auto-flush, and rotation as a single bundled runtime policy update.file_sink(...)also exposesreset_failure_counters()so open/write/flush/rotation failure counters can be cleared after diagnostics or recovery handling.file_sink(...)also exposesreset_policy()so append, auto-flush, and rotation settings can be restored to the sink's original defaults.file_sink(...)also supportsset_auto_flush(...),set_rotation(...), andclear_rotation()for runtime policy updates.ConfiguredLoggerbuilt throughbuild_logger(...)also exposesfile_reopen(),file_reopen_with_current_policy(),file_reopen_append(),file_reopen_truncate(),file_flush(),file_close(),file_append_mode(),file_path(),file_auto_flush(),file_rotation_enabled(),file_rotation_config(),file_state(), plusfile_set_append_mode(...),file_set_auto_flush(...),file_set_rotation(...),file_clear_rotation(), and the corresponding file failure counters, so config-driven file logging keeps a usable control surface.ConfiguredLoggeralso exposesfile_runtime_state()so queued file loggers can report both the underlying file snapshot and the outer queue backlog/drop state in one read.ConfiguredLoggeralso exposesfile_policy()andfile_default_policy()for reading current runtime file policy and initial config policy separately.ConfiguredLoggeralso exposesfile_policy_matches_default()for explicitly checking whether the current runtime file policy differs from its default config.ConfiguredLoggeralso exposesfile_set_policy(...)for applying a bundled runtime file policy through the config-built control surface.ConfiguredLoggeralso exposesfile_reset_failure_counters()for clearing file failure counters through the config-built control surface.ConfiguredLoggeralso exposesfile_reset_policy()for restoring runtime file policy back to the initial config values.file_sink_policy_to_json(...)andstringify_file_sink_policy(...)can export standalone file-policy snapshots directly as JSON for policy diffing, diagnostics, or reporting.file_sink_state_to_json(...),stringify_file_sink_state(...),runtime_file_state_to_json(...), andstringify_runtime_file_state(...)can export file and queued-file snapshots directly as JSON for diagnostics or reporting.sink.text_formatter.templatecurrently supports fixed tokens:{timestamp},{timestamp_ms},{level},{target},{message}, and{fields}.sink.text_formatter.color_modecurrently supportsnever,auto, andalways.sink.text_formatter.style_markupcurrently supportsdisabled,builtin, andfull.sink.text_formatter.style_tags.<name>currently supportsfg,bg,bold,dim,italic, andunderline.- Config-driven sink assembly currently supports
console,json_console,text_console, andfile. queueremains a synchronous bounded wrapper around the final sink, not an async runtime.
Async Layer
- A separate
bitlogger_async/package is now included. - It uses
moonbitlang/asyncand providesAsyncLogger,async_logger(...), a backgroundrun()worker, and bounded async queue delivery. - The current async API already supports
with_context_fields(...),with_filter(...),with_patch(...),with_target(...), andchild(...). shutdown()is now the recommended way to stop the async worker. By default it waits for the queue to drain, closes the queue, and then waits for the worker to exit.- Basic lifecycle observability is also available through
is_closed(),is_running(),has_failed(), andlast_error(). - The async worker now supports batched queue draining via
max_batchand basic flush policies throughflush=Never|Batch|Shutdown. - The recommended startup pattern is shown in examples/async_basic/main.mbt.
- This layer currently targets
native/llvmonly and remains isolated from the synchronous logger core.
Async Config
parse_async_logger_config_text(...),stringify_async_logger_config(...),parse_async_logger_build_config_text(...), andbuild_async_logger(...)are now available.- The JSON root is split into
loggerandasync_config. loggerfully reuses the synchronousLoggerConfigschema, whileasync_configcurrently supportsmax_pending,overflow,max_batch, andflush.- The recommended config-driven startup flow is shown in examples/async_basic/main.mbt.