mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
🎉 Release BitLogger v0.1.0 core
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
moonbit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install MoonBit
|
||||||
|
run: |
|
||||||
|
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
|
||||||
|
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
|
- name: Show tool versions
|
||||||
|
run: |
|
||||||
|
moon version
|
||||||
|
|
||||||
|
- name: Check bitlogger
|
||||||
|
run: |
|
||||||
|
moon check bitlogger
|
||||||
|
|
||||||
|
- name: Test bitlogger
|
||||||
|
run: |
|
||||||
|
moon test bitlogger
|
||||||
|
|
||||||
|
- name: Run example
|
||||||
|
run: |
|
||||||
|
moon run examples/basic
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# BitLogger
|
||||||
|
|
||||||
|
BitLogger is a MoonBit logging library in early development.
|
||||||
|
|
||||||
|
## Repository Layout
|
||||||
|
|
||||||
|
- `bitlogger/`: library package, tests, and checked package README
|
||||||
|
- `examples/basic/`: runnable example program
|
||||||
|
- `docs/dev/`: development notes and MoonBit gotchas collected during implementation
|
||||||
|
|
||||||
|
## Current MVP
|
||||||
|
|
||||||
|
- log levels
|
||||||
|
- structured fields
|
||||||
|
- sink trait
|
||||||
|
- console sink
|
||||||
|
- JSON console sink
|
||||||
|
- context fields
|
||||||
|
- child target composition
|
||||||
|
- fanout sink composition
|
||||||
|
- callback sink
|
||||||
|
- optional timestamps
|
||||||
|
- global default logger helpers
|
||||||
|
|
||||||
|
For the checked MoonBit example, see [bitlogger/README.mbt.md](./bitlogger/README.mbt.md).
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
///|
|
||||||
|
/// BitLogger public API surface.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
test "level filter works" {
|
||||||
|
let logger = Logger::new(console_sink(), min_level=Level::Warn, target="test")
|
||||||
|
inspect(logger.is_enabled(Level::Error), content="true")
|
||||||
|
inspect(logger.is_enabled(Level::Info), content="false")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "context sink merges fields" {
|
||||||
|
let logger = Logger::new(console_sink(), min_level=Level::Info, target="ctx")
|
||||||
|
.with_context_fields([field("service", "bitlogger")])
|
||||||
|
let merged = [field("service", "bitlogger"), field("mode", "test")]
|
||||||
|
inspect(merged.length(), content="2")
|
||||||
|
inspect(merged[0].key, content="service")
|
||||||
|
inspect(merged[1].key, content="mode")
|
||||||
|
logger.info("hello", fields=[field("mode", "test")])
|
||||||
|
}
|
||||||
|
|
||||||
|
test "fanout sink can write to plain and json outputs" {
|
||||||
|
let logger = Logger::new(
|
||||||
|
fanout_sink(console_sink(), json_console_sink()),
|
||||||
|
min_level=Level::Info,
|
||||||
|
target="fanout",
|
||||||
|
)
|
||||||
|
inspect(logger.is_enabled(Level::Info), content="true")
|
||||||
|
logger.info("hello", fields=[field("kind", "dual")])
|
||||||
|
}
|
||||||
|
|
||||||
|
test "child logger composes target path" {
|
||||||
|
let logger = Logger::new(console_sink(), min_level=Level::Info, target="app")
|
||||||
|
.child("worker")
|
||||||
|
inspect(logger.target, content="app.worker")
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
test "default logger can be reconfigured" {
|
||||||
|
set_default_min_level(Level::Debug)
|
||||||
|
set_default_target("global")
|
||||||
|
let logger = default_logger()
|
||||||
|
inspect(logger.min_level.label(), content="DEBUG")
|
||||||
|
inspect(logger.target, content="global")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "logger can enable timestamps" {
|
||||||
|
let logger = Logger::new(console_sink(), min_level=Level::Info, target="time")
|
||||||
|
.with_timestamp()
|
||||||
|
inspect(logger.timestamp, content="true")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "callback sink receives record" {
|
||||||
|
let captured_target : Ref[String] = Ref::new("")
|
||||||
|
let captured_message : Ref[String] = Ref::new("")
|
||||||
|
let logger = Logger::new(
|
||||||
|
callback_sink(fn(rec) {
|
||||||
|
captured_target.val = rec.target
|
||||||
|
captured_message.val = rec.message
|
||||||
|
}),
|
||||||
|
min_level=Level::Info,
|
||||||
|
target="callback",
|
||||||
|
)
|
||||||
|
logger.info("hello")
|
||||||
|
inspect(captured_target.val, content="callback")
|
||||||
|
inspect(captured_message.val, content="hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "callback sink sees child target and context logger shape" {
|
||||||
|
let captured_target : Ref[String] = Ref::new("")
|
||||||
|
let captured_message : Ref[String] = Ref::new("")
|
||||||
|
let captured_field_count : Ref[Int] = Ref::new(0)
|
||||||
|
let captured_timestamp : Ref[UInt64] = Ref::new(0UL)
|
||||||
|
let logger = Logger::new(
|
||||||
|
callback_sink(fn(rec) {
|
||||||
|
captured_target.val = rec.target
|
||||||
|
captured_message.val = rec.message
|
||||||
|
captured_field_count.val = rec.fields.length()
|
||||||
|
captured_timestamp.val = rec.timestamp_ms
|
||||||
|
}),
|
||||||
|
min_level=Level::Info,
|
||||||
|
target="app",
|
||||||
|
)
|
||||||
|
.child("worker")
|
||||||
|
.with_context_fields([field("service", "bitlogger")])
|
||||||
|
.with_timestamp()
|
||||||
|
logger.info("ready", fields=[field("mode", "test")])
|
||||||
|
inspect(captured_target.val, content="app.worker")
|
||||||
|
inspect(captured_message.val, content="ready")
|
||||||
|
inspect(captured_field_count.val, content="2")
|
||||||
|
inspect(captured_timestamp.val > 0UL, content="true")
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# BitLogger
|
||||||
|
|
||||||
|
BitLogger is a minimal structured logger for MoonBit.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- log levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`
|
||||||
|
- structured key-value fields
|
||||||
|
- sink abstraction
|
||||||
|
- default global console logger
|
||||||
|
- context fields via `with_context_fields(...)`
|
||||||
|
- child target composition via `child(...)`
|
||||||
|
- optional timestamps via `with_timestamp()`
|
||||||
|
- JSON console output via `json_console_sink()`
|
||||||
|
- sink composition via `fanout_sink(...)`
|
||||||
|
- custom callback sink via `callback_sink(...)`
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```mbt check
|
||||||
|
test {
|
||||||
|
let logger = Logger::new(console_sink(), min_level=Level::Debug, target="demo")
|
||||||
|
.with_timestamp()
|
||||||
|
logger.info("starting", fields=[field("port", "8080")])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```mbt check
|
||||||
|
test {
|
||||||
|
let logger = Logger::new(console_sink(), target="app").child("worker")
|
||||||
|
logger.info("ready")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```mbt check
|
||||||
|
test {
|
||||||
|
let logger = Logger::new(
|
||||||
|
fanout_sink(console_sink(), json_console_sink()),
|
||||||
|
min_level=Level::Info,
|
||||||
|
target="demo",
|
||||||
|
)
|
||||||
|
logger.info("ready", fields=[field("mode", "fanout")])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Current MVP includes plain-text output, JSON console output, context fields, child target composition, and simple sink fanout.
|
||||||
|
The intended public entry points are `Logger`, sink constructor helpers, `field(...)`, and the default global logger helpers.
|
||||||
|
Next useful steps are file sink and buffered or async delivery.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
fn fields_to_json(fields : Array[Field]) -> Json {
|
||||||
|
let obj : Map[String, Json] = {}
|
||||||
|
for item in fields {
|
||||||
|
obj[item.key] = Json::string(item.value)
|
||||||
|
}
|
||||||
|
Json::object(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_record(rec : Record) -> String {
|
||||||
|
let prefix = if rec.timestamp_ms == 0UL {
|
||||||
|
"[\{rec.level.label()}]"
|
||||||
|
} else {
|
||||||
|
"[\{rec.timestamp_ms.to_string()}] [\{rec.level.label()}]"
|
||||||
|
}
|
||||||
|
let base = if rec.target == "" {
|
||||||
|
"\{prefix} \{rec.message}"
|
||||||
|
} else {
|
||||||
|
"\{prefix} [\{rec.target}] \{rec.message}"
|
||||||
|
}
|
||||||
|
if rec.fields.length() == 0 {
|
||||||
|
base
|
||||||
|
} else {
|
||||||
|
let details = rec.fields.map(fn(f) { "\{f.key}=\{f.value}" }).join(" ")
|
||||||
|
"\{base} \{details}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_record_json(rec : Record) -> String {
|
||||||
|
let obj : Map[String, Json] = {
|
||||||
|
"level": Json::string(rec.level.label()),
|
||||||
|
"message": Json::string(rec.message),
|
||||||
|
"fields": fields_to_json(rec.fields),
|
||||||
|
}
|
||||||
|
if rec.timestamp_ms != 0UL {
|
||||||
|
obj["timestamp_ms"] = rec.timestamp_ms.to_json()
|
||||||
|
}
|
||||||
|
if rec.target == "" {
|
||||||
|
Json::object(obj).stringify()
|
||||||
|
} else {
|
||||||
|
obj["target"] = Json::string(rec.target)
|
||||||
|
Json::object(obj).stringify()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
let default_console_sink : ConsoleSink = console_sink()
|
||||||
|
let default_min_level_ref : Ref[Level] = Ref::new(Level::Info)
|
||||||
|
let default_target_ref : Ref[String] = Ref::new("")
|
||||||
|
|
||||||
|
pub fn set_default_min_level(level : Level) -> Unit {
|
||||||
|
default_min_level_ref.val = level
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_default_target(target : String) -> Unit {
|
||||||
|
default_target_ref.val = target
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_logger() -> Logger[ConsoleSink] {
|
||||||
|
Logger::new(default_console_sink, min_level=default_min_level_ref.val, target=default_target_ref.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log(level : Level, message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
default_logger().log(level, message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trace(message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
default_logger().trace(message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug(message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
default_logger().debug(message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info(message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
default_logger().info(message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warn(message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
default_logger().warn(message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error(message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
default_logger().error(message, fields=fields)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
pub(all) enum Level {
|
||||||
|
Trace
|
||||||
|
Debug
|
||||||
|
Info
|
||||||
|
Warn
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Level::priority(self : Level) -> Int {
|
||||||
|
match self {
|
||||||
|
Level::Trace => 10
|
||||||
|
Level::Debug => 20
|
||||||
|
Level::Info => 30
|
||||||
|
Level::Warn => 40
|
||||||
|
Level::Error => 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Level::label(self : Level) -> String {
|
||||||
|
match self {
|
||||||
|
Level::Trace => "TRACE"
|
||||||
|
Level::Debug => "DEBUG"
|
||||||
|
Level::Info => "INFO"
|
||||||
|
Level::Warn => "WARN"
|
||||||
|
Level::Error => "ERROR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Level::enabled(self : Level, min_level : Level) -> Bool {
|
||||||
|
self.priority() >= min_level.priority()
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
pub struct Logger[S] {
|
||||||
|
min_level : Level
|
||||||
|
sink : S
|
||||||
|
target : String
|
||||||
|
timestamp : Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S] Logger::new(sink : S, min_level~ : Level = Level::Info, target~ : String = "") -> Logger[S] {
|
||||||
|
{ min_level, sink, target, timestamp: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S] Logger::with_target(self : Logger[S], target : String) -> Logger[S] {
|
||||||
|
{ ..self, target }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine_targets(parent : String, child : String) -> String {
|
||||||
|
if parent == "" {
|
||||||
|
child
|
||||||
|
} else if child == "" {
|
||||||
|
parent
|
||||||
|
} else {
|
||||||
|
"\{parent}.\{child}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S] Logger::child(self : Logger[S], target : String) -> Logger[S] {
|
||||||
|
{ ..self, target: combine_targets(self.target, target) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S] Logger::with_context_fields(self : Logger[S], fields : Array[Field]) -> Logger[ContextSink[S]] {
|
||||||
|
{
|
||||||
|
min_level: self.min_level,
|
||||||
|
sink: ContextSink::{ sink: self.sink, context_fields: fields },
|
||||||
|
target: self.target,
|
||||||
|
timestamp: self.timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S] Logger::with_min_level(self : Logger[S], min_level : Level) -> Logger[S] {
|
||||||
|
{ ..self, min_level }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S] Logger::with_timestamp(self : Logger[S], enabled~ : Bool = true) -> Logger[S] {
|
||||||
|
{ ..self, timestamp: enabled }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S] Logger::is_enabled(self : Logger[S], level : Level) -> Bool {
|
||||||
|
level.enabled(self.min_level)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S : Sink] Logger::log(
|
||||||
|
self : Logger[S],
|
||||||
|
level : Level,
|
||||||
|
message : String,
|
||||||
|
fields~ : Array[Field] = [],
|
||||||
|
target? : String = "",
|
||||||
|
) -> Unit {
|
||||||
|
if !self.is_enabled(level) {
|
||||||
|
()
|
||||||
|
} else {
|
||||||
|
let actual_target = if target == "" { self.target } else { target }
|
||||||
|
let timestamp_ms = if self.timestamp { @env.now() } else { 0UL }
|
||||||
|
self.sink.write(
|
||||||
|
record(level, message, timestamp_ms=timestamp_ms, target=actual_target, fields=fields),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S : Sink] Logger::trace(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
self.log(Level::Trace, message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S : Sink] Logger::debug(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
self.log(Level::Debug, message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S : Sink] Logger::info(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
self.log(Level::Info, message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S : Sink] Logger::warn(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
self.log(Level::Warn, message, fields=fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[S : Sink] Logger::error(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
|
||||||
|
self.log(Level::Error, message, fields=fields)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import {
|
||||||
|
"moonbitlang/core/array",
|
||||||
|
"moonbitlang/core/builtin",
|
||||||
|
"moonbitlang/core/env" @env,
|
||||||
|
"moonbitlang/core/json",
|
||||||
|
"moonbitlang/core/ref",
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
pub struct Field {
|
||||||
|
key : String
|
||||||
|
value : String
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field(key : String, value : String) -> Field {
|
||||||
|
{ key, value }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Record {
|
||||||
|
level : Level
|
||||||
|
timestamp_ms : UInt64
|
||||||
|
target : String
|
||||||
|
message : String
|
||||||
|
fields : Array[Field]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record(
|
||||||
|
level : Level,
|
||||||
|
message : String,
|
||||||
|
timestamp_ms~ : UInt64 = 0UL,
|
||||||
|
target~ : String = "",
|
||||||
|
fields~ : Array[Field] = [],
|
||||||
|
) -> Record {
|
||||||
|
{ level, timestamp_ms, target, message, fields }
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
pub trait Sink {
|
||||||
|
write(Self, Record) -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConsoleSink {
|
||||||
|
_dummy : Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn console_sink() -> ConsoleSink {
|
||||||
|
{ _dummy: () }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub impl Sink for ConsoleSink with write(self, rec) {
|
||||||
|
ignore(self)
|
||||||
|
println(format_record(rec))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ContextSink[S] {
|
||||||
|
sink : S
|
||||||
|
context_fields : Array[Field]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub impl[S : Sink] Sink for ContextSink[S] with write(self, rec) {
|
||||||
|
let merged = if self.context_fields.length() == 0 {
|
||||||
|
rec.fields
|
||||||
|
} else if rec.fields.length() == 0 {
|
||||||
|
self.context_fields
|
||||||
|
} else {
|
||||||
|
self.context_fields + rec.fields
|
||||||
|
}
|
||||||
|
self.sink.write({ ..rec, fields: merged })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JsonConsoleSink {
|
||||||
|
_dummy : Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json_console_sink() -> JsonConsoleSink {
|
||||||
|
{ _dummy: () }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub impl Sink for JsonConsoleSink with write(self, rec) {
|
||||||
|
ignore(self)
|
||||||
|
println(format_record_json(rec))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FanoutSink[A, B] {
|
||||||
|
left : A
|
||||||
|
right : B
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn[A, B] fanout_sink(left : A, right : B) -> FanoutSink[A, B] {
|
||||||
|
{ left, right }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with write(self, rec) {
|
||||||
|
self.left.write(rec)
|
||||||
|
self.right.write({ ..rec })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CallbackSink {
|
||||||
|
callback : (Record) -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn callback_sink(callback : (Record) -> Unit) -> CallbackSink {
|
||||||
|
{ callback, }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub impl Sink for CallbackSink with write(self, rec) {
|
||||||
|
(self.callback)(rec)
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
fn main {
|
||||||
|
@lib.set_default_min_level(@lib.Level::Debug)
|
||||||
|
@lib.set_default_target("bitlogger")
|
||||||
|
@lib.info("hello from BitLogger", fields=[@lib.field("mode", "demo")])
|
||||||
|
|
||||||
|
let logger = @lib.Logger::new(@lib.console_sink(), min_level=@lib.Level::Trace, target="custom")
|
||||||
|
.with_context_fields([@lib.field("service", "bitlogger")])
|
||||||
|
logger.debug("custom logger ready", fields=[@lib.field("sink", "console")])
|
||||||
|
|
||||||
|
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")])
|
||||||
|
|
||||||
|
let fanout_logger = @lib.Logger::new(
|
||||||
|
@lib.fanout_sink(@lib.console_sink(), @lib.json_console_sink()),
|
||||||
|
min_level=@lib.Level::Info,
|
||||||
|
target="fanout",
|
||||||
|
)
|
||||||
|
fanout_logger.info("dual output", fields=[@lib.field("kind", "fanout")])
|
||||||
|
|
||||||
|
let timed_logger = @lib.Logger::new(@lib.console_sink(), min_level=@lib.Level::Info, target="timed")
|
||||||
|
.with_timestamp()
|
||||||
|
timed_logger.info("timestamp enabled", fields=[@lib.field("kind", "time")])
|
||||||
|
|
||||||
|
let child_logger = @lib.Logger::new(@lib.console_sink(), min_level=@lib.Level::Info, target="app")
|
||||||
|
.child("worker")
|
||||||
|
child_logger.info("child target ready")
|
||||||
|
|
||||||
|
let callback_logger = @lib.Logger::new(
|
||||||
|
@lib.callback_sink(fn(rec) {
|
||||||
|
println("callback saw [\{rec.target}] \{rec.message}")
|
||||||
|
}),
|
||||||
|
min_level=@lib.Level::Info,
|
||||||
|
target="hook",
|
||||||
|
)
|
||||||
|
callback_logger.info("callback sink ready")
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import {
|
||||||
|
"miaom/BitLogger/bitlogger" @lib,
|
||||||
|
}
|
||||||
|
|
||||||
|
options(
|
||||||
|
"is-main": true,
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "miaom/BitLogger",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"readme": "README.mbt.md",
|
||||||
|
"repository": "",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"keywords": [
|
||||||
|
"logger",
|
||||||
|
"logging",
|
||||||
|
"moonbit"
|
||||||
|
],
|
||||||
|
"description": "A minimal structured logger for MoonBit."
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user