🔊 Update 1.0.0

This commit is contained in:
Nanaloveyuki
2026-06-27 10:45:36 +08:00
parent 4cc43def73
commit 0cbe25a551
55 changed files with 5734 additions and 2360 deletions
+26
View File
@@ -0,0 +1,26 @@
## BitLogger Update Changes
version 1.0.0
### Migration
- migration: run the MoonBit 0.10 formatter migration across the repository and adopt the current `trait` / `impl` formatting and `fn`-prefixed impl method style
- migration: replace the legacy root manifest with `moon.mod`, aligning the package root with the current MoonBit manifest format
- migration: update the async dependency to `moonbitlang/async@0.20.0`
### Docs
- docs: refresh the README example and project command notes after the formatter migration so examples match the current MoonBit surface
- docs: document that `async_basic` depends on `moonbitlang/async`, whose upstream README currently only claims native/LLVM support on Linux/MacOS
- docs: narrow the repository prompt validation guidance so Windows-local `src-async --target native` failures are not treated as BitLogger regressions unless reproduced on a supported native platform
### Verification
- verify: keep the main package passing `moon check`, `moon test`, `moon check --target native`, `moon check --target wasm-gc`, and `moon check --target js`
- verify: keep `src-async` passing `moon check` / `moon test` on `wasm-gc` and `js`, and confirm the native async path on a supported environment baseline
- verify: keep the example path healthy with `moon run examples/basic` and `moon check examples/async_basic --target native`
### Notes
- this release marks the MoonBit 0.10 generation as the new BitLogger baseline
- the repository now treats Linux/MacOS as the supported native baseline for `moonbitlang/async` until upstream Windows native support is stated clearly
+1
View File
@@ -2,6 +2,7 @@
Versioned BitLogger change summaries. Versioned BitLogger change summaries.
- [1.0.0](./1.0.0.md)
- [0.5.3](./0.5.3.md) - [0.5.3](./0.5.3.md)
- [0.5.2](./0.5.2.md) - [0.5.2](./0.5.2.md)
- [0.5.1](./0.5.1.md) - [0.5.1](./0.5.1.md)
+14 -4
View File
@@ -1,6 +1,14 @@
///|
async fn main { async fn main {
println("examples/async_basic is shipped as a native-only example because async fn main entry support is still limited on non-native targets, even though src-async itself keeps compatibility behavior.") println(
println(@lib_async.stringify_async_runtime_state(@lib_async.async_runtime_state(), pretty=true)) "examples/async_basic is shipped as a native-only example because async fn main entry support is still limited on non-native targets, even though src-async itself keeps compatibility behavior.",
)
println(
@lib_async.stringify_async_runtime_state(
@lib_async.async_runtime_state(),
pretty=true,
),
)
let raw = "{\"logger\":{\"min_level\":\"info\",\"target\":\"async.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \"}}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":4,\"linger_ms\":5,\"flush\":\"Batch\"}}" let raw = "{\"logger\":{\"min_level\":\"info\",\"target\":\"async.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \"}}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":4,\"linger_ms\":5,\"flush\":\"Batch\"}}"
let config = @lib_async.parse_async_logger_build_config_text(raw) catch { let config = @lib_async.parse_async_logger_build_config_text(raw) catch {
@@ -14,10 +22,12 @@ async fn main {
let logger = @lib_async.build_async_logger(config) let logger = @lib_async.build_async_logger(config)
.with_context_fields([@lib.field("service", "bitlogger")]) .with_context_fields([@lib.field("service", "bitlogger")])
.with_filter(@lib.target_has_prefix("async")) .with_filter(@lib.target_has_prefix("async"))
.with_patch(@lib.compose_patches([ .with_patch(
@lib.compose_patches([
@lib.prefix_message("[async] "), @lib.prefix_message("[async] "),
@lib.redact_fields(["token"]), @lib.redact_fields(["token"]),
])) ]),
)
println(@lib_async.stringify_async_logger_state(logger.state(), pretty=true)) println(@lib_async.stringify_async_logger_state(logger.state(), pretty=true))
+1 -1
View File
@@ -1,7 +1,7 @@
import { import {
"Nanaloveyuki/BitLogger/src" @lib, "Nanaloveyuki/BitLogger/src" @lib,
"Nanaloveyuki/BitLogger/src-async" @lib_async, "Nanaloveyuki/BitLogger/src-async" @lib_async,
"moonbitlang/async" @async, "moonbitlang/async",
} }
supported_targets = "+native" supported_targets = "+native"
+66 -24
View File
@@ -1,10 +1,14 @@
///|
fn main { fn main {
let preset_logger = @lib.build_logger( let preset_logger = @lib.build_logger(
@lib.with_queue( @lib.with_queue(
@lib.text_console( @lib.text_console(
min_level=@lib.Level::Info, min_level=@lib.Level::Info,
target="preset", target="preset",
text_formatter=@lib.TextFormatterConfig::new(show_timestamp=false, separator=" | "), text_formatter=@lib.TextFormatterConfig::new(
show_timestamp=false,
separator=" | ",
),
), ),
max_pending=2, max_pending=2,
overflow=@lib.QueueOverflowPolicy::DropOldest, overflow=@lib.QueueOverflowPolicy::DropOldest,
@@ -20,8 +24,11 @@ fn main {
@lib.info("hello from BitLogger", fields=[@lib.field("mode", "demo")]) @lib.info("hello from BitLogger", fields=[@lib.field("mode", "demo")])
// Direct Logger::new(...) composition stays useful for custom sink graphs. // Direct Logger::new(...) composition stays useful for custom sink graphs.
let logger = @lib.Logger::new(@lib.console_sink(), min_level=@lib.Level::Trace, target="custom") let logger = @lib.Logger::new(
.with_context_fields([@lib.field("service", "bitlogger")]) @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")]) logger.debug("custom logger ready", fields=[@lib.field("sink", "console")])
let bound_logger = @lib.Logger::new( let bound_logger = @lib.Logger::new(
@@ -31,7 +38,11 @@ fn main {
).bind(@lib.fields([("service", "bitlogger"), ("scope", "audit")])) ).bind(@lib.fields([("service", "bitlogger"), ("scope", "audit")]))
bound_logger.info("bound logger ready", fields=[@lib.field("mode", "demo")]) 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") 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")]) json_logger.info("json output", fields=[@lib.field("kind", "example")])
let fanout_logger = @lib.Logger::new( let fanout_logger = @lib.Logger::new(
@@ -55,12 +66,18 @@ fn main {
split_logger.info("normal output") split_logger.info("normal output")
split_logger.warn("warning output") split_logger.warn("warning output")
let timed_logger = @lib.Logger::new(@lib.console_sink(), min_level=@lib.Level::Info, target="timed") let timed_logger = @lib.Logger::new(
.with_timestamp() @lib.console_sink(),
min_level=@lib.Level::Info,
target="timed",
).with_timestamp()
timed_logger.info("timestamp enabled", fields=[@lib.field("kind", "time")]) 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") let child_logger = @lib.Logger::new(
.child("worker") @lib.console_sink(),
min_level=@lib.Level::Info,
target="app",
).child("worker")
child_logger.info("child target ready") child_logger.info("child target ready")
let callback_logger = @lib.Logger::new( let callback_logger = @lib.Logger::new(
@@ -109,25 +126,36 @@ fn main {
min_level=@lib.Level::Info, min_level=@lib.Level::Info,
target="file", target="file",
) )
file_logger.info("native file sink ready", fields=[@lib.field("kind", "file")]) file_logger.info("native file sink ready", fields=[
@lib.field("kind", "file"),
])
ignore(file_logger.sink.flush()) ignore(file_logger.sink.flush())
ignore(file_logger.sink.close()) ignore(file_logger.sink.close())
} }
let buffered = @lib.buffered_sink(@lib.console_sink(), flush_limit=2) let buffered = @lib.buffered_sink(@lib.console_sink(), flush_limit=2)
let buffered_logger = @lib.Logger::new(buffered, min_level=@lib.Level::Info, target="buffered") let buffered_logger = @lib.Logger::new(
buffered,
min_level=@lib.Level::Info,
target="buffered",
)
buffered_logger.info("buffered one") buffered_logger.info("buffered one")
buffered_logger.info("buffered two") buffered_logger.info("buffered two")
buffered.flush() buffered.flush()
let filtered = @lib.filter_sink( let filtered = @lib.filter_sink(@lib.console_sink(), fn(rec) {
@lib.console_sink(),
fn(rec) {
rec.target == "kept" rec.target == "kept"
}, })
let kept_logger = @lib.Logger::new(
filtered,
min_level=@lib.Level::Info,
target="kept",
)
let dropped_logger = @lib.Logger::new(
filtered,
min_level=@lib.Level::Info,
target="dropped",
) )
let kept_logger = @lib.Logger::new(filtered, min_level=@lib.Level::Info, target="kept")
let dropped_logger = @lib.Logger::new(filtered, min_level=@lib.Level::Info, target="dropped")
kept_logger.info("filter kept this") kept_logger.info("filter kept this")
dropped_logger.info("filter dropped this") dropped_logger.info("filter dropped this")
@@ -135,10 +163,12 @@ fn main {
@lib.console_sink(), @lib.console_sink(),
min_level=@lib.Level::Info, min_level=@lib.Level::Info,
target="service", target="service",
).with_filter(@lib.all_of([ ).with_filter(
@lib.all_of([
@lib.target_has_prefix("service"), @lib.target_has_prefix("service"),
@lib.message_contains("kept"), @lib.message_contains("kept"),
])) ]),
)
filtered_logger.info("logger filter dropped this") filtered_logger.info("logger filter dropped this")
filtered_logger.child("api").info("logger filter kept this") filtered_logger.child("api").info("logger filter kept this")
@@ -146,12 +176,17 @@ fn main {
@lib.console_sink(), @lib.console_sink(),
min_level=@lib.Level::Info, min_level=@lib.Level::Info,
target="auth", target="auth",
).with_patch(@lib.compose_patches([ ).with_patch(
@lib.compose_patches([
@lib.prefix_message("[safe] "), @lib.prefix_message("[safe] "),
@lib.redact_fields(["token"]), @lib.redact_fields(["token"]),
@lib.append_fields([@lib.field("service", "bitlogger")]), @lib.append_fields([@lib.field("service", "bitlogger")]),
])) ]),
patched_logger.info("login", fields=[@lib.field("user", "alice"), @lib.field("token", "secret")]) )
patched_logger.info("login", fields=[
@lib.field("user", "alice"),
@lib.field("token", "secret"),
])
let config_logger = @lib.parse_and_build_logger( let config_logger = @lib.parse_and_build_logger(
"{\"min_level\":\"debug\",\"target\":\"config.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"field_separator\":\",\",\"template\":\"[{level}] {target} {message} :: {fields}\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}", "{\"min_level\":\"debug\",\"target\":\"config.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"field_separator\":\",\",\"template\":\"[{level}] {target} {message} :: {fields}\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}",
@@ -165,7 +200,10 @@ fn main {
config_logger.info("configured from json") config_logger.info("configured from json")
ignore(config_logger.flush()) ignore(config_logger.flush())
let queue_config = @lib.QueueConfig::new(4, overflow=@lib.QueueOverflowPolicy::DropOldest) let queue_config = @lib.QueueConfig::new(
4,
overflow=@lib.QueueOverflowPolicy::DropOldest,
)
println(@lib.stringify_queue_config(queue_config)) println(@lib.stringify_queue_config(queue_config))
let formatter_config = @lib.TextFormatterConfig::new( let formatter_config = @lib.TextFormatterConfig::new(
@@ -195,13 +233,17 @@ fn main {
let file_runtime_logger = @lib.build_logger( let file_runtime_logger = @lib.build_logger(
@lib.LoggerConfig::new( @lib.LoggerConfig::new(
sink=@lib.SinkConfig::new(kind=@lib.SinkKind::File, path="bitlogger-runtime-demo.log"), sink=@lib.SinkConfig::new(
kind=@lib.SinkKind::File,
path="bitlogger-runtime-demo.log",
),
queue=Some(@lib.QueueConfig::new(8)), queue=Some(@lib.QueueConfig::new(8)),
), ),
) )
file_runtime_logger.info("runtime file logger ready") file_runtime_logger.info("runtime file logger ready")
match file_runtime_logger.file_runtime_state() { match file_runtime_logger.file_runtime_state() {
Some(snapshot) => println(@lib.stringify_runtime_file_state(snapshot, pretty=true)) Some(snapshot) =>
println(@lib.stringify_runtime_file_state(snapshot, pretty=true))
None => () None => ()
} }
ignore(file_runtime_logger.file_close()) ignore(file_runtime_logger.file_close())
+1
View File
@@ -1,3 +1,4 @@
///|
fn main { fn main {
let config = @lib.parse_logger_config_text( let config = @lib.parse_logger_config_text(
"{\"min_level\":\"debug\",\"target\":\"demo.config\",\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \",\"template\":\"[{level}] {target} {message}\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}", "{\"min_level\":\"debug\",\"target\":\"demo.config\",\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \",\"template\":\"[{level}] {target} {message}\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}",
+1
View File
@@ -1,3 +1,4 @@
///|
fn main { fn main {
let logger = @lib.build_logger( let logger = @lib.build_logger(
@lib.console(min_level=@lib.Level::Info, target="demo.console"), @lib.console(min_level=@lib.Level::Info, target="demo.console"),
+4 -1
View File
@@ -1,6 +1,9 @@
///|
fn main { fn main {
if !@lib.native_files_supported() { if !@lib.native_files_supported() {
println("native file sink is not available on this backend; examples/presets stays portable, while file_rotation remains native-focused") println(
"native file sink is not available on this backend; examples/presets stays portable, while file_rotation remains native-focused",
)
return return
} }
+7 -2
View File
@@ -1,3 +1,4 @@
///|
fn main { fn main {
let config = @lib.with_queue( let config = @lib.with_queue(
@lib.text_console( @lib.text_console(
@@ -36,10 +37,14 @@ fn main {
} }
let file_logger = @lib.build_logger(file_config) let file_logger = @lib.build_logger(file_config)
file_logger.info("file preset ready", fields=[@lib.field("kind", "native-only")]) file_logger.info("file preset ready", fields=[
@lib.field("kind", "native-only"),
])
ignore(file_logger.flush()) ignore(file_logger.flush())
ignore(file_logger.file_close()) ignore(file_logger.file_close())
} else { } else {
println("file presets are skipped on this backend because native file support is unavailable") println(
"file presets are skipped on this backend because native file support is unavailable",
)
} }
} }
+2 -3
View File
@@ -1,3 +1,4 @@
///|
fn main { fn main {
let logger = @lib.build_logger( let logger = @lib.build_logger(
@lib.text_console( @lib.text_console(
@@ -6,9 +7,7 @@ fn main {
text_formatter=@lib.TextFormatterConfig::new( text_formatter=@lib.TextFormatterConfig::new(
show_timestamp=false, show_timestamp=false,
color_mode=@lib.ColorMode::Always, color_mode=@lib.ColorMode::Always,
style_tags={ style_tags={ "accent": @lib.text_style(fg=Some("#4cc9f0"), bold=true) },
"accent": @lib.text_style(fg=Some("#4cc9f0"), bold=true),
},
), ),
), ),
) )
+5 -1
View File
@@ -1,3 +1,4 @@
///|
fn main { fn main {
let logger = @lib.build_logger( let logger = @lib.build_logger(
@lib.text_console( @lib.text_console(
@@ -11,5 +12,8 @@ fn main {
), ),
), ),
) )
logger.info("formatted output", fields=[@lib.field("user", "alice"), @lib.field("request_id", "42")]) logger.info("formatted output", fields=[
@lib.field("user", "alice"),
@lib.field("request_id", "42"),
])
} }
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -1,19 +1,26 @@
///|
pub type ApplicationAsyncLogger = AsyncLogger[@bitlogger.RuntimeSink] pub type ApplicationAsyncLogger = AsyncLogger[@bitlogger.RuntimeSink]
pub type ApplicationTextAsyncLogger = AsyncLogger[@bitlogger.FormattedConsoleSink] ///|
pub type ApplicationTextAsyncLogger = AsyncLogger[
@bitlogger.FormattedConsoleSink,
]
///|
pub fn build_application_async_logger( pub fn build_application_async_logger(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> ApplicationAsyncLogger { ) -> ApplicationAsyncLogger {
build_async_logger(config) build_async_logger(config)
} }
///|
pub fn build_application_text_async_logger( pub fn build_application_text_async_logger(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> ApplicationTextAsyncLogger { ) -> ApplicationTextAsyncLogger {
build_async_text_logger(config) build_async_text_logger(config)
} }
///|
pub fn parse_and_build_application_async_logger( pub fn parse_and_build_application_async_logger(
input : String, input : String,
) -> ApplicationAsyncLogger raise { ) -> ApplicationAsyncLogger raise {
+5
View File
@@ -1,20 +1,25 @@
///|
pub fn async_runtime_mode() -> AsyncRuntimeMode { pub fn async_runtime_mode() -> AsyncRuntimeMode {
@utils.native_worker_async_runtime_mode() @utils.native_worker_async_runtime_mode()
} }
///|
pub fn async_runtime_supports_background_worker() -> Bool { pub fn async_runtime_supports_background_worker() -> Bool {
ignore(all_async_runtime_modes()) ignore(all_async_runtime_modes())
true true
} }
///|
fn async_runtime_guard_closed_on_log() -> Bool { fn async_runtime_guard_closed_on_log() -> Bool {
false false
} }
///|
fn async_runtime_shutdown_clears_pending_after_wait_idle() -> Bool { fn async_runtime_shutdown_clears_pending_after_wait_idle() -> Bool {
true true
} }
///|
fn async_runtime_shutdown_waits_for_worker() -> Bool { fn async_runtime_shutdown_waits_for_worker() -> Bool {
true true
} }
+153 -69
View File
@@ -1,84 +1,125 @@
///|
pub(all) suberror AsyncLoggerClosed { pub(all) suberror AsyncLoggerClosed {
AsyncLoggerClosed AsyncLoggerClosed
} }
///|
pub type AsyncOverflowPolicy = @utils.AsyncOverflowPolicy pub type AsyncOverflowPolicy = @utils.AsyncOverflowPolicy
///|
pub type AsyncFlushPolicy = @utils.AsyncFlushPolicy pub type AsyncFlushPolicy = @utils.AsyncFlushPolicy
///|
pub type AsyncRuntimeMode = @utils.AsyncRuntimeMode pub type AsyncRuntimeMode = @utils.AsyncRuntimeMode
///|
fn all_async_runtime_modes() -> Array[AsyncRuntimeMode] { fn all_async_runtime_modes() -> Array[AsyncRuntimeMode] {
[@utils.native_worker_async_runtime_mode(), @utils.compatibility_async_runtime_mode()] [
@utils.native_worker_async_runtime_mode(),
@utils.compatibility_async_runtime_mode(),
]
} }
///|
pub type AsyncRuntimeState = @utils.AsyncRuntimeState pub type AsyncRuntimeState = @utils.AsyncRuntimeState
///|
pub type AsyncLoggerState = @utils.AsyncLoggerState pub type AsyncLoggerState = @utils.AsyncLoggerState
///|
pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String { pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String {
@utils.async_runtime_mode_label(mode) @utils.async_runtime_mode_label(mode)
} }
///|
pub fn async_runtime_state() -> AsyncRuntimeState { pub fn async_runtime_state() -> AsyncRuntimeState {
AsyncRuntimeState::new(async_runtime_mode(), async_runtime_supports_background_worker()) AsyncRuntimeState::new(
async_runtime_mode(),
async_runtime_supports_background_worker(),
)
} }
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> @json_parser.JsonValue { ///|
pub fn async_runtime_state_to_json(
state : AsyncRuntimeState,
) -> @json_parser.JsonValue {
@utils.async_runtime_state_to_json(state) @utils.async_runtime_state_to_json(state)
} }
///|
pub fn stringify_async_runtime_state( pub fn stringify_async_runtime_state(
state : AsyncRuntimeState, state : AsyncRuntimeState,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
@utils.stringify_async_runtime_state(state, pretty=pretty) @utils.stringify_async_runtime_state(state, pretty~)
} }
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.JsonValue { ///|
pub fn async_logger_state_to_json(
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
@utils.async_logger_state_to_json(state) @utils.async_logger_state_to_json(state)
} }
///|
pub fn stringify_async_logger_state( pub fn stringify_async_logger_state(
state : AsyncLoggerState, state : AsyncLoggerState,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
@utils.stringify_async_logger_state(state, pretty=pretty) @utils.stringify_async_logger_state(state, pretty~)
} }
///|
pub type AsyncLoggerConfig = @utils.AsyncLoggerConfig pub type AsyncLoggerConfig = @utils.AsyncLoggerConfig
pub fn parse_async_logger_config_text(input : String) -> AsyncLoggerConfig raise { ///|
pub fn parse_async_logger_config_text(
input : String,
) -> AsyncLoggerConfig raise {
@utils.parse_async_logger_config_text(input) @utils.parse_async_logger_config_text(input)
} }
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> @json_parser.JsonValue { ///|
pub fn async_logger_config_to_json(
config : AsyncLoggerConfig,
) -> @json_parser.JsonValue {
@utils.async_logger_config_to_json(config) @utils.async_logger_config_to_json(config)
} }
pub fn stringify_async_logger_config(config : AsyncLoggerConfig, pretty~ : Bool = false) -> String { ///|
@utils.stringify_async_logger_config(config, pretty=pretty) pub fn stringify_async_logger_config(
config : AsyncLoggerConfig,
pretty? : Bool = false,
) -> String {
@utils.stringify_async_logger_config(config, pretty~)
} }
///|
pub type AsyncLoggerBuildConfig = @utils.AsyncLoggerBuildConfig pub type AsyncLoggerBuildConfig = @utils.AsyncLoggerBuildConfig
pub fn parse_async_logger_build_config_text(input : String) -> AsyncLoggerBuildConfig raise { ///|
pub fn parse_async_logger_build_config_text(
input : String,
) -> AsyncLoggerBuildConfig raise {
@utils.parse_async_logger_build_config_text(input) @utils.parse_async_logger_build_config_text(input)
} }
///|
pub fn async_logger_build_config_to_json( pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> @json_parser.JsonValue { ) -> @json_parser.JsonValue {
@utils.async_logger_build_config_to_json(config) @utils.async_logger_build_config_to_json(config)
} }
///|
pub fn stringify_async_logger_build_config( pub fn stringify_async_logger_build_config(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
@utils.stringify_async_logger_build_config(config, pretty=pretty) @utils.stringify_async_logger_build_config(config, pretty~)
} }
///|
pub struct AsyncLogger[S] { pub struct AsyncLogger[S] {
min_level : @bitlogger.Level min_level : @bitlogger.Level
target : String target : String
@@ -101,12 +142,13 @@ pub struct AsyncLogger[S] {
last_error : Ref[String] last_error : Ref[String]
} }
///|
pub fn[S] async_logger( pub fn[S] async_logger(
sink : S, sink : S,
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(), config? : AsyncLoggerConfig = AsyncLoggerConfig::new(),
min_level~ : @bitlogger.Level = @bitlogger.Level::Info, min_level? : @bitlogger.Level = @bitlogger.Level::Info,
target~ : String = "", target? : String = "",
flush~ : (S) -> Int raise = fn(_) { 0 }, flush? : (S) -> Int raise = fn(_) { 0 },
) -> AsyncLogger[S] { ) -> AsyncLogger[S] {
{ {
min_level, min_level,
@@ -131,6 +173,7 @@ pub fn[S] async_logger(
} }
} }
///|
fn queue_kind_of(config : AsyncLoggerConfig) -> @aqueue.Kind { fn queue_kind_of(config : AsyncLoggerConfig) -> @aqueue.Kind {
let limit = if config.max_pending < 0 { 0 } else { config.max_pending } let limit = if config.max_pending < 0 { 0 } else { config.max_pending }
match config.overflow { match config.overflow {
@@ -140,14 +183,23 @@ fn queue_kind_of(config : AsyncLoggerConfig) -> @aqueue.Kind {
} }
} }
pub fn[S] AsyncLogger::with_timestamp(self : AsyncLogger[S], enabled~ : Bool = true) -> AsyncLogger[S] { ///|
pub fn[S] AsyncLogger::with_timestamp(
self : AsyncLogger[S],
enabled? : Bool = true,
) -> AsyncLogger[S] {
{ ..self, timestamp: enabled } { ..self, timestamp: enabled }
} }
pub fn[S] AsyncLogger::with_target(self : AsyncLogger[S], target : String) -> AsyncLogger[S] { ///|
{ ..self, target } pub fn[S] AsyncLogger::with_target(
self : AsyncLogger[S],
target : String,
) -> AsyncLogger[S] {
{ ..self, target, }
} }
///|
pub fn[S] AsyncLogger::with_context_fields( pub fn[S] AsyncLogger::with_context_fields(
self : AsyncLogger[S], self : AsyncLogger[S],
fields : Array[@bitlogger.Field], fields : Array[@bitlogger.Field],
@@ -155,39 +207,33 @@ pub fn[S] AsyncLogger::with_context_fields(
{ ..self, context_fields: fields } { ..self, context_fields: fields }
} }
///|
pub fn[S] AsyncLogger::with_filter( pub fn[S] AsyncLogger::with_filter(
self : AsyncLogger[S], self : AsyncLogger[S],
predicate : (@bitlogger.Record) -> Bool, predicate : (@bitlogger.Record) -> Bool,
) -> AsyncLogger[S] { ) -> AsyncLogger[S] {
let current = self.filter let current = self.filter
{ { ..self, filter: fn(rec) { current(rec) && predicate(rec) } }
..self,
filter: fn(rec) {
current(rec) && predicate(rec)
},
}
} }
///|
pub fn[S] AsyncLogger::with_patch( pub fn[S] AsyncLogger::with_patch(
self : AsyncLogger[S], self : AsyncLogger[S],
patch : @bitlogger.RecordPatch, patch : @bitlogger.RecordPatch,
) -> AsyncLogger[S] { ) -> AsyncLogger[S] {
let current = self.patch let current = self.patch
{ { ..self, patch: fn(rec) { patch(current(rec)) } }
..self,
patch: fn(rec) {
patch(current(rec))
},
}
} }
///|
pub fn[S] AsyncLogger::with_min_level( pub fn[S] AsyncLogger::with_min_level(
self : AsyncLogger[S], self : AsyncLogger[S],
min_level : @bitlogger.Level, min_level : @bitlogger.Level,
) -> AsyncLogger[S] { ) -> AsyncLogger[S] {
{ ..self, min_level } { ..self, min_level, }
} }
///|
fn combine_targets(parent : String, child : String) -> String { fn combine_targets(parent : String, child : String) -> String {
if parent == "" { if parent == "" {
child child
@@ -198,14 +244,23 @@ fn combine_targets(parent : String, child : String) -> String {
} }
} }
pub fn[S] AsyncLogger::child(self : AsyncLogger[S], target : String) -> AsyncLogger[S] { ///|
pub fn[S] AsyncLogger::child(
self : AsyncLogger[S],
target : String,
) -> AsyncLogger[S] {
{ ..self, target: combine_targets(self.target, target) } { ..self, target: combine_targets(self.target, target) }
} }
pub fn[S] AsyncLogger::is_enabled(self : AsyncLogger[S], level : @bitlogger.Level) -> Bool { ///|
pub fn[S] AsyncLogger::is_enabled(
self : AsyncLogger[S],
level : @bitlogger.Level,
) -> Bool {
level.enabled(self.min_level) level.enabled(self.min_level)
} }
///|
fn merge_fields( fn merge_fields(
left : Array[@bitlogger.Field], left : Array[@bitlogger.Field],
right : Array[@bitlogger.Field], right : Array[@bitlogger.Field],
@@ -219,32 +274,27 @@ fn merge_fields(
} }
} }
///|
pub async fn[S] AsyncLogger::log( pub async fn[S] AsyncLogger::log(
self : AsyncLogger[S], self : AsyncLogger[S],
level : @bitlogger.Level, level : @bitlogger.Level,
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
target? : String = "", target? : String = "",
) -> Unit { ) -> Unit {
guard !(async_runtime_guard_closed_on_log() && self.is_closed()) else { guard !(async_runtime_guard_closed_on_log() && self.is_closed()) else { () }
() guard self.is_enabled(level) else { () }
}
guard self.is_enabled(level) else {
()
}
let actual_target = if target == "" { self.target } else { target } let actual_target = if target == "" { self.target } else { target }
let timestamp_ms = if self.timestamp { @env.now() } else { 0UL } let timestamp_ms = if self.timestamp { @env.now() } else { 0UL }
let rec = @bitlogger.Record::new( let rec = @bitlogger.Record::new(
level, level,
message, message,
timestamp_ms=timestamp_ms, timestamp_ms~,
target=actual_target, target=actual_target,
fields=merge_fields(self.context_fields, fields), fields=merge_fields(self.context_fields, fields),
) )
let rec = (self.patch)(rec) let rec = (self.patch)(rec)
guard (self.filter)(rec) else { guard (self.filter)(rec) else { () }
()
}
let accepted = self.queue.try_put(rec) catch { let accepted = self.queue.try_put(rec) catch {
err if err is AsyncLoggerClosed => false err if err is AsyncLoggerClosed => false
err => raise err err => raise err
@@ -254,7 +304,7 @@ pub async fn[S] AsyncLogger::log(
} else { } else {
match self.overflow { match self.overflow {
AsyncOverflowPolicy::Blocking => { AsyncOverflowPolicy::Blocking => {
let accepted = (async fn() -> Bool raise { let accepted = (async fn() -> Bool {
self.queue.put(rec) self.queue.put(rec)
true true
})() catch { })() catch {
@@ -265,81 +315,93 @@ pub async fn[S] AsyncLogger::log(
self.pending_count.val += 1 self.pending_count.val += 1
} }
} }
AsyncOverflowPolicy::DropOldest | AsyncOverflowPolicy::DropNewest => { AsyncOverflowPolicy::DropOldest | AsyncOverflowPolicy::DropNewest =>
self.dropped_count.val += 1 self.dropped_count.val += 1
} }
} }
}
} }
///|
pub async fn[S] AsyncLogger::trace( pub async fn[S] AsyncLogger::trace(
self : AsyncLogger[S], self : AsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.log(@bitlogger.Level::Trace, message, fields=fields) self.log(@bitlogger.Level::Trace, message, fields~)
} }
///|
pub async fn[S] AsyncLogger::debug( pub async fn[S] AsyncLogger::debug(
self : AsyncLogger[S], self : AsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.log(@bitlogger.Level::Debug, message, fields=fields) self.log(@bitlogger.Level::Debug, message, fields~)
} }
///|
pub async fn[S] AsyncLogger::info( pub async fn[S] AsyncLogger::info(
self : AsyncLogger[S], self : AsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.log(@bitlogger.Level::Info, message, fields=fields) self.log(@bitlogger.Level::Info, message, fields~)
} }
///|
pub async fn[S] AsyncLogger::warn( pub async fn[S] AsyncLogger::warn(
self : AsyncLogger[S], self : AsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.log(@bitlogger.Level::Warn, message, fields=fields) self.log(@bitlogger.Level::Warn, message, fields~)
} }
///|
pub async fn[S] AsyncLogger::error( pub async fn[S] AsyncLogger::error(
self : AsyncLogger[S], self : AsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.log(@bitlogger.Level::Error, message, fields=fields) self.log(@bitlogger.Level::Error, message, fields~)
} }
///|
pub fn[S] AsyncLogger::pending_count(self : AsyncLogger[S]) -> Int { pub fn[S] AsyncLogger::pending_count(self : AsyncLogger[S]) -> Int {
self.pending_count.val self.pending_count.val
} }
///|
pub fn[S] AsyncLogger::dropped_count(self : AsyncLogger[S]) -> Int { pub fn[S] AsyncLogger::dropped_count(self : AsyncLogger[S]) -> Int {
self.dropped_count.val self.dropped_count.val
} }
///|
pub fn[S] AsyncLogger::is_closed(self : AsyncLogger[S]) -> Bool { pub fn[S] AsyncLogger::is_closed(self : AsyncLogger[S]) -> Bool {
self.is_closed.val self.is_closed.val
} }
///|
pub fn[S] AsyncLogger::is_running(self : AsyncLogger[S]) -> Bool { pub fn[S] AsyncLogger::is_running(self : AsyncLogger[S]) -> Bool {
self.is_running.val self.is_running.val
} }
///|
pub fn[S] AsyncLogger::has_failed(self : AsyncLogger[S]) -> Bool { pub fn[S] AsyncLogger::has_failed(self : AsyncLogger[S]) -> Bool {
self.has_failed.val self.has_failed.val
} }
///|
pub fn[S] AsyncLogger::last_error(self : AsyncLogger[S]) -> String { pub fn[S] AsyncLogger::last_error(self : AsyncLogger[S]) -> String {
self.last_error.val self.last_error.val
} }
///|
pub fn[S] AsyncLogger::flush_policy(self : AsyncLogger[S]) -> AsyncFlushPolicy { pub fn[S] AsyncLogger::flush_policy(self : AsyncLogger[S]) -> AsyncFlushPolicy {
self.flush_policy self.flush_policy
} }
///|
pub fn[S] AsyncLogger::state(self : AsyncLogger[S]) -> AsyncLoggerState { pub fn[S] AsyncLogger::state(self : AsyncLogger[S]) -> AsyncLoggerState {
AsyncLoggerState::new( AsyncLoggerState::new(
async_runtime_state(), async_runtime_state(),
@@ -353,7 +415,11 @@ pub fn[S] AsyncLogger::state(self : AsyncLogger[S]) -> AsyncLoggerState {
) )
} }
pub fn[S] AsyncLogger::close(self : AsyncLogger[S], clear? : Bool = false) -> Unit { ///|
pub fn[S] AsyncLogger::close(
self : AsyncLogger[S],
clear? : Bool = false,
) -> Unit {
self.is_closed.val = true self.is_closed.val = true
if clear { if clear {
let abandoned = self.pending_count() let abandoned = self.pending_count()
@@ -362,9 +428,10 @@ pub fn[S] AsyncLogger::close(self : AsyncLogger[S], clear? : Bool = false) -> Un
self.pending_count.val = 0 self.pending_count.val = 0
} }
} }
self.queue.close(error=AsyncLoggerClosed, clear=clear) self.queue.close(error=AsyncLoggerClosed, clear~)
} }
///|
pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit { pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit {
while self.pending_count() > 0 { while self.pending_count() > 0 {
if self.has_failed() { if self.has_failed() {
@@ -374,12 +441,17 @@ pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit {
} }
} }
pub async fn[S] AsyncLogger::shutdown(self : AsyncLogger[S], clear? : Bool = false) -> Unit { ///|
pub async fn[S] AsyncLogger::shutdown(
self : AsyncLogger[S],
clear? : Bool = false,
) -> Unit {
if clear { if clear {
self.close(clear=true) self.close(clear=true)
} else { } else {
self.wait_idle() self.wait_idle()
if async_runtime_shutdown_clears_pending_after_wait_idle() && self.pending_count() > 0 { if async_runtime_shutdown_clears_pending_after_wait_idle() &&
self.pending_count() > 0 {
self.close(clear=true) self.close(clear=true)
} else { } else {
self.close() self.close()
@@ -392,6 +464,7 @@ pub async fn[S] AsyncLogger::shutdown(self : AsyncLogger[S], clear? : Bool = fal
} }
} }
///|
async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit { async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit {
while true { while true {
let rec = logger.queue.get() catch { let rec = logger.queue.get() catch {
@@ -419,7 +492,9 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit {
if logger.linger_ms <= 0 { if logger.linger_ms <= 0 {
break break
} }
let waited = @async.with_timeout_opt(logger.linger_ms, () => logger.queue.get()) catch { let waited = @async.with_timeout_opt(logger.linger_ms, () => {
logger.queue.get()
}) catch {
err if err is AsyncLoggerClosed => None err if err is AsyncLoggerClosed => None
err => raise err err => raise err
} }
@@ -447,7 +522,10 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit {
} }
} }
pub async fn[S : @bitlogger.Sink] AsyncLogger::run(self : AsyncLogger[S]) -> Unit { ///|
pub async fn[S : @bitlogger.Sink] AsyncLogger::run(
self : AsyncLogger[S],
) -> Unit {
self.is_running.val = true self.is_running.val = true
self.has_failed.val = false self.has_failed.val = false
self.last_error.val = "" self.last_error.val = ""
@@ -462,6 +540,7 @@ pub async fn[S : @bitlogger.Sink] AsyncLogger::run(self : AsyncLogger[S]) -> Uni
self.is_running.val = false self.is_running.val = false
} }
///|
pub fn build_async_logger( pub fn build_async_logger(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> AsyncLogger[@bitlogger.RuntimeSink] { ) -> AsyncLogger[@bitlogger.RuntimeSink] {
@@ -475,9 +554,14 @@ pub fn build_async_logger(
).with_timestamp(enabled=logger.timestamp) ).with_timestamp(enabled=logger.timestamp)
} }
pub fn build_async_text_logger(config : AsyncLoggerBuildConfig) -> AsyncLogger[@bitlogger.FormattedConsoleSink] { ///|
pub fn build_async_text_logger(
config : AsyncLoggerBuildConfig,
) -> AsyncLogger[@bitlogger.FormattedConsoleSink] {
async_logger( async_logger(
@bitlogger.text_console_sink(config.logger.sink.text_formatter.to_formatter()), @bitlogger.text_console_sink(
config.logger.sink.text_formatter.to_formatter(),
),
config=config.async_config, config=config.async_config,
min_level=config.logger.min_level, min_level=config.logger.min_level,
target=config.logger.target, target=config.logger.target,
+5
View File
@@ -1,20 +1,25 @@
///|
pub fn async_runtime_mode() -> AsyncRuntimeMode { pub fn async_runtime_mode() -> AsyncRuntimeMode {
@utils.compatibility_async_runtime_mode() @utils.compatibility_async_runtime_mode()
} }
///|
pub fn async_runtime_supports_background_worker() -> Bool { pub fn async_runtime_supports_background_worker() -> Bool {
ignore(all_async_runtime_modes()) ignore(all_async_runtime_modes())
false false
} }
///|
fn async_runtime_guard_closed_on_log() -> Bool { fn async_runtime_guard_closed_on_log() -> Bool {
true true
} }
///|
fn async_runtime_shutdown_clears_pending_after_wait_idle() -> Bool { fn async_runtime_shutdown_clears_pending_after_wait_idle() -> Bool {
false false
} }
///|
fn async_runtime_shutdown_waits_for_worker() -> Bool { fn async_runtime_shutdown_waits_for_worker() -> Bool {
false false
} }
+43 -25
View File
@@ -1,61 +1,67 @@
///|
pub struct LibraryAsyncLogger[S] { pub struct LibraryAsyncLogger[S] {
inner : AsyncLogger[S] inner : AsyncLogger[S]
} }
///|
fn[S] library_async_logger(logger : AsyncLogger[S]) -> LibraryAsyncLogger[S] { fn[S] library_async_logger(logger : AsyncLogger[S]) -> LibraryAsyncLogger[S] {
{ inner: logger } { inner: logger }
} }
pub fn[S] AsyncLogger::to_library_async_logger(self : AsyncLogger[S]) -> LibraryAsyncLogger[S] { ///|
pub fn[S] AsyncLogger::to_library_async_logger(
self : AsyncLogger[S],
) -> LibraryAsyncLogger[S] {
library_async_logger(self) library_async_logger(self)
} }
///|
pub fn[S] LibraryAsyncLogger::new( pub fn[S] LibraryAsyncLogger::new(
sink : S, sink : S,
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(), config? : AsyncLoggerConfig = AsyncLoggerConfig::new(),
min_level~ : @bitlogger.Level = @bitlogger.Level::Info, min_level? : @bitlogger.Level = @bitlogger.Level::Info,
target~ : String = "", target? : String = "",
flush~ : (S) -> Int raise = fn(_) { 0 }, flush? : (S) -> Int raise = fn(_) { 0 },
) -> LibraryAsyncLogger[S] { ) -> LibraryAsyncLogger[S] {
library_async_logger( library_async_logger(async_logger(sink, config~, min_level~, target~, flush~))
async_logger(
sink,
config=config,
min_level=min_level,
target=target,
flush=flush,
),
)
} }
pub fn[S] LibraryAsyncLogger::to_async_logger(self : LibraryAsyncLogger[S]) -> AsyncLogger[S] { ///|
pub fn[S] LibraryAsyncLogger::to_async_logger(
self : LibraryAsyncLogger[S],
) -> AsyncLogger[S] {
self.inner self.inner
} }
///|
fn[S] configured_library_async_logger( fn[S] configured_library_async_logger(
logger : AsyncLogger[S], logger : AsyncLogger[S],
) -> LibraryAsyncLogger[S] { ) -> LibraryAsyncLogger[S] {
library_async_logger(logger) library_async_logger(logger)
} }
///|
pub fn build_library_async_logger( pub fn build_library_async_logger(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> LibraryAsyncLogger[@bitlogger.RuntimeSink] { ) -> LibraryAsyncLogger[@bitlogger.RuntimeSink] {
configured_library_async_logger(build_async_logger(config)) configured_library_async_logger(build_async_logger(config))
} }
///|
pub fn build_library_async_text_logger( pub fn build_library_async_text_logger(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> LibraryAsyncLogger[@bitlogger.FormattedConsoleSink] { ) -> LibraryAsyncLogger[@bitlogger.FormattedConsoleSink] {
configured_library_async_logger(build_async_text_logger(config)) configured_library_async_logger(build_async_text_logger(config))
} }
///|
pub fn parse_and_build_library_async_logger( pub fn parse_and_build_library_async_logger(
input : String, input : String,
) -> LibraryAsyncLogger[@bitlogger.RuntimeSink] raise { ) -> LibraryAsyncLogger[@bitlogger.RuntimeSink] raise {
build_library_async_logger(parse_async_logger_build_config_text(input)) build_library_async_logger(parse_async_logger_build_config_text(input))
} }
///|
pub fn[S] LibraryAsyncLogger::with_target( pub fn[S] LibraryAsyncLogger::with_target(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
target : String, target : String,
@@ -63,6 +69,7 @@ pub fn[S] LibraryAsyncLogger::with_target(
library_async_logger(self.inner.with_target(target)) library_async_logger(self.inner.with_target(target))
} }
///|
pub fn[S] LibraryAsyncLogger::child( pub fn[S] LibraryAsyncLogger::child(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
target : String, target : String,
@@ -70,6 +77,7 @@ pub fn[S] LibraryAsyncLogger::child(
library_async_logger(self.inner.child(target)) library_async_logger(self.inner.child(target))
} }
///|
pub fn[S] LibraryAsyncLogger::with_context_fields( pub fn[S] LibraryAsyncLogger::with_context_fields(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
fields : Array[@bitlogger.Field], fields : Array[@bitlogger.Field],
@@ -77,6 +85,7 @@ pub fn[S] LibraryAsyncLogger::with_context_fields(
library_async_logger(self.inner.with_context_fields(fields)) library_async_logger(self.inner.with_context_fields(fields))
} }
///|
pub fn[S] LibraryAsyncLogger::bind( pub fn[S] LibraryAsyncLogger::bind(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
fields : Array[@bitlogger.Field], fields : Array[@bitlogger.Field],
@@ -84,6 +93,7 @@ pub fn[S] LibraryAsyncLogger::bind(
self.with_context_fields(fields) self.with_context_fields(fields)
} }
///|
pub fn[S] LibraryAsyncLogger::is_enabled( pub fn[S] LibraryAsyncLogger::is_enabled(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
level : @bitlogger.Level, level : @bitlogger.Level,
@@ -91,47 +101,55 @@ pub fn[S] LibraryAsyncLogger::is_enabled(
self.inner.is_enabled(level) self.inner.is_enabled(level)
} }
///|
pub async fn[S] LibraryAsyncLogger::log( pub async fn[S] LibraryAsyncLogger::log(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
level : @bitlogger.Level, level : @bitlogger.Level,
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
target? : String = "", target? : String = "",
) -> Unit { ) -> Unit {
self.inner.log(level, message, fields=fields, target=target) self.inner.log(level, message, fields~, target~)
} }
///|
pub async fn[S] LibraryAsyncLogger::info( pub async fn[S] LibraryAsyncLogger::info(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.inner.info(message, fields=fields) self.inner.info(message, fields~)
} }
///|
pub async fn[S] LibraryAsyncLogger::warn( pub async fn[S] LibraryAsyncLogger::warn(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.inner.warn(message, fields=fields) self.inner.warn(message, fields~)
} }
///|
pub async fn[S] LibraryAsyncLogger::error( pub async fn[S] LibraryAsyncLogger::error(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
message : String, message : String,
fields~ : Array[@bitlogger.Field] = [], fields? : Array[@bitlogger.Field] = [],
) -> Unit { ) -> Unit {
self.inner.error(message, fields=fields) self.inner.error(message, fields~)
} }
pub async fn[S : @bitlogger.Sink] LibraryAsyncLogger::run(self : LibraryAsyncLogger[S]) -> Unit { ///|
pub async fn[S : @bitlogger.Sink] LibraryAsyncLogger::run(
self : LibraryAsyncLogger[S],
) -> Unit {
self.inner.run() self.inner.run()
} }
///|
pub async fn[S] LibraryAsyncLogger::shutdown( pub async fn[S] LibraryAsyncLogger::shutdown(
self : LibraryAsyncLogger[S], self : LibraryAsyncLogger[S],
clear? : Bool = false, clear? : Bool = false,
) -> Unit { ) -> Unit {
self.inner.shutdown(clear=clear) self.inner.shutdown(clear~)
} }
+12 -6
View File
@@ -1,17 +1,23 @@
import { import {
"Nanaloveyuki/BitLogger/src" @bitlogger, "Nanaloveyuki/BitLogger/src" @bitlogger,
"Nanaloveyuki/BitLogger/src-async/utils" @utils, "Nanaloveyuki/BitLogger/src-async/utils",
"maria/json_parser" @json_parser, "maria/json_parser",
"moonbitlang/async" @async, "moonbitlang/async",
"moonbitlang/async/aqueue" @aqueue, "moonbitlang/async/aqueue",
"moonbitlang/core/env" @env, "moonbitlang/core/env",
"moonbitlang/core/ref", "moonbitlang/core/ref",
} }
options( options(
targets: { targets: {
"async_logger_shared.mbt": [ "native", "llvm", "js", "wasm", "wasm-gc" ], "async_logger_shared.mbt": [ "native", "llvm", "js", "wasm", "wasm-gc" ],
"application_async_logger.mbt": [ "native", "llvm", "js", "wasm", "wasm-gc" ], "application_async_logger.mbt": [
"native",
"llvm",
"js",
"wasm",
"wasm-gc",
],
"library_async_logger.mbt": [ "native", "llvm", "js", "wasm", "wasm-gc" ], "library_async_logger.mbt": [ "native", "llvm", "js", "wasm", "wasm-gc" ],
"async_logger_native.mbt": [ "native", "llvm" ], "async_logger_native.mbt": [ "native", "llvm" ],
"async_logger_stub.mbt": [ "js", "wasm", "wasm-gc" ], "async_logger_stub.mbt": [ "js", "wasm", "wasm-gc" ],
+120 -44
View File
@@ -1,20 +1,24 @@
///|
pub(all) enum AsyncOverflowPolicy { pub(all) enum AsyncOverflowPolicy {
Blocking Blocking
DropOldest DropOldest
DropNewest DropNewest
} }
///|
pub(all) enum AsyncFlushPolicy { pub(all) enum AsyncFlushPolicy {
Never Never
Batch Batch
Shutdown Shutdown
} }
///|
pub enum AsyncRuntimeMode { pub enum AsyncRuntimeMode {
NativeWorker NativeWorker
Compatibility Compatibility
} }
///|
pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String { pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String {
match mode { match mode {
AsyncRuntimeMode::NativeWorker => "native_worker" AsyncRuntimeMode::NativeWorker => "native_worker"
@@ -22,19 +26,23 @@ pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String {
} }
} }
///|
pub fn native_worker_async_runtime_mode() -> AsyncRuntimeMode { pub fn native_worker_async_runtime_mode() -> AsyncRuntimeMode {
AsyncRuntimeMode::NativeWorker AsyncRuntimeMode::NativeWorker
} }
///|
pub fn compatibility_async_runtime_mode() -> AsyncRuntimeMode { pub fn compatibility_async_runtime_mode() -> AsyncRuntimeMode {
AsyncRuntimeMode::Compatibility AsyncRuntimeMode::Compatibility
} }
///|
pub struct AsyncRuntimeState { pub struct AsyncRuntimeState {
mode : AsyncRuntimeMode mode : AsyncRuntimeMode
background_worker : Bool background_worker : Bool
} }
///|
pub fn AsyncRuntimeState::new( pub fn AsyncRuntimeState::new(
mode : AsyncRuntimeMode, mode : AsyncRuntimeMode,
background_worker : Bool, background_worker : Bool,
@@ -42,6 +50,7 @@ pub fn AsyncRuntimeState::new(
{ mode, background_worker } { mode, background_worker }
} }
///|
pub struct AsyncLoggerState { pub struct AsyncLoggerState {
runtime : AsyncRuntimeState runtime : AsyncRuntimeState
pending_count : Int pending_count : Int
@@ -53,6 +62,7 @@ pub struct AsyncLoggerState {
flush_policy : AsyncFlushPolicy flush_policy : AsyncFlushPolicy
} }
///|
pub fn AsyncLoggerState::new( pub fn AsyncLoggerState::new(
runtime : AsyncRuntimeState, runtime : AsyncRuntimeState,
pending_count : Int, pending_count : Int,
@@ -75,16 +85,20 @@ pub fn AsyncLoggerState::new(
} }
} }
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> @json_parser.JsonValue { ///|
pub fn async_runtime_state_to_json(
state : AsyncRuntimeState,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({ @json_parser.JsonValue::Object({
"mode": @json_parser.JsonValue::String(async_runtime_mode_label(state.mode)), "mode": @json_parser.JsonValue::String(async_runtime_mode_label(state.mode)),
"background_worker": @json_parser.JsonValue::Bool(state.background_worker), "background_worker": @json_parser.JsonValue::Bool(state.background_worker),
}) })
} }
///|
pub fn stringify_async_runtime_state( pub fn stringify_async_runtime_state(
state : AsyncRuntimeState, state : AsyncRuntimeState,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
let value = async_runtime_state_to_json(state) let value = async_runtime_state_to_json(state)
if pretty { if pretty {
@@ -94,6 +108,7 @@ pub fn stringify_async_runtime_state(
} }
} }
///|
fn async_flush_policy_label(policy : AsyncFlushPolicy) -> String { fn async_flush_policy_label(policy : AsyncFlushPolicy) -> String {
match policy { match policy {
AsyncFlushPolicy::Never => "Never" AsyncFlushPolicy::Never => "Never"
@@ -102,26 +117,39 @@ fn async_flush_policy_label(policy : AsyncFlushPolicy) -> String {
} }
} }
fn async_logger_state_to_json_value(state : AsyncLoggerState) -> @json_parser.JsonValue { ///|
fn async_logger_state_to_json_value(
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({ @json_parser.JsonValue::Object({
"runtime": async_runtime_state_to_json(state.runtime), "runtime": async_runtime_state_to_json(state.runtime),
"pending_count": @json_parser.JsonValue::Number(state.pending_count.to_double()), "pending_count": @json_parser.JsonValue::Number(
"dropped_count": @json_parser.JsonValue::Number(state.dropped_count.to_double()), state.pending_count.to_double(),
),
"dropped_count": @json_parser.JsonValue::Number(
state.dropped_count.to_double(),
),
"is_closed": @json_parser.JsonValue::Bool(state.is_closed), "is_closed": @json_parser.JsonValue::Bool(state.is_closed),
"is_running": @json_parser.JsonValue::Bool(state.is_running), "is_running": @json_parser.JsonValue::Bool(state.is_running),
"has_failed": @json_parser.JsonValue::Bool(state.has_failed), "has_failed": @json_parser.JsonValue::Bool(state.has_failed),
"last_error": @json_parser.JsonValue::String(state.last_error), "last_error": @json_parser.JsonValue::String(state.last_error),
"flush_policy": @json_parser.JsonValue::String(async_flush_policy_label(state.flush_policy)), "flush_policy": @json_parser.JsonValue::String(
async_flush_policy_label(state.flush_policy),
),
}) })
} }
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.JsonValue { ///|
pub fn async_logger_state_to_json(
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
async_logger_state_to_json_value(state) async_logger_state_to_json_value(state)
} }
///|
pub fn stringify_async_logger_state( pub fn stringify_async_logger_state(
state : AsyncLoggerState, state : AsyncLoggerState,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
let value = async_logger_state_to_json_value(state) let value = async_logger_state_to_json_value(state)
if pretty { if pretty {
@@ -131,6 +159,7 @@ pub fn stringify_async_logger_state(
} }
} }
///|
pub struct AsyncLoggerConfig { pub struct AsyncLoggerConfig {
max_pending : Int max_pending : Int
overflow : AsyncOverflowPolicy overflow : AsyncOverflowPolicy
@@ -139,22 +168,32 @@ pub struct AsyncLoggerConfig {
flush : AsyncFlushPolicy flush : AsyncFlushPolicy
} }
///|
pub fn AsyncLoggerConfig::new( pub fn AsyncLoggerConfig::new(
max_pending~ : Int = 0, max_pending? : Int = 0,
overflow~ : AsyncOverflowPolicy = AsyncOverflowPolicy::Blocking, overflow? : AsyncOverflowPolicy = AsyncOverflowPolicy::Blocking,
max_batch~ : Int = 1, max_batch? : Int = 1,
linger_ms~ : Int = 0, linger_ms? : Int = 0,
flush~ : AsyncFlushPolicy = AsyncFlushPolicy::Never, flush? : AsyncFlushPolicy = AsyncFlushPolicy::Never,
) -> AsyncLoggerConfig { ) -> AsyncLoggerConfig {
{ {
max_pending, max_pending,
overflow, overflow,
max_batch: if max_batch <= 1 { 1 } else { max_batch }, max_batch: if max_batch <= 1 {
linger_ms: if linger_ms < 0 { 0 } else { linger_ms }, 1
} else {
max_batch
},
linger_ms: if linger_ms < 0 {
0
} else {
linger_ms
},
flush, flush,
} }
} }
///|
fn parse_async_overflow(name : String) -> AsyncOverflowPolicy raise { fn parse_async_overflow(name : String) -> AsyncOverflowPolicy raise {
match name.to_upper() { match name.to_upper() {
"BLOCKING" => AsyncOverflowPolicy::Blocking "BLOCKING" => AsyncOverflowPolicy::Blocking
@@ -165,6 +204,7 @@ fn parse_async_overflow(name : String) -> AsyncOverflowPolicy raise {
} }
} }
///|
fn parse_async_flush(name : String) -> AsyncFlushPolicy raise { fn parse_async_flush(name : String) -> AsyncFlushPolicy raise {
match name.to_upper() { match name.to_upper() {
"NEVER" => AsyncFlushPolicy::Never "NEVER" => AsyncFlushPolicy::Never
@@ -175,75 +215,100 @@ fn parse_async_flush(name : String) -> AsyncFlushPolicy raise {
} }
} }
pub fn parse_async_logger_config_text(input : String) -> AsyncLoggerConfig raise { ///|
pub fn parse_async_logger_config_text(
input : String,
) -> AsyncLoggerConfig raise {
let root = @json_parser.parse(input) let root = @json_parser.parse(input)
let obj = match root.as_object() { let obj = match root.as_object() {
Some(obj) => obj Some(obj) => obj
None => raise Failure::Failure("Expected object for async logger config") None => raise Failure::Failure("Expected object for async logger config")
} }
let max_pending = match obj.get("max_pending") { let max_pending = match obj.get("max_pending") {
Some(value) => match value.as_number() { Some(value) =>
match value.as_number() {
Some(number) => number.to_int() Some(number) => number.to_int()
None => raise Failure::Failure("Expected number at async_config.max_pending") None =>
raise Failure::Failure("Expected number at async_config.max_pending")
} }
None => 0 None => 0
} }
let overflow = match obj.get("overflow") { let overflow = match obj.get("overflow") {
Some(value) => match value.as_string() { Some(value) =>
match value.as_string() {
Some(text) => parse_async_overflow(text) Some(text) => parse_async_overflow(text)
None => raise Failure::Failure("Expected string at async_config.overflow") None =>
raise Failure::Failure("Expected string at async_config.overflow")
} }
None => AsyncOverflowPolicy::Blocking None => AsyncOverflowPolicy::Blocking
} }
let max_batch = match obj.get("max_batch") { let max_batch = match obj.get("max_batch") {
Some(value) => match value.as_number() { Some(value) =>
match value.as_number() {
Some(number) => number.to_int() Some(number) => number.to_int()
None => raise Failure::Failure("Expected number at async_config.max_batch") None =>
raise Failure::Failure("Expected number at async_config.max_batch")
} }
None => 1 None => 1
} }
let linger_ms = match obj.get("linger_ms") { let linger_ms = match obj.get("linger_ms") {
Some(value) => match value.as_number() { Some(value) =>
match value.as_number() {
Some(number) => number.to_int() Some(number) => number.to_int()
None => raise Failure::Failure("Expected number at async_config.linger_ms") None =>
raise Failure::Failure("Expected number at async_config.linger_ms")
} }
None => 0 None => 0
} }
let flush = match obj.get("flush") { let flush = match obj.get("flush") {
Some(value) => match value.as_string() { Some(value) =>
match value.as_string() {
Some(text) => parse_async_flush(text) Some(text) => parse_async_flush(text)
None => raise Failure::Failure("Expected string at async_config.flush") None => raise Failure::Failure("Expected string at async_config.flush")
} }
None => AsyncFlushPolicy::Never None => AsyncFlushPolicy::Never
} }
AsyncLoggerConfig::new( AsyncLoggerConfig::new(
max_pending=max_pending, max_pending~,
overflow=overflow, overflow~,
max_batch=max_batch, max_batch~,
linger_ms=linger_ms, linger_ms~,
flush=flush, flush~,
) )
} }
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> @json_parser.JsonValue { ///|
pub fn async_logger_config_to_json(
config : AsyncLoggerConfig,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({ @json_parser.JsonValue::Object({
"max_pending": @json_parser.JsonValue::Number(config.max_pending.to_double()), "max_pending": @json_parser.JsonValue::Number(
config.max_pending.to_double(),
),
"max_batch": @json_parser.JsonValue::Number(config.max_batch.to_double()), "max_batch": @json_parser.JsonValue::Number(config.max_batch.to_double()),
"linger_ms": @json_parser.JsonValue::Number(config.linger_ms.to_double()), "linger_ms": @json_parser.JsonValue::Number(config.linger_ms.to_double()),
"overflow": @json_parser.JsonValue::String(match config.overflow { "overflow": @json_parser.JsonValue::String(
match config.overflow {
AsyncOverflowPolicy::Blocking => "Blocking" AsyncOverflowPolicy::Blocking => "Blocking"
AsyncOverflowPolicy::DropOldest => "DropOldest" AsyncOverflowPolicy::DropOldest => "DropOldest"
AsyncOverflowPolicy::DropNewest => "DropNewest" AsyncOverflowPolicy::DropNewest => "DropNewest"
}), },
"flush": @json_parser.JsonValue::String(match config.flush { ),
"flush": @json_parser.JsonValue::String(
match config.flush {
AsyncFlushPolicy::Never => "Never" AsyncFlushPolicy::Never => "Never"
AsyncFlushPolicy::Batch => "Batch" AsyncFlushPolicy::Batch => "Batch"
AsyncFlushPolicy::Shutdown => "Shutdown" AsyncFlushPolicy::Shutdown => "Shutdown"
}), },
),
}) })
} }
pub fn stringify_async_logger_config(config : AsyncLoggerConfig, pretty~ : Bool = false) -> String { ///|
pub fn stringify_async_logger_config(
config : AsyncLoggerConfig,
pretty? : Bool = false,
) -> String {
let value = async_logger_config_to_json(config) let value = async_logger_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) @json_parser.stringify_pretty(value, 2)
@@ -252,35 +317,45 @@ pub fn stringify_async_logger_config(config : AsyncLoggerConfig, pretty~ : Bool
} }
} }
///|
pub struct AsyncLoggerBuildConfig { pub struct AsyncLoggerBuildConfig {
logger : @bitlogger.LoggerConfig logger : @bitlogger.LoggerConfig
async_config : AsyncLoggerConfig async_config : AsyncLoggerConfig
} }
///|
pub fn AsyncLoggerBuildConfig::new( pub fn AsyncLoggerBuildConfig::new(
logger~ : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(), logger? : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(),
async_config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(), async_config? : AsyncLoggerConfig = AsyncLoggerConfig::new(),
) -> AsyncLoggerBuildConfig { ) -> AsyncLoggerBuildConfig {
{ logger, async_config } { logger, async_config }
} }
pub fn parse_async_logger_build_config_text(input : String) -> AsyncLoggerBuildConfig raise { ///|
pub fn parse_async_logger_build_config_text(
input : String,
) -> AsyncLoggerBuildConfig raise {
let root = @json_parser.parse(input) let root = @json_parser.parse(input)
let obj = match root.as_object() { let obj = match root.as_object() {
Some(obj) => obj Some(obj) => obj
None => raise Failure::Failure("Expected object at async logger build config root") None =>
raise Failure::Failure(
"Expected object at async logger build config root",
)
} }
let logger = match obj.get("logger") { let logger = match obj.get("logger") {
Some(value) => @bitlogger.parse_logger_config_text(@json_parser.stringify(value)) Some(value) =>
@bitlogger.parse_logger_config_text(@json_parser.stringify(value))
None => @bitlogger.default_logger_config() None => @bitlogger.default_logger_config()
} }
let async_config = match obj.get("async_config") { let async_config = match obj.get("async_config") {
Some(value) => parse_async_logger_config_text(@json_parser.stringify(value)) Some(value) => parse_async_logger_config_text(@json_parser.stringify(value))
None => AsyncLoggerConfig::new() None => AsyncLoggerConfig::new()
} }
AsyncLoggerBuildConfig::new(logger=logger, async_config=async_config) AsyncLoggerBuildConfig::new(logger~, async_config~)
} }
///|
pub fn async_logger_build_config_to_json( pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
) -> @json_parser.JsonValue { ) -> @json_parser.JsonValue {
@@ -290,9 +365,10 @@ pub fn async_logger_build_config_to_json(
}) })
} }
///|
pub fn stringify_async_logger_build_config( pub fn stringify_async_logger_build_config(
config : AsyncLoggerBuildConfig, config : AsyncLoggerBuildConfig,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
let value = async_logger_build_config_to_json(config) let value = async_logger_build_config_to_json(config)
if pretty { if pretty {
+1 -1
View File
@@ -1,4 +1,4 @@
import { import {
"Nanaloveyuki/BitLogger/src" @bitlogger, "Nanaloveyuki/BitLogger/src" @bitlogger,
"maria/json_parser" @json_parser, "maria/json_parser",
} }
+1646 -424
View File
File diff suppressed because it is too large Load Diff
+247 -132
View File
@@ -1,3 +1,4 @@
///|
test "default logger can be reconfigured" { test "default logger can be reconfigured" {
set_default_min_level(Level::Debug) set_default_min_level(Level::Debug)
set_default_target("global") set_default_target("global")
@@ -6,6 +7,7 @@ test "default logger can be reconfigured" {
inspect(logger.target, content="global") inspect(logger.target, content="global")
} }
///|
test "default logger snapshots shared defaults at creation time" { test "default logger snapshots shared defaults at creation time" {
set_default_min_level(Level::Warn) set_default_min_level(Level::Warn)
set_default_target("before") set_default_target("before")
@@ -24,24 +26,30 @@ test "default logger snapshots shared defaults at creation time" {
inspect(after.target, content="after") inspect(after.target, content="after")
} }
///|
test "logger can enable timestamps" { test "logger can enable timestamps" {
let logger = Logger::new(console_sink(), min_level=Level::Info, target="time") let logger = Logger::new(console_sink(), min_level=Level::Info, target="time").with_timestamp()
.with_timestamp()
inspect(logger.timestamp, content="true") inspect(logger.timestamp, content="true")
} }
///|
test "text formatter can customize visible parts" { test "text formatter can customize visible parts" {
let rec = record( let rec = record(Level::Info, "hello", timestamp_ms=123UL, target="svc.api", fields=[
Level::Info, field("user", "alice"),
"hello", field("request_id", "42"),
timestamp_ms=123UL, ])
target="svc.api", let compact = text_formatter(
fields=[field("user", "alice"), field("request_id", "42")], show_timestamp=false,
show_target=false,
field_separator=",",
)
inspect(
format_text(rec, formatter=compact),
content="[INFO] hello user=alice,request_id=42",
) )
let compact = text_formatter(show_timestamp=false, show_target=false, field_separator=",")
inspect(format_text(rec, formatter=compact), content="[INFO] hello user=alice,request_id=42")
} }
///|
test "text formatter can emit message only" { test "text formatter can emit message only" {
let rec = record( let rec = record(
Level::Warn, Level::Warn,
@@ -59,6 +67,7 @@ test "text formatter can emit message only" {
inspect(format_text(rec, formatter=message_only), content="just message") inspect(format_text(rec, formatter=message_only), content="just message")
} }
///|
test "text formatter supports template rendering" { test "text formatter supports template rendering" {
let rec = record( let rec = record(
Level::Info, Level::Info,
@@ -73,20 +82,22 @@ test "text formatter supports template rendering" {
template="[{level}] {target} {message} :: {fields} @{timestamp}", template="[{level}] {target} {message} :: {fields} @{timestamp}",
) )
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="[INFO] svc.api template hello :: user=alice,request_id=42 @123", content="[INFO] svc.api template hello :: user=alice,request_id=42 @123",
) )
} }
///|
test "text formatter can render ansi level colors" { test "text formatter can render ansi level colors" {
let rec = record(Level::Error, "boom", target="svc") let rec = record(Level::Error, "boom", target="svc")
let formatter = text_formatter(color_mode=ColorMode::Always) let formatter = text_formatter(color_mode=ColorMode::Always)
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="[\u{001b}[31;1mERROR\u{001b}[0m] [\u{001b}[34msvc\u{001b}[0m] boom", content="[\u{001b}[31;1mERROR\u{001b}[0m] [\u{001b}[34msvc\u{001b}[0m] boom",
) )
} }
///|
test "text formatter auto color respects NO_COLOR" { test "text formatter auto color respects NO_COLOR" {
let rec = record(Level::Warn, "boom", target="svc") let rec = record(Level::Warn, "boom", target="svc")
let previous = @env.get_env_var("NO_COLOR") let previous = @env.get_env_var("NO_COLOR")
@@ -101,6 +112,7 @@ test "text formatter auto color respects NO_COLOR" {
} }
} }
///|
test "text formatter renders named inline color tags in ansi mode" { test "text formatter renders named inline color tags in ansi mode" {
let rec = record(Level::Info, "<red>boom</>", target="svc") let rec = record(Level::Info, "<red>boom</>", target="svc")
inspect( inspect(
@@ -109,6 +121,7 @@ test "text formatter renders named inline color tags in ansi mode" {
) )
} }
///|
test "text formatter strips inline tags in plain mode" { test "text formatter strips inline tags in plain mode" {
let rec = record(Level::Info, "<red>boom</> <b>bold</>", target="svc") let rec = record(Level::Info, "<red>boom</> <b>bold</>", target="svc")
inspect( inspect(
@@ -117,46 +130,83 @@ test "text formatter strips inline tags in plain mode" {
) )
} }
///|
test "text formatter supports nested inline tags" { test "text formatter supports nested inline tags" {
let rec = record(Level::Info, "<red><b>fatal</></>") let rec = record(Level::Info, "<red><b>fatal</></>")
inspect( inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)), format_text(
rec,
formatter=text_formatter(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
),
),
content="\u{001b}[31;1mfatal\u{001b}[0m", content="\u{001b}[31;1mfatal\u{001b}[0m",
) )
} }
///|
test "text formatter supports named closing tags" { test "text formatter supports named closing tags" {
let rec = record(Level::Info, "<red>boom</red>") let rec = record(Level::Info, "<red>boom</red>")
inspect( inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)), format_text(
rec,
formatter=text_formatter(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
),
),
content="\u{001b}[31mboom\u{001b}[0m", content="\u{001b}[31mboom\u{001b}[0m",
) )
} }
///|
test "text formatter supports mixed short and named closing tags" { test "text formatter supports mixed short and named closing tags" {
let rec = record(Level::Info, "<red><b>fatal</b></red>") let rec = record(Level::Info, "<red><b>fatal</b></red>")
inspect( inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)), format_text(
rec,
formatter=text_formatter(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
),
),
content="\u{001b}[31;1mfatal\u{001b}[0m", content="\u{001b}[31;1mfatal\u{001b}[0m",
) )
} }
///|
test "text formatter keeps unmatched named closing tags as plain text" { test "text formatter keeps unmatched named closing tags as plain text" {
let rec = record(Level::Info, "boom</red>") let rec = record(Level::Info, "boom</red>")
inspect( inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false)), format_text(
rec,
formatter=text_formatter(show_level=false, show_target=false),
),
content="boom</red>", content="boom</red>",
) )
} }
///|
test "text formatter supports hex inline colors" { test "text formatter supports hex inline colors" {
let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>") let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>")
inspect( inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)), format_text(
rec,
formatter=text_formatter(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
),
),
content="\u{001b}[38;2;255;0;0mhot\u{001b}[0m \u{001b}[48;2;1;2;3mbg\u{001b}[0m", content="\u{001b}[38;2;255;0;0mhot\u{001b}[0m \u{001b}[48;2;1;2;3mbg\u{001b}[0m",
) )
} }
///|
test "text formatter can downgrade hex colors to basic ansi" { test "text formatter can downgrade hex colors to basic ansi" {
let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>") let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>")
inspect( inspect(
@@ -173,14 +223,19 @@ test "text formatter can downgrade hex colors to basic ansi" {
) )
} }
///|
test "text formatter keeps unknown inline tags as plain text" { test "text formatter keeps unknown inline tags as plain text" {
let rec = record(Level::Info, "<unknown>boom</>") let rec = record(Level::Info, "<unknown>boom</>")
inspect( inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false)), format_text(
rec,
formatter=text_formatter(show_level=false, show_target=false),
),
content="<unknown>boom</>", content="<unknown>boom</>",
) )
} }
///|
test "text formatter can disable style markup parsing" { test "text formatter can disable style markup parsing" {
let rec = record(Level::Info, "<red>boom</> <b>bold</>", target="svc") let rec = record(Level::Info, "<red>boom</> <b>bold</>", target="svc")
inspect( inspect(
@@ -192,6 +247,7 @@ test "text formatter can disable style markup parsing" {
) )
} }
///|
test "text formatter builtin style markup mode ignores custom tags" { test "text formatter builtin style markup mode ignores custom tags" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
@@ -203,17 +259,22 @@ test "text formatter builtin style markup mode ignores custom tags" {
) )
let rec = record(Level::Info, "<brand>custom</> <red>builtin</>") let rec = record(Level::Info, "<brand>custom</> <red>builtin</>")
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="<brand>custom</> \u{001b}[31mbuiltin\u{001b}[0m", content="<brand>custom</> \u{001b}[31mbuiltin\u{001b}[0m",
) )
} }
///|
test "style markup mode label is stable" { test "style markup mode label is stable" {
inspect(style_markup_mode_label(StyleMarkupMode::Disabled), content="disabled") inspect(
style_markup_mode_label(StyleMarkupMode::Disabled),
content="disabled",
)
inspect(style_markup_mode_label(StyleMarkupMode::Builtin), content="builtin") inspect(style_markup_mode_label(StyleMarkupMode::Builtin), content="builtin")
inspect(style_markup_mode_label(StyleMarkupMode::Full), content="full") inspect(style_markup_mode_label(StyleMarkupMode::Full), content="full")
} }
///|
test "text formatter supports custom style tags" { test "text formatter supports custom style tags" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
@@ -224,83 +285,103 @@ test "text formatter supports custom style tags" {
) )
let rec = record(Level::Info, "<accent>api</>") let rec = record(Level::Info, "<accent>api</>")
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="\u{001b}[38;2;76;201;240;1mapi\u{001b}[0m", content="\u{001b}[38;2;76;201;240;1mapi\u{001b}[0m",
) )
} }
///|
test "text formatter can override builtin style tags" { test "text formatter can override builtin style tags" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
show_target=false, show_target=false,
color_mode=ColorMode::Always, color_mode=ColorMode::Always,
).with_style_tags( ).with_style_tags(
default_style_tag_registry().set_tag("red", fg=Some("#ff5a5f"), underline=true), default_style_tag_registry().set_tag(
"red",
fg=Some("#ff5a5f"),
underline=true,
),
) )
let rec = record(Level::Info, "<red>alert</>") let rec = record(Level::Info, "<red>alert</>")
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="\u{001b}[38;2;255;90;95;4malert\u{001b}[0m", content="\u{001b}[38;2;255;90;95;4malert\u{001b}[0m",
) )
} }
///|
test "formatter style tags take priority over global style tags" { test "formatter style tags take priority over global style tags" {
let previous = global_style_tag_registry() let previous = global_style_tag_registry()
set_global_style_tag_registry(style_tag_registry().set_tag("accent", fg=Some("#123456"))) set_global_style_tag_registry(
style_tag_registry().set_tag("accent", fg=Some("#123456")),
)
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
show_target=false, show_target=false,
color_mode=ColorMode::Always, color_mode=ColorMode::Always,
).with_style_tags( ).with_style_tags(style_tag_registry().set_tag("accent", fg=Some("#abcdef")))
style_tag_registry().set_tag("accent", fg=Some("#abcdef")),
)
let rec = record(Level::Info, "<accent>tag</>") let rec = record(Level::Info, "<accent>tag</>")
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="\u{001b}[38;2;171;205;239mtag\u{001b}[0m", content="\u{001b}[38;2;171;205;239mtag\u{001b}[0m",
) )
set_global_style_tag_registry(previous) set_global_style_tag_registry(previous)
} }
///|
test "global style tags apply when formatter has no local registry" { test "global style tags apply when formatter has no local registry" {
let previous = global_style_tag_registry() let previous = global_style_tag_registry()
set_global_style_tag_registry(style_tag_registry().set_tag("accent", fg=Some("#102030"), dim=true)) set_global_style_tag_registry(
style_tag_registry().set_tag("accent", fg=Some("#102030"), dim=true),
)
let rec = record(Level::Info, "<accent>tag</>") let rec = record(Level::Info, "<accent>tag</>")
inspect( inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)), format_text(
rec,
formatter=text_formatter(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
),
),
content="\u{001b}[38;2;16;32;48;2mtag\u{001b}[0m", content="\u{001b}[38;2;16;32;48;2mtag\u{001b}[0m",
) )
set_global_style_tag_registry(previous) set_global_style_tag_registry(previous)
} }
///|
test "reset global style tag registry restores builtin defaults" { test "reset global style tag registry restores builtin defaults" {
set_global_style_tag_registry(style_tag_registry().set_tag("accent", fg=Some("#123456"), bold=true)) set_global_style_tag_registry(
style_tag_registry().set_tag("accent", fg=Some("#123456"), bold=true),
)
reset_global_style_tag_registry() reset_global_style_tag_registry()
let rec = record(Level::Info, "<success>ok</>") let rec = record(Level::Info, "<success>ok</>")
inspect( inspect(
format_text( format_text(
rec, rec,
formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always), formatter=text_formatter(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
),
), ),
content="\u{001b}[32;1mok\u{001b}[0m", content="\u{001b}[32;1mok\u{001b}[0m",
) )
} }
///|
test "style tag alias can reuse builtin tags" { test "style tag alias can reuse builtin tags" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
show_target=false, show_target=false,
color_mode=ColorMode::Always, color_mode=ColorMode::Always,
).with_style_tags( ).with_style_tags(style_tag_registry().define_alias("danger", "red"))
style_tag_registry().define_alias("danger", "red"),
)
let rec = record(Level::Info, "<danger>boom</>") let rec = record(Level::Info, "<danger>boom</>")
inspect( inspect(format_text(rec, formatter~), content="\u{001b}[31mboom\u{001b}[0m")
format_text(rec, formatter=formatter),
content="\u{001b}[31mboom\u{001b}[0m",
)
} }
///|
test "builtin semantic style tags are available" { test "builtin semantic style tags are available" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
@@ -308,28 +389,37 @@ test "builtin semantic style tags are available" {
color_mode=ColorMode::Always, color_mode=ColorMode::Always,
style_markup=StyleMarkupMode::Builtin, style_markup=StyleMarkupMode::Builtin,
) )
let rec = record(Level::Info, "<success>ok</> <warning>careful</> <danger>boom</> <muted>quiet</>") let rec = record(
Level::Info,
"<success>ok</> <warning>careful</> <danger>boom</> <muted>quiet</>",
)
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="\u{001b}[32;1mok\u{001b}[0m \u{001b}[33;1mcareful\u{001b}[0m \u{001b}[31;1mboom\u{001b}[0m \u{001b}[90;2mquiet\u{001b}[0m", content="\u{001b}[32;1mok\u{001b}[0m \u{001b}[33;1mcareful\u{001b}[0m \u{001b}[31;1mboom\u{001b}[0m \u{001b}[90;2mquiet\u{001b}[0m",
) )
} }
///|
test "builtin semantic style tags can still be overridden" { test "builtin semantic style tags can still be overridden" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
show_target=false, show_target=false,
color_mode=ColorMode::Always, color_mode=ColorMode::Always,
).with_style_tags( ).with_style_tags(
default_style_tag_registry().set_tag("success", fg=Some("#00ffaa"), underline=true), default_style_tag_registry().set_tag(
"success",
fg=Some("#00ffaa"),
underline=true,
),
) )
let rec = record(Level::Info, "<success>ok</>") let rec = record(Level::Info, "<success>ok</>")
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="\u{001b}[38;2;0;255;170;4mok\u{001b}[0m", content="\u{001b}[38;2;0;255;170;4mok\u{001b}[0m",
) )
} }
///|
test "text formatter can enable markup for target separately" { test "text formatter can enable markup for target separately" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
@@ -338,11 +428,12 @@ test "text formatter can enable markup for target separately" {
) )
let rec = record(Level::Info, "hello", target="<danger>svc</>") let rec = record(Level::Info, "hello", target="<danger>svc</>")
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="[\u{001b}[34m\u{001b}[31;1msvc\u{001b}[0m\u{001b}[0m] hello", content="[\u{001b}[34m\u{001b}[31;1msvc\u{001b}[0m\u{001b}[0m] hello",
) )
} }
///|
test "text formatter can enable markup for field values separately" { test "text formatter can enable markup for field values separately" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
@@ -350,13 +441,16 @@ test "text formatter can enable markup for field values separately" {
color_mode=ColorMode::Always, color_mode=ColorMode::Always,
fields_style_markup=StyleMarkupMode::Builtin, fields_style_markup=StyleMarkupMode::Builtin,
) )
let rec = record(Level::Info, "hello", fields=[field("status", "<success>ok</>")]) let rec = record(Level::Info, "hello", fields=[
field("status", "<success>ok</>"),
])
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="hello \u{001b}[35mstatus=\u{001b}[32;1mok\u{001b}[0m\u{001b}[0m", content="hello \u{001b}[35mstatus=\u{001b}[32;1mok\u{001b}[0m\u{001b}[0m",
) )
} }
///|
test "text formatter leaves field keys raw when field markup is enabled" { test "text formatter leaves field keys raw when field markup is enabled" {
let formatter = text_formatter( let formatter = text_formatter(
show_level=false, show_level=false,
@@ -364,13 +458,16 @@ test "text formatter leaves field keys raw when field markup is enabled" {
color_mode=ColorMode::Always, color_mode=ColorMode::Always,
fields_style_markup=StyleMarkupMode::Builtin, fields_style_markup=StyleMarkupMode::Builtin,
) )
let rec = record(Level::Info, "hello", fields=[field("<danger>status</>", "ok")]) let rec = record(Level::Info, "hello", fields=[
field("<danger>status</>", "ok"),
])
inspect( inspect(
format_text(rec, formatter=formatter), format_text(rec, formatter~),
content="hello \u{001b}[35m<danger>status</>=ok\u{001b}[0m", content="hello \u{001b}[35m<danger>status</>=ok\u{001b}[0m",
) )
} }
///|
test "text formatter template respects disabled fields" { test "text formatter template respects disabled fields" {
let rec = record(Level::Warn, "just message", target="svc") let rec = record(Level::Warn, "just message", target="svc")
let formatter = text_formatter( let formatter = text_formatter(
@@ -378,26 +475,30 @@ test "text formatter template respects disabled fields" {
show_fields=false, show_fields=false,
template="[{level}] {target}{message}{fields}", template="[{level}] {target}{message}{fields}",
) )
inspect(format_text(rec, formatter=formatter), content="[WARN] just message") inspect(format_text(rec, formatter~), content="[WARN] just message")
} }
///|
test "formatted callback sink receives rendered text" { test "formatted callback sink receives rendered text" {
let rendered : Ref[String] = Ref("") let rendered : Ref[String] = Ref("")
let sink = text_callback_sink( let sink = text_callback_sink(
text_formatter(show_timestamp=false, separator=" | "), text_formatter(show_timestamp=false, separator=" | "),
fn(text) { fn(text) { rendered.val = text },
rendered.val = text
},
) )
let logger = Logger::new(sink, min_level=Level::Info, target="svc") let logger = Logger::new(sink, min_level=Level::Info, target="svc")
logger.info("hello", fields=[field("user", "alice")]) logger.info("hello", fields=[field("user", "alice")])
inspect(rendered.val, content="[INFO] | [svc] | hello | user=alice") inspect(rendered.val, content="[INFO] | [svc] | hello | user=alice")
} }
///|
test "native file support flag is queryable" { test "native file support flag is queryable" {
inspect(native_files_supported() == true || native_files_supported() == false, content="true") inspect(
native_files_supported() == true || native_files_supported() == false,
content="true",
)
} }
///|
test "file sink availability reflects backend support" { test "file sink availability reflects backend support" {
let sink = file_sink("bitlogger-test.log") let sink = file_sink("bitlogger-test.log")
let state = sink.state() let state = sink.state()
@@ -407,12 +508,18 @@ test "file sink availability reflects backend support" {
inspect(state.available == sink.is_available(), content="true") inspect(state.available == sink.is_available(), content="true")
inspect(state.append == sink.append_mode(), content="true") inspect(state.append == sink.append_mode(), content="true")
inspect(state.auto_flush == sink.auto_flush_enabled(), content="true") inspect(state.auto_flush == sink.auto_flush_enabled(), content="true")
inspect((state.rotation is None) == (sink.rotation_config() is None), content="true") inspect(
(state.rotation is None) == (sink.rotation_config() is None),
content="true",
)
inspect(sink.append_mode(), content="true") inspect(sink.append_mode(), content="true")
inspect(sink.auto_flush_enabled(), content="true") inspect(sink.auto_flush_enabled(), content="true")
inspect(sink.rotation_enabled(), content="false") inspect(sink.rotation_enabled(), content="false")
inspect(sink.rotation_config() is None, content="true") inspect(sink.rotation_config() is None, content="true")
inspect(sink.open_failures(), content=if sink.is_available() { "0" } else { "1" }) inspect(
sink.open_failures(),
content=if sink.is_available() { "0" } else { "1" },
)
inspect(sink.write_failures(), content="0") inspect(sink.write_failures(), content="0")
inspect(sink.flush_failures(), content="0") inspect(sink.flush_failures(), content="0")
if sink.is_available() { if sink.is_available() {
@@ -424,8 +531,12 @@ test "file sink availability reflects backend support" {
} }
} }
///|
test "file sink unavailable backend keeps stable fallback state" { test "file sink unavailable backend keeps stable fallback state" {
let sink = file_sink("bitlogger-unavailable.log", rotation=Some(file_rotation(64, max_backups=2))) let sink = file_sink(
"bitlogger-unavailable.log",
rotation=Some(file_rotation(64, max_backups=2)),
)
if sink.is_available() { if sink.is_available() {
inspect(sink.state().available, content="true") inspect(sink.state().available, content="true")
ignore(sink.close()) ignore(sink.close())
@@ -449,6 +560,7 @@ test "file sink unavailable backend keeps stable fallback state" {
} }
} }
///|
test "file sink rotation config normalizes invalid inputs" { test "file sink rotation config normalizes invalid inputs" {
let rotation = file_rotation(0, max_backups=0) let rotation = file_rotation(0, max_backups=0)
inspect(rotation.max_bytes, content="1") inspect(rotation.max_bytes, content="1")
@@ -464,6 +576,7 @@ test "file sink rotation config normalizes invalid inputs" {
} }
} }
///|
test "file sink setters update auto flush and rotation state" { test "file sink setters update auto flush and rotation state" {
let sink = file_sink("bitlogger-setters.log") let sink = file_sink("bitlogger-setters.log")
let default_policy = sink.default_policy() let default_policy = sink.default_policy()
@@ -490,7 +603,10 @@ test "file sink setters update auto flush and rotation state" {
sink.clear_rotation() sink.clear_rotation()
inspect(sink.rotation_enabled(), content="false") inspect(sink.rotation_enabled(), content="false")
inspect(sink.rotation_config() is None, content="true") inspect(sink.rotation_config() is None, content="true")
inspect(sink.reopen(), content=if sink.is_available() { "true" } else { "false" }) inspect(
sink.reopen(),
content=if sink.is_available() { "true" } else { "false" },
)
inspect(sink.append_mode(), content="false") inspect(sink.append_mode(), content="false")
let state = sink.state() let state = sink.state()
let policy = sink.policy() let policy = sink.policy()
@@ -508,6 +624,7 @@ test "file sink setters update auto flush and rotation state" {
inspect(sink.policy_matches_default(), content="true") inspect(sink.policy_matches_default(), content="true")
} }
///|
test "file sink reset policy restores configured defaults" { test "file sink reset policy restores configured defaults" {
let sink = file_sink( let sink = file_sink(
"bitlogger-reset-policy.log", "bitlogger-reset-policy.log",
@@ -535,6 +652,7 @@ test "file sink reset policy restores configured defaults" {
} }
} }
///|
test "file sink set policy applies bundled runtime policy" { test "file sink set policy applies bundled runtime policy" {
let sink = file_sink("bitlogger-set-policy.log") let sink = file_sink("bitlogger-set-policy.log")
let default_policy = sink.default_policy() let default_policy = sink.default_policy()
@@ -561,6 +679,7 @@ test "file sink set policy applies bundled runtime policy" {
inspect(sink.policy_matches_default(), content="false") inspect(sink.policy_matches_default(), content="false")
} }
///|
test "file sink tracks rotation failures on unavailable backend" { test "file sink tracks rotation failures on unavailable backend" {
let path = "bitlogger-rotate.log" let path = "bitlogger-rotate.log"
ignore(remove_file_internal(path)) ignore(remove_file_internal(path))
@@ -579,6 +698,7 @@ test "file sink tracks rotation failures on unavailable backend" {
} }
} }
///|
test "file sink reopen and failure counters reflect backend state" { test "file sink reopen and failure counters reflect backend state" {
let sink = file_sink("bitlogger-reopen.log") let sink = file_sink("bitlogger-reopen.log")
if sink.is_available() { if sink.is_available() {
@@ -630,6 +750,7 @@ test "file sink reopen and failure counters reflect backend state" {
} }
} }
///|
test "file sink can rotate on native backend" { test "file sink can rotate on native backend" {
let path = "bitlogger-rotate-native.log" let path = "bitlogger-rotate-native.log"
ignore(remove_file_internal(path)) ignore(remove_file_internal(path))
@@ -666,20 +787,18 @@ test "file sink can rotate on native backend" {
} }
} }
///|
test "json formatter keeps structured shape" { test "json formatter keeps structured shape" {
let rec = record( let rec = record(Level::Error, "failed", timestamp_ms=55UL, target="svc", fields=[
Level::Error, field("code", "500"),
"failed", ])
timestamp_ms=55UL,
target="svc",
fields=[field("code", "500")],
)
inspect( inspect(
format_json(rec), format_json(rec),
content="{\"level\":\"ERROR\",\"message\":\"failed\",\"fields\":{\"code\":\"500\"},\"timestamp_ms\":\"55\",\"target\":\"svc\"}", content="{\"level\":\"ERROR\",\"message\":\"failed\",\"fields\":{\"code\":\"500\"},\"timestamp_ms\":\"55\",\"target\":\"svc\"}",
) )
} }
///|
test "callback sink receives record" { test "callback sink receives record" {
let captured_target : Ref[String] = Ref("") let captured_target : Ref[String] = Ref("")
let captured_message : Ref[String] = Ref("") let captured_message : Ref[String] = Ref("")
@@ -696,20 +815,15 @@ test "callback sink receives record" {
inspect(captured_message.val, content="hello") inspect(captured_message.val, content="hello")
} }
///|
test "split sink routes records by predicate" { test "split sink routes records by predicate" {
let left_messages : Ref[Array[String]] = Ref([]) let left_messages : Ref[Array[String]] = Ref([])
let right_messages : Ref[Array[String]] = Ref([]) let right_messages : Ref[Array[String]] = Ref([])
let logger = Logger::new( let logger = Logger::new(
split_sink( split_sink(
callback_sink(fn(rec) { callback_sink(fn(rec) { left_messages.val.push(rec.message) }),
left_messages.val.push(rec.message) callback_sink(fn(rec) { right_messages.val.push(rec.message) }),
}), fn(rec) { rec.target == "audit" },
callback_sink(fn(rec) {
right_messages.val.push(rec.message)
}),
fn(rec) {
rec.target == "audit"
},
), ),
min_level=Level::Info, min_level=Level::Info,
target="main", target="main",
@@ -722,6 +836,7 @@ test "split sink routes records by predicate" {
inspect(right_messages.val[0], content="drop to right") inspect(right_messages.val[0], content="drop to right")
} }
///|
test "split_by_level routes warn and error separately" { test "split_by_level routes warn and error separately" {
let high_messages : Ref[Array[String]] = Ref([]) let high_messages : Ref[Array[String]] = Ref([])
let low_messages : Ref[Array[String]] = Ref([]) let low_messages : Ref[Array[String]] = Ref([])
@@ -748,6 +863,7 @@ test "split_by_level routes warn and error separately" {
inspect(low_messages.val[0], content="INFO:info") inspect(low_messages.val[0], content="INFO:info")
} }
///|
test "callback sink sees child target and context logger shape" { test "callback sink sees child target and context logger shape" {
let captured_target : Ref[String] = Ref("") let captured_target : Ref[String] = Ref("")
let captured_message : Ref[String] = Ref("") let captured_message : Ref[String] = Ref("")
@@ -773,6 +889,7 @@ test "callback sink sees child target and context logger shape" {
inspect(captured_timestamp.val > 0UL, content="true") inspect(captured_timestamp.val > 0UL, content="true")
} }
///|
test "bind aliases context fields ergonomically" { test "bind aliases context fields ergonomically" {
let captured_target : Ref[String] = Ref("") let captured_target : Ref[String] = Ref("")
let captured_message : Ref[String] = Ref("") let captured_message : Ref[String] = Ref("")
@@ -798,12 +915,11 @@ test "bind aliases context fields ergonomically" {
inspect(captured_fields.val[2].value, content="test") inspect(captured_fields.val[2].value, content="test")
} }
///|
test "buffered sink flushes manually" { test "buffered sink flushes manually" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let sink = buffered_sink( let sink = buffered_sink(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
flush_limit=10, flush_limit=10,
) )
let logger = Logger::new(sink, min_level=Level::Info, target="buffered") let logger = Logger::new(sink, min_level=Level::Info, target="buffered")
@@ -818,12 +934,11 @@ test "buffered sink flushes manually" {
inspect(flushed_messages.val[1], content="two") inspect(flushed_messages.val[1], content="two")
} }
///|
test "buffered sink flushes automatically at limit" { test "buffered sink flushes automatically at limit" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let sink = buffered_sink( let sink = buffered_sink(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
flush_limit=2, flush_limit=2,
) )
let logger = Logger::new(sink, min_level=Level::Info, target="buffered") let logger = Logger::new(sink, min_level=Level::Info, target="buffered")
@@ -836,15 +951,12 @@ test "buffered sink flushes automatically at limit" {
inspect(flushed_messages.val[1], content="two") inspect(flushed_messages.val[1], content="two")
} }
///|
test "filter sink only forwards matching records" { test "filter sink only forwards matching records" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let sink = filter_sink( let sink = filter_sink(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message) fn(rec) { rec.target == "kept" },
}),
fn(rec) {
rec.target == "kept"
},
) )
let kept = Logger::new(sink, min_level=Level::Info, target="kept") let kept = Logger::new(sink, min_level=Level::Info, target="kept")
let dropped = Logger::new(sink, min_level=Level::Info, target="dropped") let dropped = Logger::new(sink, min_level=Level::Info, target="dropped")
@@ -856,37 +968,34 @@ test "filter sink only forwards matching records" {
inspect(flushed_messages.val[1], content="three") inspect(flushed_messages.val[1], content="three")
} }
///|
test "logger with_filter composes naturally" { test "logger with_filter composes naturally" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let logger = Logger::new( let logger = Logger::new(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
min_level=Level::Info, min_level=Level::Info,
target="app", target="app",
) ).with_filter(fn(rec) { rec.target == "app.worker" })
.with_filter(fn(rec) {
rec.target == "app.worker"
})
logger.info("drop at app") logger.info("drop at app")
logger.child("worker").info("keep at worker") logger.child("worker").info("keep at worker")
inspect(flushed_messages.val.length(), content="1") inspect(flushed_messages.val.length(), content="1")
inspect(flushed_messages.val[0], content="keep at worker") inspect(flushed_messages.val[0], content="keep at worker")
} }
///|
test "filter helpers support target level and message composition" { test "filter helpers support target level and message composition" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let logger = Logger::new( let logger = Logger::new(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
min_level=Level::Trace, min_level=Level::Trace,
target="service", target="service",
).with_filter(all_of([ ).with_filter(
all_of([
target_has_prefix("service"), target_has_prefix("service"),
level_at_least(Level::Info), level_at_least(Level::Info),
message_contains("visible"), message_contains("visible"),
])) ]),
)
logger.debug("visible debug") logger.debug("visible debug")
logger.info("hidden info") logger.info("hidden info")
logger.child("api").info("visible info") logger.child("api").info("visible info")
@@ -894,39 +1003,46 @@ test "filter helpers support target level and message composition" {
inspect(flushed_messages.val[0], content="visible info") inspect(flushed_messages.val[0], content="visible info")
} }
///|
test "field helpers can match and negate records" { test "field helpers can match and negate records" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let logger = Logger::new( let logger = Logger::new(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
min_level=Level::Info, min_level=Level::Info,
target="fields", target="fields",
).with_filter(all_of([ ).with_filter(
all_of([
has_field("request_id"), has_field("request_id"),
field_equals("kind", "audit"), field_equals("kind", "audit"),
not_(target_is("fields.drop")), not_(target_is("fields.drop")),
])) ]),
)
logger.info("missing field") logger.info("missing field")
logger.info("wrong kind", fields=[field("request_id", "1"), field("kind", "trace")]) logger.info("wrong kind", fields=[
logger.child("drop").info("blocked target", fields=[field("request_id", "2"), field("kind", "audit")]) field("request_id", "1"),
field("kind", "trace"),
])
logger
.child("drop")
.info("blocked target", fields=[
field("request_id", "2"),
field("kind", "audit"),
])
logger.info("kept", fields=[field("request_id", "3"), field("kind", "audit")]) logger.info("kept", fields=[field("request_id", "3"), field("kind", "audit")])
inspect(flushed_messages.val.length(), content="1") inspect(flushed_messages.val.length(), content="1")
inspect(flushed_messages.val[0], content="kept") inspect(flushed_messages.val[0], content="kept")
} }
///|
test "any_of helper accepts multiple predicates" { test "any_of helper accepts multiple predicates" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let logger = Logger::new( let logger = Logger::new(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
min_level=Level::Info, min_level=Level::Info,
target="multi", target="multi",
).with_filter(any_of([ ).with_filter(
target_is("multi.keep"), any_of([target_is("multi.keep"), field_equals("force", "true")]),
field_equals("force", "true"), )
]))
logger.info("drop") logger.info("drop")
logger.child("keep").info("keep by target") logger.child("keep").info("keep by target")
logger.info("keep by field", fields=[field("force", "true")]) logger.info("keep by field", fields=[field("force", "true")])
@@ -935,6 +1051,7 @@ test "any_of helper accepts multiple predicates" {
inspect(flushed_messages.val[1], content="keep by field") inspect(flushed_messages.val[1], content="keep by field")
} }
///|
test "patch sink can rewrite message target and fields" { test "patch sink can rewrite message target and fields" {
let captured_target : Ref[String] = Ref("") let captured_target : Ref[String] = Ref("")
let captured_message : Ref[String] = Ref("") let captured_message : Ref[String] = Ref("")
@@ -947,12 +1064,14 @@ test "patch sink can rewrite message target and fields" {
}), }),
min_level=Level::Info, min_level=Level::Info,
target="auth", target="auth",
).with_patch(compose_patches([ ).with_patch(
compose_patches([
set_target("audit.auth"), set_target("audit.auth"),
prefix_message("[safe] "), prefix_message("[safe] "),
redact_field("token"), redact_field("token"),
append_fields([field("service", "bitlogger")]), append_fields([field("service", "bitlogger")]),
])) ]),
)
logger.info("login", fields=[field("token", "secret"), field("user", "alice")]) logger.info("login", fields=[field("token", "secret"), field("user", "alice")])
inspect(captured_target.val, content="audit.auth") inspect(captured_target.val, content="audit.auth")
inspect(captured_message.val, content="[safe] login") inspect(captured_message.val, content="[safe] login")
@@ -965,31 +1084,30 @@ test "patch sink can rewrite message target and fields" {
inspect(captured_fields.val[2].value, content="bitlogger") inspect(captured_fields.val[2].value, content="bitlogger")
} }
///|
test "patch helpers can redact multiple fields" { test "patch helpers can redact multiple fields" {
let captured_fields : Ref[Array[Field]] = Ref([]) let captured_fields : Ref[Array[Field]] = Ref([])
let logger = Logger::new( let logger = Logger::new(
callback_sink(fn(rec) { callback_sink(fn(rec) { captured_fields.val = rec.fields }),
captured_fields.val = rec.fields
}),
min_level=Level::Info, min_level=Level::Info,
target="audit", target="audit",
).with_patch(redact_fields(["token", "password"], placeholder="[redacted]")) ).with_patch(redact_fields(["token", "password"], placeholder="[redacted]"))
logger.info( logger.info("credentials", fields=[
"credentials", field("token", "abc"),
fields=[field("token", "abc"), field("password", "123"), field("user", "alice")], field("password", "123"),
) field("user", "alice"),
])
inspect(captured_fields.val.length(), content="3") inspect(captured_fields.val.length(), content="3")
inspect(captured_fields.val[0].value, content="[redacted]") inspect(captured_fields.val[0].value, content="[redacted]")
inspect(captured_fields.val[1].value, content="[redacted]") inspect(captured_fields.val[1].value, content="[redacted]")
inspect(captured_fields.val[2].value, content="alice") inspect(captured_fields.val[2].value, content="alice")
} }
///|
test "queued sink drains in order" { test "queued sink drains in order" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let sink = queued_sink( let sink = queued_sink(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
) )
let logger = Logger::new(sink, min_level=Level::Info, target="queue") let logger = Logger::new(sink, min_level=Level::Info, target="queue")
logger.info("one") logger.info("one")
@@ -1007,12 +1125,11 @@ test "queued sink drains in order" {
inspect(flushed_messages.val[2], content="three") inspect(flushed_messages.val[2], content="three")
} }
///|
test "queued sink can drop newest when full" { test "queued sink can drop newest when full" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let sink = queued_sink( let sink = queued_sink(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
max_pending=2, max_pending=2,
overflow=QueueOverflowPolicy::DropNewest, overflow=QueueOverflowPolicy::DropNewest,
) )
@@ -1028,12 +1145,11 @@ test "queued sink can drop newest when full" {
inspect(flushed_messages.val[1], content="two") inspect(flushed_messages.val[1], content="two")
} }
///|
test "queued sink can drop oldest when full" { test "queued sink can drop oldest when full" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let sink = queued_sink( let sink = queued_sink(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
max_pending=2, max_pending=2,
overflow=QueueOverflowPolicy::DropOldest, overflow=QueueOverflowPolicy::DropOldest,
) )
@@ -1049,12 +1165,11 @@ test "queued sink can drop oldest when full" {
inspect(flushed_messages.val[1], content="three") inspect(flushed_messages.val[1], content="three")
} }
///|
test "logger with_queue preserves chaining ergonomics" { test "logger with_queue preserves chaining ergonomics" {
let flushed_messages : Ref[Array[String]] = Ref([]) let flushed_messages : Ref[Array[String]] = Ref([])
let logger = Logger::new( let logger = Logger::new(
callback_sink(fn(rec) { callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
flushed_messages.val.push(rec.message)
}),
min_level=Level::Info, min_level=Level::Info,
target="service", target="service",
) )
+8 -2
View File
@@ -19,13 +19,17 @@ BitLogger 是一个使用 MoonBit 编写的结构化日志库.
## Example / 示例 ## Example / 示例
```moonbit ```moonbit nocheck
///|
test { test {
let logger = build_logger( let logger = build_logger(
text_console( text_console(
min_level=Level::Debug, min_level=Level::Debug,
target="demo", target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "), text_formatter=TextFormatterConfig::new(
show_timestamp=false,
separator=" | ",
),
), ),
) )
logger.info("starting", fields=[field("port", "8080")]) logger.info("starting", fields=[field("port", "8080")])
@@ -76,7 +80,9 @@ Project command note / 项目命令说明:
- `console_basic` / `text_formatter` / `style_tags` / `config_build` / `presets` are the portable-first set - `console_basic` / `text_formatter` / `style_tags` / `config_build` / `presets` are the portable-first set
- `console_basic` / `text_formatter` / `style_tags` / `config_build` / `presets` 是 portable-first 示例集 - `console_basic` / `text_formatter` / `style_tags` / `config_build` / `presets` 是 portable-first 示例集
- `file_rotation` is native-sensitive, and `async_basic` stays native-only because of the current `async fn main` entry limitation - `file_rotation` is native-sensitive, and `async_basic` stays native-only because of the current `async fn main` entry limitation
- `async_basic` currently relies on `moonbitlang/async`, whose upstream README only claims native/LLVM support on Linux/MacOS; do not treat Windows native as a supported baseline for this example until upstream support is stated clearly
- `file_rotation` 是 native-sensitive`async_basic` 的 native-only 限制来自当前 `async fn main` 入口能力 - `file_rotation` 是 native-sensitive`async_basic` 的 native-only 限制来自当前 `async fn main` 入口能力
- `async_basic` 当前依赖 `moonbitlang/async`,其上游 README 目前只明确承诺 Linux/MacOS 的 native/LLVM 支持;在上游明确支持之前,不要把 Windows native 当作这个示例的支持基线
- package-level API docs / 单接口 API 文档: - package-level API docs / 单接口 API 文档:
- `../docs/api/` - `../docs/api/`
- common entry points / 常用入口: - common entry points / 常用入口:
+3
View File
@@ -1,9 +1,12 @@
///|
pub type ApplicationLogger = ConfiguredLogger pub type ApplicationLogger = ConfiguredLogger
///|
pub fn build_application_logger(config : LoggerConfig) -> ApplicationLogger { pub fn build_application_logger(config : LoggerConfig) -> ApplicationLogger {
build_logger(config) build_logger(config)
} }
///|
pub fn parse_and_build_application_logger( pub fn parse_and_build_application_logger(
input : String, input : String,
) -> ApplicationLogger raise ConfigError { ) -> ApplicationLogger raise ConfigError {
+45 -11
View File
@@ -1,66 +1,100 @@
///|
pub type ConfigError = @utils.ConfigError pub type ConfigError = @utils.ConfigError
///|
pub type SinkKind = @utils.SinkKind pub type SinkKind = @utils.SinkKind
///|
pub type TextFormatterConfig = @utils.TextFormatterConfig pub type TextFormatterConfig = @utils.TextFormatterConfig
///|
pub type QueueConfig = @utils.QueueConfig pub type QueueConfig = @utils.QueueConfig
///|
pub type SinkConfig = @utils.SinkConfig pub type SinkConfig = @utils.SinkConfig
///|
pub type LoggerConfig = @utils.LoggerConfig pub type LoggerConfig = @utils.LoggerConfig
///|
pub fn default_text_formatter_config() -> TextFormatterConfig { pub fn default_text_formatter_config() -> TextFormatterConfig {
@utils.default_text_formatter_config() @utils.default_text_formatter_config()
} }
///|
pub fn default_sink_config() -> SinkConfig { pub fn default_sink_config() -> SinkConfig {
@utils.default_sink_config() @utils.default_sink_config()
} }
///|
pub fn default_logger_config() -> LoggerConfig { pub fn default_logger_config() -> LoggerConfig {
@utils.default_logger_config() @utils.default_logger_config()
} }
pub fn parse_logger_config_text(input : String) -> LoggerConfig raise ConfigError { ///|
pub fn parse_logger_config_text(
input : String,
) -> LoggerConfig raise ConfigError {
@utils.parse_logger_config_text(input) @utils.parse_logger_config_text(input)
} }
///|
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue { pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
@utils.queue_config_to_json(queue) @utils.queue_config_to_json(queue)
} }
pub fn stringify_queue_config(queue : QueueConfig, pretty~ : Bool = false) -> String { ///|
@utils.stringify_queue_config(queue, pretty=pretty) pub fn stringify_queue_config(
queue : QueueConfig,
pretty? : Bool = false,
) -> String {
@utils.stringify_queue_config(queue, pretty~)
} }
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_parser.JsonValue { ///|
pub fn text_formatter_config_to_json(
config : TextFormatterConfig,
) -> @json_parser.JsonValue {
@utils.text_formatter_config_to_json(config) @utils.text_formatter_config_to_json(config)
} }
///|
pub fn stringify_text_formatter_config( pub fn stringify_text_formatter_config(
config : TextFormatterConfig, config : TextFormatterConfig,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
@utils.stringify_text_formatter_config(config, pretty=pretty) @utils.stringify_text_formatter_config(config, pretty~)
} }
pub fn file_rotation_config_to_json(config : FileRotation) -> @json_parser.JsonValue { ///|
pub fn file_rotation_config_to_json(
config : FileRotation,
) -> @json_parser.JsonValue {
@utils.file_rotation_config_to_json(config) @utils.file_rotation_config_to_json(config)
} }
///|
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue { pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
@utils.sink_config_to_json(config) @utils.sink_config_to_json(config)
} }
pub fn stringify_sink_config(config : SinkConfig, pretty~ : Bool = false) -> String { ///|
@utils.stringify_sink_config(config, pretty=pretty) pub fn stringify_sink_config(
config : SinkConfig,
pretty? : Bool = false,
) -> String {
@utils.stringify_sink_config(config, pretty~)
} }
///|
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue { pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
@utils.logger_config_to_json(config) @utils.logger_config_to_json(config)
} }
pub fn stringify_logger_config(config : LoggerConfig, pretty~ : Bool = false) -> String { ///|
@utils.stringify_logger_config(config, pretty=pretty) pub fn stringify_logger_config(
config : LoggerConfig,
pretty? : Bool = false,
) -> String {
@utils.stringify_logger_config(config, pretty~)
} }
+4
View File
@@ -1,3 +1,4 @@
///|
pub(all) enum Level { pub(all) enum Level {
Trace Trace
Debug Debug
@@ -6,6 +7,7 @@ pub(all) enum Level {
Error Error
} }
///|
pub fn Level::priority(self : Level) -> Int { pub fn Level::priority(self : Level) -> Int {
match self { match self {
Level::Trace => 10 Level::Trace => 10
@@ -16,6 +18,7 @@ pub fn Level::priority(self : Level) -> Int {
} }
} }
///|
pub fn Level::label(self : Level) -> String { pub fn Level::label(self : Level) -> String {
match self { match self {
Level::Trace => "TRACE" Level::Trace => "TRACE"
@@ -26,6 +29,7 @@ pub fn Level::label(self : Level) -> String {
} }
} }
///|
pub fn Level::enabled(self : Level, min_level : Level) -> Bool { pub fn Level::enabled(self : Level, min_level : Level) -> Bool {
self.priority() >= min_level.priority() self.priority() >= min_level.priority()
} }
+16 -8
View File
@@ -1,18 +1,20 @@
///|
pub struct Field { pub struct Field {
key : String key : String
value : String value : String
} }
///|
pub fn field(key : String, value : String) -> Field { pub fn field(key : String, value : String) -> Field {
{ key, value } { key, value }
} }
///|
pub fn fields(entries : Array[(String, String)]) -> Array[Field] { pub fn fields(entries : Array[(String, String)]) -> Array[Field] {
entries.map(fn(entry) { entries.map(fn(entry) { field(entry.0, entry.1) })
field(entry.0, entry.1)
})
} }
///|
pub struct Record { pub struct Record {
level : Level level : Level
timestamp_ms : UInt64 timestamp_ms : UInt64
@@ -21,30 +23,34 @@ pub struct Record {
fields : Array[Field] fields : Array[Field]
} }
///|
pub fn Record::new( pub fn Record::new(
level : Level, level : Level,
message : String, message : String,
timestamp_ms~ : UInt64 = 0UL, timestamp_ms? : UInt64 = 0UL,
target~ : String = "", target? : String = "",
fields~ : Array[Field] = [], fields? : Array[Field] = [],
) -> Record { ) -> Record {
{ level, timestamp_ms, target, message, fields } { level, timestamp_ms, target, message, fields }
} }
///|
pub fn Field::with_value(self : Field, value : String) -> Field { pub fn Field::with_value(self : Field, value : String) -> Field {
field(self.key, value) field(self.key, value)
} }
///|
pub fn Record::with_target(self : Record, target : String) -> Record { pub fn Record::with_target(self : Record, target : String) -> Record {
Record::new( Record::new(
self.level, self.level,
self.message, self.message,
timestamp_ms=self.timestamp_ms, timestamp_ms=self.timestamp_ms,
target=target, target~,
fields=self.fields, fields=self.fields,
) )
} }
///|
pub fn Record::with_message(self : Record, message : String) -> Record { pub fn Record::with_message(self : Record, message : String) -> Record {
Record::new( Record::new(
self.level, self.level,
@@ -55,16 +61,18 @@ pub fn Record::with_message(self : Record, message : String) -> Record {
) )
} }
///|
pub fn Record::with_fields(self : Record, fields : Array[Field]) -> Record { pub fn Record::with_fields(self : Record, fields : Array[Field]) -> Record {
Record::new( Record::new(
self.level, self.level,
self.message, self.message,
timestamp_ms=self.timestamp_ms, timestamp_ms=self.timestamp_ms,
target=self.target, target=self.target,
fields=fields, fields~,
) )
} }
///|
pub fn Record::copy(self : Record) -> Record { pub fn Record::copy(self : Record) -> Record {
Record::new( Record::new(
self.level, self.level,
+10
View File
@@ -1,37 +1,47 @@
///|
type FileHandle = @utils.FileHandle type FileHandle = @utils.FileHandle
///|
fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? { fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
@utils.open_file_handle_internal(path, append) @utils.open_file_handle_internal(path, append)
} }
///|
fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool { fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool {
@utils.write_file_handle_internal(handle, content) @utils.write_file_handle_internal(handle, content)
} }
///|
fn flush_file_handle_internal(handle : FileHandle) -> Bool { fn flush_file_handle_internal(handle : FileHandle) -> Bool {
@utils.flush_file_handle_internal(handle) @utils.flush_file_handle_internal(handle)
} }
///|
fn close_file_handle_internal(handle : FileHandle) -> Bool { fn close_file_handle_internal(handle : FileHandle) -> Bool {
@utils.close_file_handle_internal(handle) @utils.close_file_handle_internal(handle)
} }
///|
fn file_size_internal(handle : FileHandle) -> Int { fn file_size_internal(handle : FileHandle) -> Int {
@utils.file_size_internal(handle) @utils.file_size_internal(handle)
} }
///|
fn rename_file_internal(from_path : String, to_path : String) -> Bool { fn rename_file_internal(from_path : String, to_path : String) -> Bool {
@utils.rename_file_internal(from_path, to_path) @utils.rename_file_internal(from_path, to_path)
} }
///|
fn remove_file_internal(path : String) -> Bool { fn remove_file_internal(path : String) -> Bool {
@utils.remove_file_internal(path) @utils.remove_file_internal(path)
} }
///|
fn string_byte_length_internal(content : String) -> Int { fn string_byte_length_internal(content : String) -> Int {
@utils.string_byte_length_internal(content) @utils.string_byte_length_internal(content)
} }
///|
fn native_files_supported_internal() -> Bool { fn native_files_supported_internal() -> Bool {
@utils.native_files_supported_internal() @utils.native_files_supported_internal()
} }
+10
View File
@@ -1,37 +1,47 @@
///|
type FileHandle = @utils.FileHandle type FileHandle = @utils.FileHandle
///|
fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? { fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
@utils.open_file_handle_internal(path, append) @utils.open_file_handle_internal(path, append)
} }
///|
fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool { fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool {
@utils.write_file_handle_internal(handle, content) @utils.write_file_handle_internal(handle, content)
} }
///|
fn flush_file_handle_internal(handle : FileHandle) -> Bool { fn flush_file_handle_internal(handle : FileHandle) -> Bool {
@utils.flush_file_handle_internal(handle) @utils.flush_file_handle_internal(handle)
} }
///|
fn close_file_handle_internal(handle : FileHandle) -> Bool { fn close_file_handle_internal(handle : FileHandle) -> Bool {
@utils.close_file_handle_internal(handle) @utils.close_file_handle_internal(handle)
} }
///|
fn file_size_internal(handle : FileHandle) -> Int { fn file_size_internal(handle : FileHandle) -> Int {
@utils.file_size_internal(handle) @utils.file_size_internal(handle)
} }
///|
fn rename_file_internal(from_path : String, to_path : String) -> Bool { fn rename_file_internal(from_path : String, to_path : String) -> Bool {
@utils.rename_file_internal(from_path, to_path) @utils.rename_file_internal(from_path, to_path)
} }
///|
fn remove_file_internal(path : String) -> Bool { fn remove_file_internal(path : String) -> Bool {
@utils.remove_file_internal(path) @utils.remove_file_internal(path)
} }
///|
fn string_byte_length_internal(content : String) -> Int { fn string_byte_length_internal(content : String) -> Int {
@utils.string_byte_length_internal(content) @utils.string_byte_length_internal(content)
} }
///|
fn native_files_supported_internal() -> Bool { fn native_files_supported_internal() -> Bool {
@utils.native_files_supported_internal() @utils.native_files_supported_internal()
} }
+10
View File
@@ -1,37 +1,47 @@
///|
pub type RecordPredicate = @utils.RecordPredicate pub type RecordPredicate = @utils.RecordPredicate
///|
pub fn level_at_least(min_level : Level) -> RecordPredicate { pub fn level_at_least(min_level : Level) -> RecordPredicate {
@utils.level_at_least(min_level) @utils.level_at_least(min_level)
} }
///|
pub fn target_is(target : String) -> RecordPredicate { pub fn target_is(target : String) -> RecordPredicate {
@utils.target_is(target) @utils.target_is(target)
} }
///|
pub fn target_has_prefix(prefix : String) -> RecordPredicate { pub fn target_has_prefix(prefix : String) -> RecordPredicate {
@utils.target_has_prefix(prefix) @utils.target_has_prefix(prefix)
} }
///|
pub fn message_contains(fragment : String) -> RecordPredicate { pub fn message_contains(fragment : String) -> RecordPredicate {
@utils.message_contains(fragment) @utils.message_contains(fragment)
} }
///|
pub fn has_field(key : String) -> RecordPredicate { pub fn has_field(key : String) -> RecordPredicate {
@utils.has_field(key) @utils.has_field(key)
} }
///|
pub fn field_equals(key : String, value : String) -> RecordPredicate { pub fn field_equals(key : String, value : String) -> RecordPredicate {
@utils.field_equals(key, value) @utils.field_equals(key, value)
} }
///|
pub fn not_(predicate : RecordPredicate) -> RecordPredicate { pub fn not_(predicate : RecordPredicate) -> RecordPredicate {
@utils.not_(predicate) @utils.not_(predicate)
} }
///|
pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate { pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
@utils.all_of(predicates) @utils.all_of(predicates)
} }
///|
pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate { pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
@utils.any_of(predicates) @utils.any_of(predicates)
} }
+57 -42
View File
@@ -1,103 +1,118 @@
///|
pub type RecordFormatter = @utils.RecordFormatter pub type RecordFormatter = @utils.RecordFormatter
///|
pub type ColorMode = @utils.ColorMode pub type ColorMode = @utils.ColorMode
///|
pub type ColorSupport = @utils.ColorSupport pub type ColorSupport = @utils.ColorSupport
///|
pub type StyleMarkupMode = @utils.StyleMarkupMode pub type StyleMarkupMode = @utils.StyleMarkupMode
///|
pub type TextStyle = @utils.TextStyle pub type TextStyle = @utils.TextStyle
///|
pub fn text_style( pub fn text_style(
fg~ : String? = None, fg? : String? = None,
bg~ : String? = None, bg? : String? = None,
bold~ : Bool = false, bold? : Bool = false,
dim~ : Bool = false, dim? : Bool = false,
italic~ : Bool = false, italic? : Bool = false,
underline~ : Bool = false, underline? : Bool = false,
) -> TextStyle { ) -> TextStyle {
@utils.text_style( @utils.text_style(fg~, bg~, bold~, dim~, italic~, underline~)
fg=fg,
bg=bg,
bold=bold,
dim=dim,
italic=italic,
underline=underline,
)
} }
///|
pub type StyleTagRegistry = @utils.StyleTagRegistry pub type StyleTagRegistry = @utils.StyleTagRegistry
///|
pub fn style_tag_registry() -> StyleTagRegistry { pub fn style_tag_registry() -> StyleTagRegistry {
@utils.style_tag_registry() @utils.style_tag_registry()
} }
///|
pub fn default_style_tag_registry() -> StyleTagRegistry { pub fn default_style_tag_registry() -> StyleTagRegistry {
@utils.default_style_tag_registry() @utils.default_style_tag_registry()
} }
///|
pub fn global_style_tag_registry() -> StyleTagRegistry { pub fn global_style_tag_registry() -> StyleTagRegistry {
@utils.global_style_tag_registry() @utils.global_style_tag_registry()
} }
///|
pub fn set_global_style_tag_registry(registry : StyleTagRegistry) -> Unit { pub fn set_global_style_tag_registry(registry : StyleTagRegistry) -> Unit {
@utils.set_global_style_tag_registry(registry) @utils.set_global_style_tag_registry(registry)
} }
///|
pub fn reset_global_style_tag_registry() -> Unit { pub fn reset_global_style_tag_registry() -> Unit {
@utils.reset_global_style_tag_registry() @utils.reset_global_style_tag_registry()
} }
///|
pub type TextFormatter = @utils.TextFormatter pub type TextFormatter = @utils.TextFormatter
///|
pub fn text_formatter( pub fn text_formatter(
show_timestamp~ : Bool = true, show_timestamp? : Bool = true,
show_level~ : Bool = true, show_level? : Bool = true,
show_target~ : Bool = true, show_target? : Bool = true,
show_fields~ : Bool = true, show_fields? : Bool = true,
separator~ : String = " ", separator? : String = " ",
field_separator~ : String = " ", field_separator? : String = " ",
template~ : String = "", template? : String = "",
color_mode~ : ColorMode = ColorMode::Never, color_mode? : ColorMode = ColorMode::Never,
color_support~ : ColorSupport = ColorSupport::TrueColor, color_support? : ColorSupport = ColorSupport::TrueColor,
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full, style_markup? : StyleMarkupMode = StyleMarkupMode::Full,
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled, target_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled, fields_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
style_tags~ : StyleTagRegistry? = None, style_tags? : StyleTagRegistry? = None,
) -> TextFormatter { ) -> TextFormatter {
@utils.text_formatter( @utils.text_formatter(
show_timestamp=show_timestamp, show_timestamp~,
show_level=show_level, show_level~,
show_target=show_target, show_target~,
show_fields=show_fields, show_fields~,
separator=separator, separator~,
field_separator=field_separator, field_separator~,
template=template, template~,
color_mode=color_mode, color_mode~,
color_support=color_support, color_support~,
style_markup=style_markup, style_markup~,
target_style_markup=target_style_markup, target_style_markup~,
fields_style_markup=fields_style_markup, fields_style_markup~,
style_tags=style_tags, style_tags~,
) )
} }
///|
pub fn color_support_label(support : ColorSupport) -> String { pub fn color_support_label(support : ColorSupport) -> String {
@utils.color_support_label(support) @utils.color_support_label(support)
} }
///|
pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String { pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String {
@utils.style_markup_mode_label(mode) @utils.style_markup_mode_label(mode)
} }
///|
pub fn color_mode_label(mode : ColorMode) -> String { pub fn color_mode_label(mode : ColorMode) -> String {
@utils.color_mode_label(mode) @utils.color_mode_label(mode)
} }
pub fn format_text(rec : Record, formatter~ : TextFormatter = text_formatter()) -> String { ///|
@utils.format_text(rec, formatter=formatter) pub fn format_text(
rec : Record,
formatter? : TextFormatter = text_formatter(),
) -> String {
@utils.format_text(rec, formatter~)
} }
///|
pub fn format_json(rec : Record) -> String { pub fn format_json(rec : Record) -> String {
@utils.format_json(rec) @utils.format_json(rec)
} }
+13 -1
View File
@@ -1,15 +1,27 @@
///|
let default_console_sink : ConsoleSink = console_sink() let default_console_sink : ConsoleSink = console_sink()
///|
let default_min_level_ref : Ref[Level] = Ref(Level::Info) let default_min_level_ref : Ref[Level] = Ref(Level::Info)
///|
let default_target_ref : Ref[String] = Ref("") let default_target_ref : Ref[String] = Ref("")
///|
pub fn set_default_min_level(level : Level) -> Unit { pub fn set_default_min_level(level : Level) -> Unit {
default_min_level_ref.val = level default_min_level_ref.val = level
} }
///|
pub fn set_default_target(target : String) -> Unit { pub fn set_default_target(target : String) -> Unit {
default_target_ref.val = target default_target_ref.val = target
} }
///|
pub fn default_logger() -> Logger[ConsoleSink] { pub fn default_logger() -> Logger[ConsoleSink] {
Logger::new(default_console_sink, min_level=default_min_level_ref.val, target=default_target_ref.val) Logger::new(
default_console_sink,
min_level=default_min_level_ref.val,
target=default_target_ref.val,
)
} }
+22 -12
View File
@@ -1,23 +1,33 @@
pub fn log(level : Level, message : String, fields~ : Array[Field] = []) -> Unit { ///|
default_logger().log(level, message, fields=fields) pub fn log(
level : Level,
message : String,
fields? : Array[Field] = [],
) -> Unit {
default_logger().log(level, message, fields~)
} }
pub fn trace(message : String, fields~ : Array[Field] = []) -> Unit { ///|
default_logger().trace(message, fields=fields) pub fn trace(message : String, fields? : Array[Field] = []) -> Unit {
default_logger().trace(message, fields~)
} }
pub fn debug(message : String, fields~ : Array[Field] = []) -> Unit { ///|
default_logger().debug(message, fields=fields) pub fn debug(message : String, fields? : Array[Field] = []) -> Unit {
default_logger().debug(message, fields~)
} }
pub fn info(message : String, fields~ : Array[Field] = []) -> Unit { ///|
default_logger().info(message, fields=fields) pub fn info(message : String, fields? : Array[Field] = []) -> Unit {
default_logger().info(message, fields~)
} }
pub fn warn(message : String, fields~ : Array[Field] = []) -> Unit { ///|
default_logger().warn(message, fields=fields) pub fn warn(message : String, fields? : Array[Field] = []) -> Unit {
default_logger().warn(message, fields~)
} }
pub fn error(message : String, fields~ : Array[Field] = []) -> Unit { ///|
default_logger().error(message, fields=fields) pub fn error(message : String, fields? : Array[Field] = []) -> Unit {
default_logger().error(message, fields~)
} }
+1
View File
@@ -1 +1,2 @@
///|
pub type Level = @core.Level pub type Level = @core.Level
+47 -16
View File
@@ -1,49 +1,70 @@
///|
pub struct LibraryLogger[S] { pub struct LibraryLogger[S] {
inner : Logger[S] inner : Logger[S]
} }
///|
fn[S] library_logger(logger : Logger[S]) -> LibraryLogger[S] { fn[S] library_logger(logger : Logger[S]) -> LibraryLogger[S] {
{ inner: logger } { inner: logger }
} }
///|
pub fn[S] Logger::to_library_logger(self : Logger[S]) -> LibraryLogger[S] { pub fn[S] Logger::to_library_logger(self : Logger[S]) -> LibraryLogger[S] {
library_logger(self) library_logger(self)
} }
///|
pub fn[S] LibraryLogger::new( pub fn[S] LibraryLogger::new(
sink : S, sink : S,
min_level~ : Level = Level::Info, min_level? : Level = Level::Info,
target~ : String = "", target? : String = "",
) -> LibraryLogger[S] { ) -> LibraryLogger[S] {
library_logger(Logger::new(sink, min_level=min_level, target=target)) library_logger(Logger::new(sink, min_level~, target~))
} }
///|
pub fn[S] LibraryLogger::to_logger(self : LibraryLogger[S]) -> Logger[S] { pub fn[S] LibraryLogger::to_logger(self : LibraryLogger[S]) -> Logger[S] {
self.inner self.inner
} }
fn configured_library_logger(logger : ConfiguredLogger) -> LibraryLogger[RuntimeSink] { ///|
fn configured_library_logger(
logger : ConfiguredLogger,
) -> LibraryLogger[RuntimeSink] {
library_logger(logger) library_logger(logger)
} }
pub fn build_library_logger(config : LoggerConfig) -> LibraryLogger[RuntimeSink] { ///|
pub fn build_library_logger(
config : LoggerConfig,
) -> LibraryLogger[RuntimeSink] {
configured_library_logger(build_logger(config)) configured_library_logger(build_logger(config))
} }
///|
pub fn parse_and_build_library_logger( pub fn parse_and_build_library_logger(
input : String, input : String,
) -> LibraryLogger[RuntimeSink] raise ConfigError { ) -> LibraryLogger[RuntimeSink] raise ConfigError {
configured_library_logger(parse_and_build_logger(input)) configured_library_logger(parse_and_build_logger(input))
} }
pub fn[S] LibraryLogger::with_target(self : LibraryLogger[S], target : String) -> LibraryLogger[S] { ///|
pub fn[S] LibraryLogger::with_target(
self : LibraryLogger[S],
target : String,
) -> LibraryLogger[S] {
library_logger(self.inner.with_target(target)) library_logger(self.inner.with_target(target))
} }
pub fn[S] LibraryLogger::child(self : LibraryLogger[S], target : String) -> LibraryLogger[S] { ///|
pub fn[S] LibraryLogger::child(
self : LibraryLogger[S],
target : String,
) -> LibraryLogger[S] {
library_logger(self.inner.child(target)) library_logger(self.inner.child(target))
} }
///|
pub fn[S] LibraryLogger::with_context_fields( pub fn[S] LibraryLogger::with_context_fields(
self : LibraryLogger[S], self : LibraryLogger[S],
fields : Array[Field], fields : Array[Field],
@@ -51,6 +72,7 @@ pub fn[S] LibraryLogger::with_context_fields(
library_logger(self.inner.with_context_fields(fields)) library_logger(self.inner.with_context_fields(fields))
} }
///|
pub fn[S] LibraryLogger::bind( pub fn[S] LibraryLogger::bind(
self : LibraryLogger[S], self : LibraryLogger[S],
fields : Array[Field], fields : Array[Field],
@@ -58,44 +80,53 @@ pub fn[S] LibraryLogger::bind(
self.with_context_fields(fields) self.with_context_fields(fields)
} }
pub fn[S] LibraryLogger::is_enabled(self : LibraryLogger[S], level : Level) -> Bool { ///|
pub fn[S] LibraryLogger::is_enabled(
self : LibraryLogger[S],
level : Level,
) -> Bool {
self.inner.is_enabled(level) self.inner.is_enabled(level)
} }
///|
pub fn[S : Sink] LibraryLogger::log( pub fn[S : Sink] LibraryLogger::log(
self : LibraryLogger[S], self : LibraryLogger[S],
level : Level, level : Level,
message : String, message : String,
fields~ : Array[Field] = [], fields? : Array[Field] = [],
target? : String = "", target? : String = "",
) -> Unit { ) -> Unit {
self.inner.log(level, message, fields=fields, target=target) self.inner.log(level, message, fields~, target~)
} }
///|
pub fn[S : Sink] LibraryLogger::info( pub fn[S : Sink] LibraryLogger::info(
self : LibraryLogger[S], self : LibraryLogger[S],
message : String, message : String,
fields~ : Array[Field] = [], fields? : Array[Field] = [],
) -> Unit { ) -> Unit {
self.inner.info(message, fields=fields) self.inner.info(message, fields~)
} }
///|
pub fn[S : Sink] LibraryLogger::warn( pub fn[S : Sink] LibraryLogger::warn(
self : LibraryLogger[S], self : LibraryLogger[S],
message : String, message : String,
fields~ : Array[Field] = [], fields? : Array[Field] = [],
) -> Unit { ) -> Unit {
self.inner.warn(message, fields=fields) self.inner.warn(message, fields~)
} }
///|
pub fn[S : Sink] LibraryLogger::error( pub fn[S : Sink] LibraryLogger::error(
self : LibraryLogger[S], self : LibraryLogger[S],
message : String, message : String,
fields~ : Array[Field] = [], fields? : Array[Field] = [],
) -> Unit { ) -> Unit {
self.inner.error(message, fields=fields) self.inner.error(message, fields~)
} }
///|
pub fn default_library_logger() -> LibraryLogger[ConsoleSink] { pub fn default_library_logger() -> LibraryLogger[ConsoleSink] {
library_logger(default_logger()) library_logger(default_logger())
} }
+47 -12
View File
@@ -1,3 +1,4 @@
///|
pub struct Logger[S] { pub struct Logger[S] {
min_level : Level min_level : Level
sink : S sink : S
@@ -5,14 +6,21 @@ pub struct Logger[S] {
timestamp : Bool timestamp : Bool
} }
pub fn[S] Logger::new(sink : S, min_level~ : Level = Level::Info, target~ : String = "") -> Logger[S] { ///|
pub fn[S] Logger::new(
sink : S,
min_level? : Level = Level::Info,
target? : String = "",
) -> Logger[S] {
{ min_level, sink, target, timestamp: false } { min_level, sink, target, timestamp: false }
} }
///|
pub fn[S] Logger::with_target(self : Logger[S], target : String) -> Logger[S] { pub fn[S] Logger::with_target(self : Logger[S], target : String) -> Logger[S] {
{ ..self, target } { ..self, target, }
} }
///|
fn combine_targets(parent : String, child : String) -> String { fn combine_targets(parent : String, child : String) -> String {
if parent == "" { if parent == "" {
child child
@@ -23,11 +31,16 @@ fn combine_targets(parent : String, child : String) -> String {
} }
} }
///|
pub fn[S] Logger::child(self : Logger[S], target : String) -> Logger[S] { pub fn[S] Logger::child(self : Logger[S], target : String) -> Logger[S] {
{ ..self, target: combine_targets(self.target, target) } { ..self, target: combine_targets(self.target, target) }
} }
pub fn[S] Logger::with_context_fields(self : Logger[S], fields : Array[Field]) -> Logger[ContextSink[S]] { ///|
pub fn[S] Logger::with_context_fields(
self : Logger[S],
fields : Array[Field],
) -> Logger[ContextSink[S]] {
{ {
min_level: self.min_level, min_level: self.min_level,
sink: ContextSink::{ sink: self.sink, context_fields: fields }, sink: ContextSink::{ sink: self.sink, context_fields: fields },
@@ -36,11 +49,19 @@ 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]] { ///|
pub fn[S] Logger::bind(
self : Logger[S],
fields : Array[Field],
) -> Logger[ContextSink[S]] {
self.with_context_fields(fields) self.with_context_fields(fields)
} }
pub fn[S] Logger::with_filter(self : Logger[S], predicate : (Record) -> Bool) -> Logger[FilterSink[S]] { ///|
pub fn[S] Logger::with_filter(
self : Logger[S],
predicate : (Record) -> Bool,
) -> Logger[FilterSink[S]] {
{ {
min_level: self.min_level, min_level: self.min_level,
sink: filter_sink(self.sink, predicate), sink: filter_sink(self.sink, predicate),
@@ -49,7 +70,11 @@ pub fn[S] Logger::with_filter(self : Logger[S], predicate : (Record) -> Bool) ->
} }
} }
pub fn[S] Logger::with_patch(self : Logger[S], patch : RecordPatch) -> Logger[PatchSink[S]] { ///|
pub fn[S] Logger::with_patch(
self : Logger[S],
patch : RecordPatch,
) -> Logger[PatchSink[S]] {
{ {
min_level: self.min_level, min_level: self.min_level,
sink: patch_sink(self.sink, patch), sink: patch_sink(self.sink, patch),
@@ -58,27 +83,37 @@ pub fn[S] Logger::with_patch(self : Logger[S], patch : RecordPatch) -> Logger[Pa
} }
} }
///|
pub fn[S] Logger::with_queue( pub fn[S] Logger::with_queue(
self : Logger[S], self : Logger[S],
max_pending~ : Int = 0, max_pending? : Int = 0,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest, overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> Logger[QueuedSink[S]] { ) -> Logger[QueuedSink[S]] {
{ {
min_level: self.min_level, min_level: self.min_level,
sink: queued_sink(self.sink, max_pending=max_pending, overflow=overflow), sink: queued_sink(self.sink, max_pending~, overflow~),
target: self.target, target: self.target,
timestamp: self.timestamp, 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_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] { ///|
pub fn[S] Logger::with_timestamp(
self : Logger[S],
enabled? : Bool = true,
) -> Logger[S] {
{ ..self, timestamp: enabled } { ..self, timestamp: enabled }
} }
///|
pub fn[S] Logger::is_enabled(self : Logger[S], level : Level) -> Bool { pub fn[S] Logger::is_enabled(self : Logger[S], level : Level) -> Bool {
level.enabled(self.min_level) level.enabled(self.min_level)
} }
+38 -12
View File
@@ -1,8 +1,9 @@
///|
pub fn[S : Sink] Logger::log( pub fn[S : Sink] Logger::log(
self : Logger[S], self : Logger[S],
level : Level, level : Level,
message : String, message : String,
fields~ : Array[Field] = [], fields? : Array[Field] = [],
target? : String = "", target? : String = "",
) -> Unit { ) -> Unit {
if !self.is_enabled(level) { if !self.is_enabled(level) {
@@ -11,27 +12,52 @@ pub fn[S : Sink] Logger::log(
let actual_target = if target == "" { self.target } else { target } let actual_target = if target == "" { self.target } else { target }
let timestamp_ms = if self.timestamp { @env.now() } else { 0UL } let timestamp_ms = if self.timestamp { @env.now() } else { 0UL }
self.sink.write( self.sink.write(
record(level, message, timestamp_ms=timestamp_ms, target=actual_target, fields=fields), record(level, message, timestamp_ms~, target=actual_target, 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::trace(
self : Logger[S],
message : String,
fields? : Array[Field] = [],
) -> Unit {
self.log(Level::Trace, message, 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::debug(
self : Logger[S],
message : String,
fields? : Array[Field] = [],
) -> Unit {
self.log(Level::Debug, message, 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::info(
self : Logger[S],
message : String,
fields? : Array[Field] = [],
) -> Unit {
self.log(Level::Info, message, 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::warn(
self : Logger[S],
message : String,
fields? : Array[Field] = [],
) -> Unit {
self.log(Level::Warn, message, fields~)
} }
pub fn[S : Sink] Logger::error(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit { ///|
self.log(Level::Error, message, fields=fields) pub fn[S : Sink] Logger::error(
self : Logger[S],
message : String,
fields? : Array[Field] = [],
) -> Unit {
self.log(Level::Error, message, fields~)
} }
+5 -5
View File
@@ -1,12 +1,12 @@
import { import {
"Nanaloveyuki/BitLogger/src/core" @core, "Nanaloveyuki/BitLogger/src/core",
"Nanaloveyuki/BitLogger/src/utils" @utils, "Nanaloveyuki/BitLogger/src/utils",
"maria/json_parser" @json_parser, "maria/json_parser",
"moonbitlang/core/array", "moonbitlang/core/array",
"moonbitlang/core/builtin", "moonbitlang/core/builtin",
"moonbitlang/core/env" @env, "moonbitlang/core/env",
"moonbitlang/core/json", "moonbitlang/core/json",
"moonbitlang/core/queue" @queue, "moonbitlang/core/queue",
"moonbitlang/core/ref", "moonbitlang/core/ref",
} }
+15 -4
View File
@@ -1,29 +1,40 @@
///|
pub type RecordPatch = @utils.RecordPatch pub type RecordPatch = @utils.RecordPatch
///|
pub fn identity_patch() -> RecordPatch { pub fn identity_patch() -> RecordPatch {
@utils.identity_patch() @utils.identity_patch()
} }
///|
pub fn set_target(target : String) -> RecordPatch { pub fn set_target(target : String) -> RecordPatch {
@utils.set_target(target) @utils.set_target(target)
} }
///|
pub fn prefix_message(prefix : String) -> RecordPatch { pub fn prefix_message(prefix : String) -> RecordPatch {
@utils.prefix_message(prefix) @utils.prefix_message(prefix)
} }
///|
pub fn append_fields(extra_fields : Array[Field]) -> RecordPatch { pub fn append_fields(extra_fields : Array[Field]) -> RecordPatch {
@utils.append_fields(extra_fields) @utils.append_fields(extra_fields)
} }
pub fn redact_field(key : String, placeholder~ : String = "***") -> RecordPatch { ///|
@utils.redact_field(key, placeholder=placeholder) pub fn redact_field(key : String, placeholder? : String = "***") -> RecordPatch {
@utils.redact_field(key, placeholder~)
} }
pub fn redact_fields(keys : Array[String], placeholder~ : String = "***") -> RecordPatch { ///|
@utils.redact_fields(keys, placeholder=placeholder) pub fn redact_fields(
keys : Array[String],
placeholder? : String = "***",
) -> RecordPatch {
@utils.redact_fields(keys, placeholder~)
} }
///|
pub fn compose_patches(patches : Array[RecordPatch]) -> RecordPatch { pub fn compose_patches(patches : Array[RecordPatch]) -> RecordPatch {
@utils.compose_patches(patches) @utils.compose_patches(patches)
} }
+46 -40
View File
@@ -1,89 +1,95 @@
///|
pub fn console( pub fn console(
min_level~ : Level = Level::Info, min_level? : Level = Level::Info,
target~ : String = "", target? : String = "",
timestamp~ : Bool = false, timestamp? : Bool = false,
) -> LoggerConfig { ) -> LoggerConfig {
LoggerConfig::new( LoggerConfig::new(
min_level=min_level, min_level~,
target=target, target~,
timestamp=timestamp, timestamp~,
sink=SinkConfig::new(kind=SinkKind::Console), sink=SinkConfig::new(kind=SinkKind::Console),
) )
} }
///|
pub fn json_console( pub fn json_console(
min_level~ : Level = Level::Info, min_level? : Level = Level::Info,
target~ : String = "", target? : String = "",
timestamp~ : Bool = false, timestamp? : Bool = false,
) -> LoggerConfig { ) -> LoggerConfig {
LoggerConfig::new( LoggerConfig::new(
min_level=min_level, min_level~,
target=target, target~,
timestamp=timestamp, timestamp~,
sink=SinkConfig::new(kind=SinkKind::JsonConsole), sink=SinkConfig::new(kind=SinkKind::JsonConsole),
) )
} }
///|
pub fn text_console( pub fn text_console(
min_level~ : Level = Level::Info, min_level? : Level = Level::Info,
target~ : String = "", target? : String = "",
timestamp~ : Bool = false, timestamp? : Bool = false,
text_formatter~ : TextFormatterConfig = default_text_formatter_config(), text_formatter? : TextFormatterConfig = default_text_formatter_config(),
) -> LoggerConfig { ) -> LoggerConfig {
LoggerConfig::new( LoggerConfig::new(
min_level=min_level, min_level~,
target=target, target~,
timestamp=timestamp, timestamp~,
sink=SinkConfig::new(kind=SinkKind::TextConsole, text_formatter=text_formatter), sink=SinkConfig::new(kind=SinkKind::TextConsole, text_formatter~),
) )
} }
///|
pub fn file( pub fn file(
path : String, path : String,
min_level~ : Level = Level::Info, min_level? : Level = Level::Info,
target~ : String = "", target? : String = "",
timestamp~ : Bool = false, timestamp? : Bool = false,
append~ : Bool = true, append? : Bool = true,
auto_flush~ : Bool = true, auto_flush? : Bool = true,
rotation~ : FileRotation? = None, rotation? : FileRotation? = None,
text_formatter~ : TextFormatterConfig = default_text_formatter_config(), text_formatter? : TextFormatterConfig = default_text_formatter_config(),
) -> LoggerConfig raise ConfigError { ) -> LoggerConfig raise ConfigError {
if path == "" { if path == "" {
raise ConfigError::InvalidConfig("File sink requires non-empty path") raise ConfigError::InvalidConfig("File sink requires non-empty path")
} }
LoggerConfig::new( LoggerConfig::new(
min_level=min_level, min_level~,
target=target, target~,
timestamp=timestamp, timestamp~,
sink=SinkConfig::new( sink=SinkConfig::new(
kind=SinkKind::File, kind=SinkKind::File,
path=path, path~,
append=append, append~,
auto_flush=auto_flush, auto_flush~,
rotation=rotation, rotation~,
text_formatter=text_formatter, text_formatter~,
), ),
) )
} }
///|
pub fn with_queue( pub fn with_queue(
config : LoggerConfig, config : LoggerConfig,
max_pending~ : Int = 0, max_pending? : Int = 0,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest, overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> LoggerConfig { ) -> LoggerConfig {
LoggerConfig::new( LoggerConfig::new(
min_level=config.min_level, min_level=config.min_level,
target=config.target, target=config.target,
timestamp=config.timestamp, timestamp=config.timestamp,
sink=config.sink, sink=config.sink,
queue=Some(QueueConfig::new(max_pending, overflow=overflow)), queue=Some(QueueConfig::new(max_pending, overflow~)),
) )
} }
///|
pub fn with_file_rotation( pub fn with_file_rotation(
config : LoggerConfig, config : LoggerConfig,
max_bytes : Int, max_bytes : Int,
max_backups~ : Int = 1, max_backups? : Int = 1,
) -> LoggerConfig { ) -> LoggerConfig {
match config.sink.kind { match config.sink.kind {
SinkKind::File => SinkKind::File =>
@@ -96,7 +102,7 @@ pub fn with_file_rotation(
path=config.sink.path, path=config.sink.path,
append=config.sink.append, append=config.sink.append,
auto_flush=config.sink.auto_flush, auto_flush=config.sink.auto_flush,
rotation=Some(file_rotation(max_bytes, max_backups=max_backups)), rotation=Some(file_rotation(max_bytes, max_backups~)),
text_formatter=config.sink.text_formatter, text_formatter=config.sink.text_formatter,
), ),
queue=config.queue, queue=config.queue,
+77 -25
View File
@@ -1,39 +1,56 @@
///|
test "logger config presets use expected defaults" { test "logger config presets use expected defaults" {
let console_config = console() let console_config = console()
inspect(console_config.min_level.label(), content="INFO") inspect(console_config.min_level.label(), content="INFO")
inspect(console_config.target, content="") inspect(console_config.target, content="")
inspect(console_config.timestamp, content="false") inspect(console_config.timestamp, content="false")
inspect(match console_config.sink.kind { inspect(
match console_config.sink.kind {
SinkKind::Console => "Console" SinkKind::Console => "Console"
_ => "other" _ => "other"
}, content="Console") },
content="Console",
)
inspect(console_config.queue is None, content="true") inspect(console_config.queue is None, content="true")
let json_config = json_console() let json_config = json_console()
inspect(match json_config.sink.kind { inspect(
match json_config.sink.kind {
SinkKind::JsonConsole => "JsonConsole" SinkKind::JsonConsole => "JsonConsole"
_ => "other" _ => "other"
}, content="JsonConsole") },
content="JsonConsole",
)
inspect(json_config.queue is None, content="true") inspect(json_config.queue is None, content="true")
let text_config = text_console() let text_config = text_console()
inspect(match text_config.sink.kind { inspect(
match text_config.sink.kind {
SinkKind::TextConsole => "TextConsole" SinkKind::TextConsole => "TextConsole"
_ => "other" _ => "other"
}, content="TextConsole") },
content="TextConsole",
)
inspect(text_config.sink.text_formatter.show_timestamp, content="true") inspect(text_config.sink.text_formatter.show_timestamp, content="true")
inspect(color_mode_label(text_config.sink.text_formatter.color_mode), content="never") inspect(
color_mode_label(text_config.sink.text_formatter.color_mode),
content="never",
)
} }
///|
test "file preset uses file sink defaults" { test "file preset uses file sink defaults" {
let config = file("bitlogger.log") let config = file("bitlogger.log")
inspect(config.min_level.label(), content="INFO") inspect(config.min_level.label(), content="INFO")
inspect(config.target, content="") inspect(config.target, content="")
inspect(config.timestamp, content="false") inspect(config.timestamp, content="false")
inspect(match config.sink.kind { inspect(
match config.sink.kind {
SinkKind::File => "File" SinkKind::File => "File"
_ => "other" _ => "other"
}, content="File") },
content="File",
)
inspect(config.sink.path, content="bitlogger.log") inspect(config.sink.path, content="bitlogger.log")
inspect(config.sink.append, content="true") inspect(config.sink.append, content="true")
inspect(config.sink.auto_flush, content="true") inspect(config.sink.auto_flush, content="true")
@@ -41,6 +58,7 @@ test "file preset uses file sink defaults" {
inspect(config.queue is None, content="true") inspect(config.queue is None, content="true")
} }
///|
test "file preset rejects empty path like parser file sink config" { test "file preset rejects empty path like parser file sink config" {
let preset_error = (fn() -> String raise ConfigError { let preset_error = (fn() -> String raise ConfigError {
ignore(file("")) ignore(file(""))
@@ -51,7 +69,9 @@ test "file preset rejects empty path like parser file sink config" {
inspect(preset_error, content="File sink requires non-empty path") inspect(preset_error, content="File sink requires non-empty path")
let parser_error = (fn() -> String raise ConfigError { let parser_error = (fn() -> String raise ConfigError {
ignore(parse_logger_config_text("{\"sink\":{\"kind\":\"file\",\"path\":\"\"}}")) ignore(
parse_logger_config_text("{\"sink\":{\"kind\":\"file\",\"path\":\"\"}}"),
)
"no error" "no error"
})() catch { })() catch {
ConfigError::InvalidConfig(message) => message ConfigError::InvalidConfig(message) => message
@@ -59,8 +79,12 @@ test "file preset rejects empty path like parser file sink config" {
inspect(parser_error, content="File sink requires non-empty path") inspect(parser_error, content="File sink requires non-empty path")
} }
///|
test "preset helpers compose queue and file rotation without losing config" { test "preset helpers compose queue and file rotation without losing config" {
let formatter = TextFormatterConfig::new(show_timestamp=false, separator=" | ") let formatter = TextFormatterConfig::new(
show_timestamp=false,
separator=" | ",
)
let config = file( let config = file(
"service.log", "service.log",
min_level=Level::Warn, min_level=Level::Warn,
@@ -86,10 +110,13 @@ test "preset helpers compose queue and file rotation without losing config" {
match config.queue { match config.queue {
Some(queue) => { Some(queue) => {
inspect(queue.max_pending, content="32") inspect(queue.max_pending, content="32")
inspect(match queue.overflow { inspect(
match queue.overflow {
QueueOverflowPolicy::DropNewest => "DropNewest" QueueOverflowPolicy::DropNewest => "DropNewest"
QueueOverflowPolicy::DropOldest => "DropOldest" QueueOverflowPolicy::DropOldest => "DropOldest"
}, content="DropOldest") },
content="DropOldest",
)
} }
None => inspect(false, content="true") None => inspect(false, content="true")
} }
@@ -102,6 +129,7 @@ test "preset helpers compose queue and file rotation without losing config" {
} }
} }
///|
test "queue helper preserves file rotation when applied after rotation" { test "queue helper preserves file rotation when applied after rotation" {
let config = with_queue( let config = with_queue(
with_file_rotation(file("service.log", append=false), 256, max_backups=2), with_file_rotation(file("service.log", append=false), 256, max_backups=2),
@@ -113,10 +141,13 @@ test "queue helper preserves file rotation when applied after rotation" {
match config.queue { match config.queue {
Some(queue) => { Some(queue) => {
inspect(queue.max_pending, content="8") inspect(queue.max_pending, content="8")
inspect(match queue.overflow { inspect(
match queue.overflow {
QueueOverflowPolicy::DropNewest => "DropNewest" QueueOverflowPolicy::DropNewest => "DropNewest"
QueueOverflowPolicy::DropOldest => "DropOldest" QueueOverflowPolicy::DropOldest => "DropOldest"
}, content="DropNewest") },
content="DropNewest",
)
} }
None => inspect(false, content="true") None => inspect(false, content="true")
} }
@@ -129,29 +160,44 @@ test "queue helper preserves file rotation when applied after rotation" {
} }
} }
///|
test "file rotation helper leaves non-file presets unchanged" { test "file rotation helper leaves non-file presets unchanged" {
let console_config = with_file_rotation(console(target="console"), 64, max_backups=2) let console_config = with_file_rotation(
inspect(match console_config.sink.kind { console(target="console"),
64,
max_backups=2,
)
inspect(
match console_config.sink.kind {
SinkKind::Console => "Console" SinkKind::Console => "Console"
_ => "other" _ => "other"
}, content="Console") },
content="Console",
)
inspect(console_config.target, content="console") inspect(console_config.target, content="console")
inspect(console_config.sink.rotation is None, content="true") inspect(console_config.sink.rotation is None, content="true")
let text_config = with_file_rotation( let text_config = with_file_rotation(
text_console(target="text", text_formatter=TextFormatterConfig::new(separator=" :: ")), text_console(
target="text",
text_formatter=TextFormatterConfig::new(separator=" :: "),
),
96, 96,
max_backups=4, max_backups=4,
) )
inspect(match text_config.sink.kind { inspect(
match text_config.sink.kind {
SinkKind::TextConsole => "TextConsole" SinkKind::TextConsole => "TextConsole"
_ => "other" _ => "other"
}, content="TextConsole") },
content="TextConsole",
)
inspect(text_config.target, content="text") inspect(text_config.target, content="text")
inspect(text_config.sink.text_formatter.separator, content=" :: ") inspect(text_config.sink.text_formatter.separator, content=" :: ")
inspect(text_config.sink.rotation is None, content="true") inspect(text_config.sink.rotation is None, content="true")
} }
///|
test "file rotation helper keeps queued non-file presets unchanged" { test "file rotation helper keeps queued non-file presets unchanged" {
let config = with_queue( let config = with_queue(
json_console(min_level=Level::Error, target="json"), json_console(min_level=Level::Error, target="json"),
@@ -161,18 +207,24 @@ test "file rotation helper keeps queued non-file presets unchanged" {
let rotated = with_file_rotation(config, 512, max_backups=5) let rotated = with_file_rotation(config, 512, max_backups=5)
inspect(rotated.min_level.label(), content="ERROR") inspect(rotated.min_level.label(), content="ERROR")
inspect(rotated.target, content="json") inspect(rotated.target, content="json")
inspect(match rotated.sink.kind { inspect(
match rotated.sink.kind {
SinkKind::JsonConsole => "JsonConsole" SinkKind::JsonConsole => "JsonConsole"
_ => "other" _ => "other"
}, content="JsonConsole") },
content="JsonConsole",
)
inspect(rotated.sink.rotation is None, content="true") inspect(rotated.sink.rotation is None, content="true")
match rotated.queue { match rotated.queue {
Some(queue) => { Some(queue) => {
inspect(queue.max_pending, content="4") inspect(queue.max_pending, content="4")
inspect(match queue.overflow { inspect(
match queue.overflow {
QueueOverflowPolicy::DropNewest => "DropNewest" QueueOverflowPolicy::DropNewest => "DropNewest"
QueueOverflowPolicy::DropOldest => "DropOldest" QueueOverflowPolicy::DropOldest => "DropOldest"
}, content="DropOldest") },
content="DropOldest",
)
} }
None => inspect(false, content="true") None => inspect(false, content="true")
} }
+9 -4
View File
@@ -1,21 +1,26 @@
///|
pub type Field = @core.Field pub type Field = @core.Field
///|
pub fn field(key : String, value : String) -> Field { pub fn field(key : String, value : String) -> Field {
@core.field(key, value) @core.field(key, value)
} }
///|
pub fn fields(entries : Array[(String, String)]) -> Array[Field] { pub fn fields(entries : Array[(String, String)]) -> Array[Field] {
@core.fields(entries) @core.fields(entries)
} }
///|
pub type Record = @core.Record pub type Record = @core.Record
///|
fn record( fn record(
level : Level, level : Level,
message : String, message : String,
timestamp_ms~ : UInt64 = 0UL, timestamp_ms? : UInt64 = 0UL,
target~ : String = "", target? : String = "",
fields~ : Array[Field] = [], fields? : Array[Field] = [],
) -> Record { ) -> Record {
@core.Record::new(level, message, timestamp_ms=timestamp_ms, target=target, fields=fields) @core.Record::new(level, message, timestamp_ms~, target~, fields~)
} }
+117 -20
View File
@@ -1,3 +1,4 @@
///|
pub fn RuntimeSink::file_available(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_available(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.is_available() File(sink) => sink.is_available()
@@ -6,14 +7,19 @@ pub fn RuntimeSink::file_available(self : RuntimeSink) -> Bool {
} }
} }
pub fn RuntimeSink::file_reopen(self : RuntimeSink, append~ : Bool? = None) -> Bool { ///|
pub fn RuntimeSink::file_reopen(
self : RuntimeSink,
append? : Bool? = None,
) -> Bool {
match self { match self {
File(sink) => sink.reopen(append=append) File(sink) => sink.reopen(append~)
QueuedFile(sink) => sink.sink.reopen(append=append) QueuedFile(sink) => sink.sink.reopen(append~)
_ => false _ => false
} }
} }
///|
pub fn RuntimeSink::file_reopen_with_current_policy(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_reopen_with_current_policy(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.reopen_with_current_policy() File(sink) => sink.reopen_with_current_policy()
@@ -22,6 +28,7 @@ pub fn RuntimeSink::file_reopen_with_current_policy(self : RuntimeSink) -> Bool
} }
} }
///|
pub fn RuntimeSink::file_reopen_append(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_reopen_append(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.reopen_append() File(sink) => sink.reopen_append()
@@ -30,6 +37,7 @@ pub fn RuntimeSink::file_reopen_append(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_reopen_truncate(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_reopen_truncate(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.reopen_truncate() File(sink) => sink.reopen_truncate()
@@ -38,6 +46,7 @@ pub fn RuntimeSink::file_reopen_truncate(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_append_mode(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_append_mode(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.append_mode() File(sink) => sink.append_mode()
@@ -46,7 +55,11 @@ pub fn RuntimeSink::file_append_mode(self : RuntimeSink) -> Bool {
} }
} }
pub fn RuntimeSink::file_set_append_mode(self : RuntimeSink, append : Bool) -> Bool { ///|
pub fn RuntimeSink::file_set_append_mode(
self : RuntimeSink,
append : Bool,
) -> Bool {
match self { match self {
File(sink) => { File(sink) => {
sink.set_append_mode(append) sink.set_append_mode(append)
@@ -60,6 +73,7 @@ pub fn RuntimeSink::file_set_append_mode(self : RuntimeSink, append : Bool) -> B
} }
} }
///|
pub fn RuntimeSink::file_path(self : RuntimeSink) -> String { pub fn RuntimeSink::file_path(self : RuntimeSink) -> String {
match self { match self {
File(sink) => sink.path() File(sink) => sink.path()
@@ -68,6 +82,7 @@ pub fn RuntimeSink::file_path(self : RuntimeSink) -> String {
} }
} }
///|
pub fn RuntimeSink::file_auto_flush(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_auto_flush(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.auto_flush_enabled() File(sink) => sink.auto_flush_enabled()
@@ -76,6 +91,7 @@ pub fn RuntimeSink::file_auto_flush(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_rotation_enabled(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_rotation_enabled(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.rotation_enabled() File(sink) => sink.rotation_enabled()
@@ -84,6 +100,7 @@ pub fn RuntimeSink::file_rotation_enabled(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_rotation_config(self : RuntimeSink) -> FileRotation? { pub fn RuntimeSink::file_rotation_config(self : RuntimeSink) -> FileRotation? {
match self { match self {
File(sink) => sink.rotation_config() File(sink) => sink.rotation_config()
@@ -92,7 +109,11 @@ pub fn RuntimeSink::file_rotation_config(self : RuntimeSink) -> FileRotation? {
} }
} }
pub fn RuntimeSink::file_set_auto_flush(self : RuntimeSink, enabled : Bool) -> Bool { ///|
pub fn RuntimeSink::file_set_auto_flush(
self : RuntimeSink,
enabled : Bool,
) -> Bool {
match self { match self {
File(sink) => { File(sink) => {
sink.set_auto_flush(enabled) sink.set_auto_flush(enabled)
@@ -106,7 +127,11 @@ pub fn RuntimeSink::file_set_auto_flush(self : RuntimeSink, enabled : Bool) -> B
} }
} }
pub fn RuntimeSink::file_set_policy(self : RuntimeSink, policy : FileSinkPolicy) -> Bool { ///|
pub fn RuntimeSink::file_set_policy(
self : RuntimeSink,
policy : FileSinkPolicy,
) -> Bool {
match self { match self {
File(sink) => { File(sink) => {
sink.set_policy(policy) sink.set_policy(policy)
@@ -120,7 +145,11 @@ pub fn RuntimeSink::file_set_policy(self : RuntimeSink, policy : FileSinkPolicy)
} }
} }
pub fn RuntimeSink::file_set_rotation(self : RuntimeSink, rotation : FileRotation?) -> Bool { ///|
pub fn RuntimeSink::file_set_rotation(
self : RuntimeSink,
rotation : FileRotation?,
) -> Bool {
match self { match self {
File(sink) => { File(sink) => {
sink.set_rotation(rotation) sink.set_rotation(rotation)
@@ -134,6 +163,7 @@ pub fn RuntimeSink::file_set_rotation(self : RuntimeSink, rotation : FileRotatio
} }
} }
///|
pub fn RuntimeSink::file_clear_rotation(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_clear_rotation(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => { File(sink) => {
@@ -148,6 +178,7 @@ pub fn RuntimeSink::file_clear_rotation(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_flush(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_flush(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.flush() File(sink) => sink.flush()
@@ -159,6 +190,7 @@ pub fn RuntimeSink::file_flush(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_close(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_close(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.close() File(sink) => sink.close()
@@ -170,6 +202,7 @@ pub fn RuntimeSink::file_close(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_open_failures(self : RuntimeSink) -> Int { pub fn RuntimeSink::file_open_failures(self : RuntimeSink) -> Int {
match self { match self {
File(sink) => sink.open_failures() File(sink) => sink.open_failures()
@@ -178,6 +211,7 @@ pub fn RuntimeSink::file_open_failures(self : RuntimeSink) -> Int {
} }
} }
///|
pub fn RuntimeSink::file_write_failures(self : RuntimeSink) -> Int { pub fn RuntimeSink::file_write_failures(self : RuntimeSink) -> Int {
match self { match self {
File(sink) => sink.write_failures() File(sink) => sink.write_failures()
@@ -186,6 +220,7 @@ pub fn RuntimeSink::file_write_failures(self : RuntimeSink) -> Int {
} }
} }
///|
pub fn RuntimeSink::file_flush_failures(self : RuntimeSink) -> Int { pub fn RuntimeSink::file_flush_failures(self : RuntimeSink) -> Int {
match self { match self {
File(sink) => sink.flush_failures() File(sink) => sink.flush_failures()
@@ -194,6 +229,7 @@ pub fn RuntimeSink::file_flush_failures(self : RuntimeSink) -> Int {
} }
} }
///|
pub fn RuntimeSink::file_rotation_failures(self : RuntimeSink) -> Int { pub fn RuntimeSink::file_rotation_failures(self : RuntimeSink) -> Int {
match self { match self {
File(sink) => sink.rotation_failures() File(sink) => sink.rotation_failures()
@@ -202,6 +238,7 @@ pub fn RuntimeSink::file_rotation_failures(self : RuntimeSink) -> Int {
} }
} }
///|
pub fn RuntimeSink::file_reset_failure_counters(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_reset_failure_counters(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => { File(sink) => {
@@ -216,6 +253,7 @@ pub fn RuntimeSink::file_reset_failure_counters(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_reset_policy(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_reset_policy(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => { File(sink) => {
@@ -230,6 +268,7 @@ pub fn RuntimeSink::file_reset_policy(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_policy(self : RuntimeSink) -> FileSinkPolicy { pub fn RuntimeSink::file_policy(self : RuntimeSink) -> FileSinkPolicy {
match self { match self {
File(sink) => sink.policy() File(sink) => sink.policy()
@@ -238,6 +277,7 @@ pub fn RuntimeSink::file_policy(self : RuntimeSink) -> FileSinkPolicy {
} }
} }
///|
pub fn RuntimeSink::file_default_policy(self : RuntimeSink) -> FileSinkPolicy { pub fn RuntimeSink::file_default_policy(self : RuntimeSink) -> FileSinkPolicy {
match self { match self {
File(sink) => sink.default_policy() File(sink) => sink.default_policy()
@@ -246,6 +286,7 @@ pub fn RuntimeSink::file_default_policy(self : RuntimeSink) -> FileSinkPolicy {
} }
} }
///|
pub fn RuntimeSink::file_policy_matches_default(self : RuntimeSink) -> Bool { pub fn RuntimeSink::file_policy_matches_default(self : RuntimeSink) -> Bool {
match self { match self {
File(sink) => sink.policy_matches_default() File(sink) => sink.policy_matches_default()
@@ -254,11 +295,13 @@ pub fn RuntimeSink::file_policy_matches_default(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::file_state(self : RuntimeSink) -> FileSinkState { pub fn RuntimeSink::file_state(self : RuntimeSink) -> FileSinkState {
match self { match self {
File(sink) => sink.state() File(sink) => sink.state()
QueuedFile(sink) => sink.sink.state() QueuedFile(sink) => sink.sink.state()
_ => FileSinkState::new( _ =>
FileSinkState::new(
"", "",
available=false, available=false,
append=false, append=false,
@@ -272,10 +315,12 @@ pub fn RuntimeSink::file_state(self : RuntimeSink) -> FileSinkState {
} }
} }
///|
pub fn RuntimeSink::file_runtime_state(self : RuntimeSink) -> RuntimeFileState? { pub fn RuntimeSink::file_runtime_state(self : RuntimeSink) -> RuntimeFileState? {
match self { match self {
File(sink) => Some(RuntimeFileState::new(sink.state())) File(sink) => Some(RuntimeFileState::new(sink.state()))
QueuedFile(sink) => Some( QueuedFile(sink) =>
Some(
RuntimeFileState::new( RuntimeFileState::new(
sink.sink.state(), sink.sink.state(),
queued=true, queued=true,
@@ -287,58 +332,88 @@ pub fn RuntimeSink::file_runtime_state(self : RuntimeSink) -> RuntimeFileState?
} }
} }
///|
pub fn ConfiguredLogger::file_available(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_available(self : ConfiguredLogger) -> Bool {
self.sink.file_available() self.sink.file_available()
} }
pub fn ConfiguredLogger::file_reopen(self : ConfiguredLogger, append~ : Bool? = None) -> Bool { ///|
self.sink.file_reopen(append=append) pub fn ConfiguredLogger::file_reopen(
self : ConfiguredLogger,
append? : Bool? = None,
) -> Bool {
self.sink.file_reopen(append~)
} }
pub fn ConfiguredLogger::file_reopen_with_current_policy(self : ConfiguredLogger) -> Bool { ///|
pub fn ConfiguredLogger::file_reopen_with_current_policy(
self : ConfiguredLogger,
) -> Bool {
self.sink.file_reopen_with_current_policy() self.sink.file_reopen_with_current_policy()
} }
///|
pub fn ConfiguredLogger::file_reopen_append(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_reopen_append(self : ConfiguredLogger) -> Bool {
self.sink.file_reopen_append() self.sink.file_reopen_append()
} }
///|
pub fn ConfiguredLogger::file_reopen_truncate(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_reopen_truncate(self : ConfiguredLogger) -> Bool {
self.sink.file_reopen_truncate() self.sink.file_reopen_truncate()
} }
///|
pub fn ConfiguredLogger::file_append_mode(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_append_mode(self : ConfiguredLogger) -> Bool {
self.sink.file_append_mode() self.sink.file_append_mode()
} }
pub fn ConfiguredLogger::file_set_append_mode(self : ConfiguredLogger, append : Bool) -> Bool { ///|
pub fn ConfiguredLogger::file_set_append_mode(
self : ConfiguredLogger,
append : Bool,
) -> Bool {
self.sink.file_set_append_mode(append) self.sink.file_set_append_mode(append)
} }
///|
pub fn ConfiguredLogger::file_path(self : ConfiguredLogger) -> String { pub fn ConfiguredLogger::file_path(self : ConfiguredLogger) -> String {
self.sink.file_path() self.sink.file_path()
} }
///|
pub fn ConfiguredLogger::file_auto_flush(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_auto_flush(self : ConfiguredLogger) -> Bool {
self.sink.file_auto_flush() self.sink.file_auto_flush()
} }
///|
pub fn ConfiguredLogger::file_rotation_enabled(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_rotation_enabled(self : ConfiguredLogger) -> Bool {
self.sink.file_rotation_enabled() self.sink.file_rotation_enabled()
} }
pub fn ConfiguredLogger::file_rotation_config(self : ConfiguredLogger) -> FileRotation? { ///|
pub fn ConfiguredLogger::file_rotation_config(
self : ConfiguredLogger,
) -> FileRotation? {
self.sink.file_rotation_config() self.sink.file_rotation_config()
} }
pub fn ConfiguredLogger::file_set_auto_flush(self : ConfiguredLogger, enabled : Bool) -> Bool { ///|
pub fn ConfiguredLogger::file_set_auto_flush(
self : ConfiguredLogger,
enabled : Bool,
) -> Bool {
self.sink.file_set_auto_flush(enabled) self.sink.file_set_auto_flush(enabled)
} }
pub fn ConfiguredLogger::file_set_policy(self : ConfiguredLogger, policy : FileSinkPolicy) -> Bool { ///|
pub fn ConfiguredLogger::file_set_policy(
self : ConfiguredLogger,
policy : FileSinkPolicy,
) -> Bool {
self.sink.file_set_policy(policy) self.sink.file_set_policy(policy)
} }
///|
pub fn ConfiguredLogger::file_set_rotation( pub fn ConfiguredLogger::file_set_rotation(
self : ConfiguredLogger, self : ConfiguredLogger,
rotation : FileRotation?, rotation : FileRotation?,
@@ -346,58 +421,80 @@ pub fn ConfiguredLogger::file_set_rotation(
self.sink.file_set_rotation(rotation) self.sink.file_set_rotation(rotation)
} }
///|
pub fn ConfiguredLogger::file_clear_rotation(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_clear_rotation(self : ConfiguredLogger) -> Bool {
self.sink.file_clear_rotation() self.sink.file_clear_rotation()
} }
///|
pub fn ConfiguredLogger::file_flush(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_flush(self : ConfiguredLogger) -> Bool {
self.sink.file_flush() self.sink.file_flush()
} }
///|
pub fn ConfiguredLogger::file_close(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_close(self : ConfiguredLogger) -> Bool {
self.sink.file_close() self.sink.file_close()
} }
///|
pub fn ConfiguredLogger::file_open_failures(self : ConfiguredLogger) -> Int { pub fn ConfiguredLogger::file_open_failures(self : ConfiguredLogger) -> Int {
self.sink.file_open_failures() self.sink.file_open_failures()
} }
///|
pub fn ConfiguredLogger::file_write_failures(self : ConfiguredLogger) -> Int { pub fn ConfiguredLogger::file_write_failures(self : ConfiguredLogger) -> Int {
self.sink.file_write_failures() self.sink.file_write_failures()
} }
///|
pub fn ConfiguredLogger::file_flush_failures(self : ConfiguredLogger) -> Int { pub fn ConfiguredLogger::file_flush_failures(self : ConfiguredLogger) -> Int {
self.sink.file_flush_failures() self.sink.file_flush_failures()
} }
///|
pub fn ConfiguredLogger::file_rotation_failures(self : ConfiguredLogger) -> Int { pub fn ConfiguredLogger::file_rotation_failures(self : ConfiguredLogger) -> Int {
self.sink.file_rotation_failures() self.sink.file_rotation_failures()
} }
pub fn ConfiguredLogger::file_reset_failure_counters(self : ConfiguredLogger) -> Bool { ///|
pub fn ConfiguredLogger::file_reset_failure_counters(
self : ConfiguredLogger,
) -> Bool {
self.sink.file_reset_failure_counters() self.sink.file_reset_failure_counters()
} }
///|
pub fn ConfiguredLogger::file_reset_policy(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::file_reset_policy(self : ConfiguredLogger) -> Bool {
self.sink.file_reset_policy() self.sink.file_reset_policy()
} }
///|
pub fn ConfiguredLogger::file_policy(self : ConfiguredLogger) -> FileSinkPolicy { pub fn ConfiguredLogger::file_policy(self : ConfiguredLogger) -> FileSinkPolicy {
self.sink.file_policy() self.sink.file_policy()
} }
pub fn ConfiguredLogger::file_default_policy(self : ConfiguredLogger) -> FileSinkPolicy { ///|
pub fn ConfiguredLogger::file_default_policy(
self : ConfiguredLogger,
) -> FileSinkPolicy {
self.sink.file_default_policy() self.sink.file_default_policy()
} }
pub fn ConfiguredLogger::file_policy_matches_default(self : ConfiguredLogger) -> Bool { ///|
pub fn ConfiguredLogger::file_policy_matches_default(
self : ConfiguredLogger,
) -> Bool {
self.sink.file_policy_matches_default() self.sink.file_policy_matches_default()
} }
///|
pub fn ConfiguredLogger::file_state(self : ConfiguredLogger) -> FileSinkState { pub fn ConfiguredLogger::file_state(self : ConfiguredLogger) -> FileSinkState {
self.sink.file_state() self.sink.file_state()
} }
pub fn ConfiguredLogger::file_runtime_state(self : ConfiguredLogger) -> RuntimeFileState? { ///|
pub fn ConfiguredLogger::file_runtime_state(
self : ConfiguredLogger,
) -> RuntimeFileState? {
self.sink.file_runtime_state() self.sink.file_runtime_state()
} }
+94 -29
View File
@@ -1,3 +1,4 @@
///|
pub(all) enum RuntimeSink { pub(all) enum RuntimeSink {
Console(ConsoleSink) Console(ConsoleSink)
JsonConsole(JsonConsoleSink) JsonConsole(JsonConsoleSink)
@@ -9,33 +10,54 @@ pub(all) enum RuntimeSink {
QueuedFile(QueuedSink[FileSink]) QueuedFile(QueuedSink[FileSink])
} }
///|
pub type RuntimeFileState = @utils.RuntimeFileState pub type RuntimeFileState = @utils.RuntimeFileState
pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> @json_parser.JsonValue { ///|
pub fn file_sink_policy_to_json(
policy : FileSinkPolicy,
) -> @json_parser.JsonValue {
@utils.file_sink_policy_to_json(policy) @utils.file_sink_policy_to_json(policy)
} }
pub fn stringify_file_sink_policy(policy : FileSinkPolicy, pretty~ : Bool = false) -> String { ///|
@utils.stringify_file_sink_policy(policy, pretty=pretty) pub fn stringify_file_sink_policy(
policy : FileSinkPolicy,
pretty? : Bool = false,
) -> String {
@utils.stringify_file_sink_policy(policy, pretty~)
} }
///|
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue { pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue {
@utils.file_sink_state_to_json(state) @utils.file_sink_state_to_json(state)
} }
pub fn stringify_file_sink_state(state : FileSinkState, pretty~ : Bool = false) -> String { ///|
@utils.stringify_file_sink_state(state, pretty=pretty) pub fn stringify_file_sink_state(
state : FileSinkState,
pretty? : Bool = false,
) -> String {
@utils.stringify_file_sink_state(state, pretty~)
} }
pub fn runtime_file_state_to_json(state : RuntimeFileState) -> @json_parser.JsonValue { ///|
pub fn runtime_file_state_to_json(
state : RuntimeFileState,
) -> @json_parser.JsonValue {
@utils.runtime_file_state_to_json(state) @utils.runtime_file_state_to_json(state)
} }
pub fn stringify_runtime_file_state(state : RuntimeFileState, pretty~ : Bool = false) -> String { ///|
@utils.stringify_runtime_file_state(state, pretty=pretty) pub fn stringify_runtime_file_state(
state : RuntimeFileState,
pretty? : Bool = false,
) -> String {
@utils.stringify_runtime_file_state(state, pretty~)
} }
pub impl Sink for RuntimeSink with write(self, rec) { ///|
pub impl Sink for RuntimeSink with fn write(self, rec) {
match self { match self {
Console(sink) => sink.write(rec) Console(sink) => sink.write(rec)
JsonConsole(sink) => sink.write(rec) JsonConsole(sink) => sink.write(rec)
@@ -48,6 +70,7 @@ pub impl Sink for RuntimeSink with write(self, rec) {
} }
} }
///|
pub fn RuntimeSink::flush(self : RuntimeSink) -> Int { pub fn RuntimeSink::flush(self : RuntimeSink) -> Int {
match self { match self {
Console(_) => 0 Console(_) => 0
@@ -61,19 +84,21 @@ pub fn RuntimeSink::flush(self : RuntimeSink) -> Int {
} }
} }
pub fn RuntimeSink::drain(self : RuntimeSink, max_items~ : Int = -1) -> Int { ///|
pub fn RuntimeSink::drain(self : RuntimeSink, max_items? : Int = -1) -> Int {
match self { match self {
Console(_) => 0 Console(_) => 0
JsonConsole(_) => 0 JsonConsole(_) => 0
TextConsole(_) => 0 TextConsole(_) => 0
File(sink) => if sink.flush() { 1 } else { 0 } File(sink) => if sink.flush() { 1 } else { 0 }
QueuedConsole(sink) => sink.drain(max_items=max_items) QueuedConsole(sink) => sink.drain(max_items~)
QueuedJsonConsole(sink) => sink.drain(max_items=max_items) QueuedJsonConsole(sink) => sink.drain(max_items~)
QueuedTextConsole(sink) => sink.drain(max_items=max_items) QueuedTextConsole(sink) => sink.drain(max_items~)
QueuedFile(sink) => sink.drain(max_items=max_items) QueuedFile(sink) => sink.drain(max_items~)
} }
} }
///|
pub fn RuntimeSink::close(self : RuntimeSink) -> Bool { pub fn RuntimeSink::close(self : RuntimeSink) -> Bool {
match self { match self {
Console(_) => true Console(_) => true
@@ -87,6 +112,7 @@ pub fn RuntimeSink::close(self : RuntimeSink) -> Bool {
} }
} }
///|
pub fn RuntimeSink::pending_count(self : RuntimeSink) -> Int { pub fn RuntimeSink::pending_count(self : RuntimeSink) -> Int {
match self { match self {
Console(_) => 0 Console(_) => 0
@@ -100,6 +126,7 @@ pub fn RuntimeSink::pending_count(self : RuntimeSink) -> Int {
} }
} }
///|
pub fn RuntimeSink::dropped_count(self : RuntimeSink) -> Int { pub fn RuntimeSink::dropped_count(self : RuntimeSink) -> Int {
match self { match self {
Console(_) => 0 Console(_) => 0
@@ -113,36 +140,48 @@ pub fn RuntimeSink::dropped_count(self : RuntimeSink) -> Int {
} }
} }
///|
pub type ConfiguredLogger = Logger[RuntimeSink] pub type ConfiguredLogger = Logger[RuntimeSink]
///|
pub fn ConfiguredLogger::flush(self : ConfiguredLogger) -> Int { pub fn ConfiguredLogger::flush(self : ConfiguredLogger) -> Int {
self.sink.flush() self.sink.flush()
} }
pub fn ConfiguredLogger::drain(self : ConfiguredLogger, max_items~ : Int = -1) -> Int { ///|
self.sink.drain(max_items=max_items) pub fn ConfiguredLogger::drain(
self : ConfiguredLogger,
max_items? : Int = -1,
) -> Int {
self.sink.drain(max_items~)
} }
///|
pub fn ConfiguredLogger::close(self : ConfiguredLogger) -> Bool { pub fn ConfiguredLogger::close(self : ConfiguredLogger) -> Bool {
self.sink.close() self.sink.close()
} }
///|
pub fn ConfiguredLogger::pending_count(self : ConfiguredLogger) -> Int { pub fn ConfiguredLogger::pending_count(self : ConfiguredLogger) -> Int {
self.sink.pending_count() self.sink.pending_count()
} }
///|
pub fn ConfiguredLogger::dropped_count(self : ConfiguredLogger) -> Int { pub fn ConfiguredLogger::dropped_count(self : ConfiguredLogger) -> Int {
self.sink.dropped_count() self.sink.dropped_count()
} }
///|
fn build_runtime_sink(config : SinkConfig) -> RuntimeSink { fn build_runtime_sink(config : SinkConfig) -> RuntimeSink {
match config.kind { match config.kind {
SinkKind::Console => RuntimeSink::Console(console_sink()) SinkKind::Console => RuntimeSink::Console(console_sink())
SinkKind::JsonConsole => RuntimeSink::JsonConsole(json_console_sink()) SinkKind::JsonConsole => RuntimeSink::JsonConsole(json_console_sink())
SinkKind::TextConsole => RuntimeSink::TextConsole( SinkKind::TextConsole =>
RuntimeSink::TextConsole(
text_console_sink(config.text_formatter.to_formatter()), text_console_sink(config.text_formatter.to_formatter()),
) )
SinkKind::File => RuntimeSink::File( SinkKind::File =>
RuntimeSink::File(
file_sink( file_sink(
config.path, config.path,
append=config.append, append=config.append,
@@ -156,19 +195,40 @@ fn build_runtime_sink(config : SinkConfig) -> RuntimeSink {
} }
} }
///|
fn apply_queue_config(sink : RuntimeSink, queue : QueueConfig) -> RuntimeSink { fn apply_queue_config(sink : RuntimeSink, queue : QueueConfig) -> RuntimeSink {
match sink { match sink {
Console(inner) => RuntimeSink::QueuedConsole( Console(inner) =>
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow), RuntimeSink::QueuedConsole(
queued_sink(
inner,
max_pending=queue.max_pending,
overflow=queue.overflow,
),
) )
JsonConsole(inner) => RuntimeSink::QueuedJsonConsole( JsonConsole(inner) =>
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow), RuntimeSink::QueuedJsonConsole(
queued_sink(
inner,
max_pending=queue.max_pending,
overflow=queue.overflow,
),
) )
TextConsole(inner) => RuntimeSink::QueuedTextConsole( TextConsole(inner) =>
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow), RuntimeSink::QueuedTextConsole(
queued_sink(
inner,
max_pending=queue.max_pending,
overflow=queue.overflow,
),
) )
File(inner) => RuntimeSink::QueuedFile( File(inner) =>
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow), RuntimeSink::QueuedFile(
queued_sink(
inner,
max_pending=queue.max_pending,
overflow=queue.overflow,
),
) )
QueuedConsole(_) => sink QueuedConsole(_) => sink
QueuedJsonConsole(_) => sink QueuedJsonConsole(_) => sink
@@ -177,16 +237,21 @@ fn apply_queue_config(sink : RuntimeSink, queue : QueueConfig) -> RuntimeSink {
} }
} }
///|
pub fn build_logger(config : LoggerConfig) -> ConfiguredLogger { pub fn build_logger(config : LoggerConfig) -> ConfiguredLogger {
let sink = build_runtime_sink(config.sink) let sink = build_runtime_sink(config.sink)
let actual_sink = match config.queue { let actual_sink = match config.queue {
None => sink None => sink
Some(queue) => apply_queue_config(sink, queue) Some(queue) => apply_queue_config(sink, queue)
} }
Logger::new(actual_sink, min_level=config.min_level, target=config.target) Logger::new(actual_sink, min_level=config.min_level, target=config.target).with_timestamp(
.with_timestamp(enabled=config.timestamp) enabled=config.timestamp,
)
} }
pub fn parse_and_build_logger(input : String) -> ConfiguredLogger raise ConfigError { ///|
pub fn parse_and_build_logger(
input : String,
) -> ConfiguredLogger raise ConfigError {
build_logger(parse_logger_config_text(input)) build_logger(parse_logger_config_text(input))
} }
+78 -29
View File
@@ -1,26 +1,32 @@
///|
pub trait Sink { pub trait Sink {
write(Self, Record) -> Unit fn write(Self, Record) -> Unit
} }
///|
pub struct ConsoleSink { pub struct ConsoleSink {
_dummy : Unit _dummy : Unit
} }
///|
pub fn console_sink() -> ConsoleSink { pub fn console_sink() -> ConsoleSink {
{ _dummy: () } { _dummy: () }
} }
pub impl Sink for ConsoleSink with write(self, rec) { ///|
pub impl Sink for ConsoleSink with fn write(self, rec) {
ignore(self) ignore(self)
println(format_text(rec)) println(format_text(rec))
} }
///|
pub struct ContextSink[S] { pub struct ContextSink[S] {
sink : S sink : S
context_fields : Array[Field] context_fields : Array[Field]
} }
pub impl[S : Sink] Sink for ContextSink[S] with write(self, rec) { ///|
pub impl[S : Sink] Sink for ContextSink[S] with fn write(self, rec) {
let merged = if self.context_fields.length() == 0 { let merged = if self.context_fields.length() == 0 {
rec.fields rec.fields
} else if rec.fields.length() == 0 { } else if rec.fields.length() == 0 {
@@ -31,42 +37,51 @@ pub impl[S : Sink] Sink for ContextSink[S] with write(self, rec) {
self.sink.write(rec.with_fields(merged)) self.sink.write(rec.with_fields(merged))
} }
///|
pub struct JsonConsoleSink { pub struct JsonConsoleSink {
_dummy : Unit _dummy : Unit
} }
///|
pub fn json_console_sink() -> JsonConsoleSink { pub fn json_console_sink() -> JsonConsoleSink {
{ _dummy: () } { _dummy: () }
} }
pub impl Sink for JsonConsoleSink with write(self, rec) { ///|
pub impl Sink for JsonConsoleSink with fn write(self, rec) {
ignore(self) ignore(self)
println(format_json(rec)) println(format_json(rec))
} }
///|
pub struct FormattedConsoleSink { pub struct FormattedConsoleSink {
formatter : RecordFormatter formatter : RecordFormatter
} }
pub fn formatted_console_sink(formatter : RecordFormatter) -> FormattedConsoleSink { ///|
pub fn formatted_console_sink(
formatter : RecordFormatter,
) -> FormattedConsoleSink {
{ formatter, } { formatter, }
} }
///|
pub fn text_console_sink(formatter : TextFormatter) -> FormattedConsoleSink { pub fn text_console_sink(formatter : TextFormatter) -> FormattedConsoleSink {
formatted_console_sink(fn(rec) { formatted_console_sink(fn(rec) { format_text(rec, formatter~) })
format_text(rec, formatter=formatter)
})
} }
pub impl Sink for FormattedConsoleSink with write(self, rec) { ///|
pub impl Sink for FormattedConsoleSink with fn write(self, rec) {
println((self.formatter)(rec)) println((self.formatter)(rec))
} }
///|
pub struct FormattedCallbackSink { pub struct FormattedCallbackSink {
formatter : RecordFormatter formatter : RecordFormatter
callback : (String) -> Unit callback : (String) -> Unit
} }
///|
pub fn formatted_callback_sink( pub fn formatted_callback_sink(
formatter : RecordFormatter, formatter : RecordFormatter,
callback : (String) -> Unit, callback : (String) -> Unit,
@@ -74,54 +89,63 @@ pub fn formatted_callback_sink(
{ formatter, callback } { formatter, callback }
} }
///|
pub fn text_callback_sink( pub fn text_callback_sink(
formatter : TextFormatter, formatter : TextFormatter,
callback : (String) -> Unit, callback : (String) -> Unit,
) -> FormattedCallbackSink { ) -> FormattedCallbackSink {
formatted_callback_sink(fn(rec) { formatted_callback_sink(fn(rec) { format_text(rec, formatter~) }, callback)
format_text(rec, formatter=formatter)
}, callback)
} }
pub impl Sink for FormattedCallbackSink with write(self, rec) { ///|
pub impl Sink for FormattedCallbackSink with fn write(self, rec) {
(self.callback)((self.formatter)(rec)) (self.callback)((self.formatter)(rec))
} }
///|
pub struct FanoutSink[A, B] { pub struct FanoutSink[A, B] {
left : A left : A
right : B right : B
} }
///|
pub fn[A, B] fanout_sink(left : A, right : B) -> FanoutSink[A, B] { pub fn[A, B] fanout_sink(left : A, right : B) -> FanoutSink[A, B] {
{ left, right } { left, right }
} }
pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with write(self, rec) { ///|
pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with fn write(self, rec) {
self.left.write(rec) self.left.write(rec)
self.right.write(rec.copy()) self.right.write(rec.copy())
} }
///|
pub struct SplitSink[A, B] { pub struct SplitSink[A, B] {
left : A left : A
right : B right : B
predicate : (Record) -> Bool predicate : (Record) -> Bool
} }
pub fn[A, B] split_sink(left : A, right : B, predicate : (Record) -> Bool) -> SplitSink[A, B] { ///|
pub fn[A, B] split_sink(
left : A,
right : B,
predicate : (Record) -> Bool,
) -> SplitSink[A, B] {
{ left, right, predicate } { left, right, predicate }
} }
///|
pub fn[A, B] split_by_level( pub fn[A, B] split_by_level(
left : A, left : A,
right : B, right : B,
min_level~ : Level = Level::Warn, min_level? : Level = Level::Warn,
) -> SplitSink[A, B] { ) -> SplitSink[A, B] {
split_sink(left, right, fn(rec) { split_sink(left, right, fn(rec) { rec.level.enabled(min_level) })
rec.level.enabled(min_level)
})
} }
pub impl[A : Sink, B : Sink] Sink for SplitSink[A, B] with write(self, rec) { ///|
pub impl[A : Sink, B : Sink] Sink for SplitSink[A, B] with fn write(self, rec) {
if (self.predicate)(rec) { if (self.predicate)(rec) {
self.left.write(rec) self.left.write(rec)
} else { } else {
@@ -129,33 +153,40 @@ pub impl[A : Sink, B : Sink] Sink for SplitSink[A, B] with write(self, rec) {
} }
} }
///|
pub struct CallbackSink { pub struct CallbackSink {
callback : (Record) -> Unit callback : (Record) -> Unit
} }
///|
pub fn callback_sink(callback : (Record) -> Unit) -> CallbackSink { pub fn callback_sink(callback : (Record) -> Unit) -> CallbackSink {
{ callback, } { callback, }
} }
pub impl Sink for CallbackSink with write(self, rec) { ///|
pub impl Sink for CallbackSink with fn write(self, rec) {
(self.callback)(rec) (self.callback)(rec)
} }
///|
pub struct BufferedSink[S] { pub struct BufferedSink[S] {
sink : S sink : S
buffer : Ref[Array[Record]] buffer : Ref[Array[Record]]
flush_limit : Int flush_limit : Int
} }
pub fn[S] buffered_sink(sink : S, flush_limit~ : Int = 1) -> BufferedSink[S] { ///|
pub fn[S] buffered_sink(sink : S, flush_limit? : Int = 1) -> BufferedSink[S] {
let actual_limit = if flush_limit <= 0 { 1 } else { flush_limit } let actual_limit = if flush_limit <= 0 { 1 } else { flush_limit }
{ sink, buffer: Ref([]), flush_limit: actual_limit } { sink, buffer: Ref([]), flush_limit: actual_limit }
} }
///|
pub fn[S] BufferedSink::pending_count(self : BufferedSink[S]) -> Int { pub fn[S] BufferedSink::pending_count(self : BufferedSink[S]) -> Int {
self.buffer.val.length() self.buffer.val.length()
} }
///|
pub fn[S : Sink] BufferedSink::flush(self : BufferedSink[S]) -> Unit { pub fn[S : Sink] BufferedSink::flush(self : BufferedSink[S]) -> Unit {
if self.buffer.val.length() == 0 { if self.buffer.val.length() == 0 {
() ()
@@ -168,15 +199,18 @@ pub fn[S : Sink] BufferedSink::flush(self : BufferedSink[S]) -> Unit {
} }
} }
pub impl[S : Sink] Sink for BufferedSink[S] with write(self, rec) { ///|
pub impl[S : Sink] Sink for BufferedSink[S] with fn write(self, rec) {
self.buffer.val.push(rec) self.buffer.val.push(rec)
if self.buffer.val.length() >= self.flush_limit { if self.buffer.val.length() >= self.flush_limit {
self.flush() self.flush()
} }
} }
///|
pub type QueueOverflowPolicy = @utils.QueueOverflowPolicy pub type QueueOverflowPolicy = @utils.QueueOverflowPolicy
///|
pub struct QueuedSink[S] { pub struct QueuedSink[S] {
sink : S sink : S
queue : @queue.Queue[Record] queue : @queue.Queue[Record]
@@ -185,10 +219,11 @@ pub struct QueuedSink[S] {
dropped_count : Ref[Int] dropped_count : Ref[Int]
} }
///|
pub fn[S] queued_sink( pub fn[S] queued_sink(
sink : S, sink : S,
max_pending~ : Int = 0, max_pending? : Int = 0,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest, overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> QueuedSink[S] { ) -> QueuedSink[S] {
{ {
sink, sink,
@@ -199,15 +234,21 @@ pub fn[S] queued_sink(
} }
} }
///|
pub fn[S] QueuedSink::pending_count(self : QueuedSink[S]) -> Int { pub fn[S] QueuedSink::pending_count(self : QueuedSink[S]) -> Int {
self.queue.length() self.queue.length()
} }
///|
pub fn[S] QueuedSink::dropped_count(self : QueuedSink[S]) -> Int { pub fn[S] QueuedSink::dropped_count(self : QueuedSink[S]) -> Int {
self.dropped_count.val self.dropped_count.val
} }
pub fn[S : Sink] QueuedSink::drain(self : QueuedSink[S], max_items~ : Int = -1) -> Int { ///|
pub fn[S : Sink] QueuedSink::drain(
self : QueuedSink[S],
max_items? : Int = -1,
) -> Int {
if max_items == 0 { if max_items == 0 {
return 0 return 0
} }
@@ -225,11 +266,13 @@ pub fn[S : Sink] QueuedSink::drain(self : QueuedSink[S], max_items~ : Int = -1)
} }
} }
///|
pub fn[S : Sink] QueuedSink::flush(self : QueuedSink[S]) -> Int { pub fn[S : Sink] QueuedSink::flush(self : QueuedSink[S]) -> Int {
self.drain() self.drain()
} }
pub impl[S] Sink for QueuedSink[S] with write(self, rec) { ///|
pub impl[S] Sink for QueuedSink[S] with fn write(self, rec) {
let full = self.max_pending > 0 && self.pending_count() >= self.max_pending let full = self.max_pending > 0 && self.pending_count() >= self.max_pending
if !full { if !full {
self.queue.push(rec) self.queue.push(rec)
@@ -245,30 +288,36 @@ pub impl[S] Sink for QueuedSink[S] with write(self, rec) {
} }
} }
///|
pub struct FilterSink[S] { pub struct FilterSink[S] {
sink : S sink : S
predicate : (Record) -> Bool predicate : (Record) -> Bool
} }
///|
pub fn[S] filter_sink(sink : S, predicate : (Record) -> Bool) -> FilterSink[S] { pub fn[S] filter_sink(sink : S, predicate : (Record) -> Bool) -> FilterSink[S] {
{ sink, predicate } { sink, predicate }
} }
pub impl[S : Sink] Sink for FilterSink[S] with write(self, rec) { ///|
pub impl[S : Sink] Sink for FilterSink[S] with fn write(self, rec) {
if (self.predicate)(rec) { if (self.predicate)(rec) {
self.sink.write(rec) self.sink.write(rec)
} }
} }
///|
pub struct PatchSink[S] { pub struct PatchSink[S] {
sink : S sink : S
patch : RecordPatch patch : RecordPatch
} }
///|
pub fn[S] patch_sink(sink : S, patch : RecordPatch) -> PatchSink[S] { pub fn[S] patch_sink(sink : S, patch : RecordPatch) -> PatchSink[S] {
{ sink, patch } { sink, patch }
} }
pub impl[S : Sink] Sink for PatchSink[S] with write(self, rec) { ///|
pub impl[S : Sink] Sink for PatchSink[S] with fn write(self, rec) {
self.sink.write((self.patch)(rec)) self.sink.write((self.patch)(rec))
} }
+68 -22
View File
@@ -1,3 +1,4 @@
///|
pub struct FileSink { pub struct FileSink {
path : String path : String
append : Ref[Bool] append : Ref[Bool]
@@ -14,28 +15,32 @@ pub struct FileSink {
rotation_failures : Ref[Int] rotation_failures : Ref[Int]
} }
///|
pub type FileRotation = @utils.FileRotation pub type FileRotation = @utils.FileRotation
///|
pub type FileSinkState = @utils.FileSinkState pub type FileSinkState = @utils.FileSinkState
///|
pub type FileSinkPolicy = @utils.FileSinkPolicy pub type FileSinkPolicy = @utils.FileSinkPolicy
pub fn file_rotation(max_bytes : Int, max_backups~ : Int = 1) -> FileRotation { ///|
@utils.file_rotation(max_bytes, max_backups=max_backups) pub fn file_rotation(max_bytes : Int, max_backups? : Int = 1) -> FileRotation {
@utils.file_rotation(max_bytes, max_backups~)
} }
///|
pub fn native_files_supported() -> Bool { pub fn native_files_supported() -> Bool {
native_files_supported_internal() native_files_supported_internal()
} }
///|
pub fn file_sink( pub fn file_sink(
path : String, path : String,
append~ : Bool = true, append? : Bool = true,
auto_flush~ : Bool = true, auto_flush? : Bool = true,
rotation~ : FileRotation? = None, rotation? : FileRotation? = None,
formatter~ : RecordFormatter = fn(rec) { formatter? : RecordFormatter = fn(rec) { format_text(rec) },
format_text(rec)
},
) -> FileSink { ) -> FileSink {
let handle = open_file_handle_internal(path, append) let handle = open_file_handle_internal(path, append)
{ {
@@ -55,10 +60,12 @@ pub fn file_sink(
} }
} }
///|
pub fn FileSink::is_available(self : FileSink) -> Bool { pub fn FileSink::is_available(self : FileSink) -> Bool {
self.handle.val is Some(_) self.handle.val is Some(_)
} }
///|
pub fn FileSink::flush(self : FileSink) -> Bool { pub fn FileSink::flush(self : FileSink) -> Bool {
match self.handle.val { match self.handle.val {
None => false None => false
@@ -72,48 +79,62 @@ pub fn FileSink::flush(self : FileSink) -> Bool {
} }
} }
///|
pub fn FileSink::append_mode(self : FileSink) -> Bool { pub fn FileSink::append_mode(self : FileSink) -> Bool {
self.append.val self.append.val
} }
///|
pub fn FileSink::set_append_mode(self : FileSink, append : Bool) -> Unit { pub fn FileSink::set_append_mode(self : FileSink, append : Bool) -> Unit {
self.append.val = append self.append.val = append
} }
///|
pub fn FileSink::path(self : FileSink) -> String { pub fn FileSink::path(self : FileSink) -> String {
self.path self.path
} }
///|
pub fn FileSink::auto_flush_enabled(self : FileSink) -> Bool { pub fn FileSink::auto_flush_enabled(self : FileSink) -> Bool {
self.auto_flush.val self.auto_flush.val
} }
///|
pub fn FileSink::rotation_enabled(self : FileSink) -> Bool { pub fn FileSink::rotation_enabled(self : FileSink) -> Bool {
self.rotation.val is Some(_) self.rotation.val is Some(_)
} }
///|
pub fn FileSink::rotation_config(self : FileSink) -> FileRotation? { pub fn FileSink::rotation_config(self : FileSink) -> FileRotation? {
self.rotation.val self.rotation.val
} }
///|
pub fn FileSink::set_auto_flush(self : FileSink, enabled : Bool) -> Unit { pub fn FileSink::set_auto_flush(self : FileSink, enabled : Bool) -> Unit {
self.auto_flush.val = enabled self.auto_flush.val = enabled
} }
///|
pub fn FileSink::set_policy(self : FileSink, policy : FileSinkPolicy) -> Unit { pub fn FileSink::set_policy(self : FileSink, policy : FileSinkPolicy) -> Unit {
self.append.val = policy.append self.append.val = policy.append
self.auto_flush.val = policy.auto_flush self.auto_flush.val = policy.auto_flush
self.rotation.val = policy.rotation self.rotation.val = policy.rotation
} }
pub fn FileSink::set_rotation(self : FileSink, rotation : FileRotation?) -> Unit { ///|
pub fn FileSink::set_rotation(
self : FileSink,
rotation : FileRotation?,
) -> Unit {
self.rotation.val = rotation self.rotation.val = rotation
} }
///|
pub fn FileSink::clear_rotation(self : FileSink) -> Unit { pub fn FileSink::clear_rotation(self : FileSink) -> Unit {
self.rotation.val = None self.rotation.val = None
} }
///|
pub fn FileSink::close(self : FileSink) -> Bool { pub fn FileSink::close(self : FileSink) -> Bool {
match self.handle.val { match self.handle.val {
None => false None => false
@@ -125,22 +146,27 @@ pub fn FileSink::close(self : FileSink) -> Bool {
} }
} }
///|
pub fn FileSink::rotation_failures(self : FileSink) -> Int { pub fn FileSink::rotation_failures(self : FileSink) -> Int {
self.rotation_failures.val self.rotation_failures.val
} }
///|
pub fn FileSink::open_failures(self : FileSink) -> Int { pub fn FileSink::open_failures(self : FileSink) -> Int {
self.open_failures.val self.open_failures.val
} }
///|
pub fn FileSink::write_failures(self : FileSink) -> Int { pub fn FileSink::write_failures(self : FileSink) -> Int {
self.write_failures.val self.write_failures.val
} }
///|
pub fn FileSink::flush_failures(self : FileSink) -> Int { pub fn FileSink::flush_failures(self : FileSink) -> Int {
self.flush_failures.val self.flush_failures.val
} }
///|
pub fn FileSink::reset_failure_counters(self : FileSink) -> Unit { pub fn FileSink::reset_failure_counters(self : FileSink) -> Unit {
self.open_failures.val = 0 self.open_failures.val = 0
self.write_failures.val = 0 self.write_failures.val = 0
@@ -148,12 +174,14 @@ pub fn FileSink::reset_failure_counters(self : FileSink) -> Unit {
self.rotation_failures.val = 0 self.rotation_failures.val = 0
} }
///|
pub fn FileSink::reset_policy(self : FileSink) -> Unit { pub fn FileSink::reset_policy(self : FileSink) -> Unit {
self.append.val = self.default_append self.append.val = self.default_append
self.auto_flush.val = self.default_auto_flush self.auto_flush.val = self.default_auto_flush
self.rotation.val = self.default_rotation self.rotation.val = self.default_rotation
} }
///|
pub fn FileSink::policy(self : FileSink) -> FileSinkPolicy { pub fn FileSink::policy(self : FileSink) -> FileSinkPolicy {
FileSinkPolicy::new( FileSinkPolicy::new(
append=self.append.val, append=self.append.val,
@@ -162,6 +190,7 @@ pub fn FileSink::policy(self : FileSink) -> FileSinkPolicy {
) )
} }
///|
pub fn FileSink::default_policy(self : FileSink) -> FileSinkPolicy { pub fn FileSink::default_policy(self : FileSink) -> FileSinkPolicy {
FileSinkPolicy::new( FileSinkPolicy::new(
append=self.default_append, append=self.default_append,
@@ -170,6 +199,7 @@ pub fn FileSink::default_policy(self : FileSink) -> FileSinkPolicy {
) )
} }
///|
pub fn FileSink::policy_matches_default(self : FileSink) -> Bool { pub fn FileSink::policy_matches_default(self : FileSink) -> Bool {
let current = self.policy() let current = self.policy()
let default = self.default_policy() let default = self.default_policy()
@@ -178,14 +208,20 @@ pub fn FileSink::policy_matches_default(self : FileSink) -> Bool {
policy_rotation_equals_internal(current.rotation, default.rotation) policy_rotation_equals_internal(current.rotation, default.rotation)
} }
fn policy_rotation_equals_internal(left : FileRotation?, right : FileRotation?) -> Bool { ///|
fn policy_rotation_equals_internal(
left : FileRotation?,
right : FileRotation?,
) -> Bool {
match (left, right) { match (left, right) {
(None, None) => true (None, None) => true
(Some(a), Some(b)) => a.max_bytes == b.max_bytes && a.max_backups == b.max_backups (Some(a), Some(b)) =>
a.max_bytes == b.max_bytes && a.max_backups == b.max_backups
_ => false _ => false
} }
} }
///|
pub fn FileSink::state(self : FileSink) -> FileSinkState { pub fn FileSink::state(self : FileSink) -> FileSinkState {
FileSinkState::new( FileSinkState::new(
self.path, self.path,
@@ -200,7 +236,8 @@ pub fn FileSink::state(self : FileSink) -> FileSinkState {
) )
} }
pub fn FileSink::reopen(self : FileSink, append~ : Bool? = None) -> Bool { ///|
pub fn FileSink::reopen(self : FileSink, append? : Bool? = None) -> Bool {
let append_mode = append.unwrap_or(self.append.val) let append_mode = append.unwrap_or(self.append.val)
self.append.val = append_mode self.append.val = append_mode
match self.handle.val { match self.handle.val {
@@ -220,22 +257,27 @@ pub fn FileSink::reopen(self : FileSink, append~ : Bool? = None) -> Bool {
} }
} }
///|
pub fn FileSink::reopen_with_current_policy(self : FileSink) -> Bool { pub fn FileSink::reopen_with_current_policy(self : FileSink) -> Bool {
self.reopen() self.reopen()
} }
///|
pub fn FileSink::reopen_append(self : FileSink) -> Bool { pub fn FileSink::reopen_append(self : FileSink) -> Bool {
self.reopen(append=Some(true)) self.reopen(append=Some(true))
} }
///|
pub fn FileSink::reopen_truncate(self : FileSink) -> Bool { pub fn FileSink::reopen_truncate(self : FileSink) -> Bool {
self.reopen(append=Some(false)) self.reopen(append=Some(false))
} }
///|
fn rotated_file_path(path : String, index : Int) -> String { fn rotated_file_path(path : String, index : Int) -> String {
"\{path}.\{index}" "\{path}.\{index}"
} }
///|
fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool { fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
let closed = match sink.handle.val { let closed = match sink.handle.val {
None => true None => true
@@ -249,7 +291,9 @@ fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
return false return false
} }
if rotation.max_backups > 0 { if rotation.max_backups > 0 {
ignore(remove_file_internal(rotated_file_path(sink.path, rotation.max_backups))) ignore(
remove_file_internal(rotated_file_path(sink.path, rotation.max_backups)),
)
for index = rotation.max_backups - 1; index >= 1; { for index = rotation.max_backups - 1; index >= 1; {
let from_path = rotated_file_path(sink.path, index) let from_path = rotated_file_path(sink.path, index)
let to_path = rotated_file_path(sink.path, index + 1) let to_path = rotated_file_path(sink.path, index + 1)
@@ -264,10 +308,12 @@ fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
sink.handle.val is Some(_) sink.handle.val is Some(_)
} }
///|
fn rotate_if_needed_internal(sink : FileSink, next_line_bytes : Int) -> Bool { fn rotate_if_needed_internal(sink : FileSink, next_line_bytes : Int) -> Bool {
match sink.rotation.val { match sink.rotation.val {
None => true None => true
Some(rotation) => match sink.handle.val { Some(rotation) =>
match sink.handle.val {
None => false None => false
Some(handle) => { Some(handle) => {
let size = file_size_internal(handle) let size = file_size_internal(handle)
@@ -285,19 +331,19 @@ fn rotate_if_needed_internal(sink : FileSink, next_line_bytes : Int) -> Bool {
} }
} }
pub impl Sink for FileSink with write(self, rec) { ///|
pub impl Sink for FileSink with fn write(self, rec) {
match self.handle.val { match self.handle.val {
None => { None => self.write_failures.val += 1
self.write_failures.val += 1
}
Some(_) => { Some(_) => {
let line = "\{(self.formatter)(rec)}\n" let line = "\{(self.formatter)(rec)}\n"
let can_write = rotate_if_needed_internal(self, string_byte_length_internal(line)) let can_write = rotate_if_needed_internal(
self,
string_byte_length_internal(line),
)
if can_write { if can_write {
match self.handle.val { match self.handle.val {
None => { None => self.write_failures.val += 1
self.write_failures.val += 1
}
Some(active) => { Some(active) => {
let wrote = write_file_handle_internal(active, line) let wrote = write_file_handle_internal(active, line)
if wrote { if wrote {
+182 -82
View File
@@ -1,7 +1,9 @@
///|
pub(all) suberror ConfigError { pub(all) suberror ConfigError {
InvalidConfig(String) InvalidConfig(String)
} }
///|
pub(all) enum SinkKind { pub(all) enum SinkKind {
Console Console
JsonConsole JsonConsole
@@ -9,6 +11,7 @@ pub(all) enum SinkKind {
File File
} }
///|
pub struct TextFormatterConfig { pub struct TextFormatterConfig {
show_timestamp : Bool show_timestamp : Bool
show_level : Bool show_level : Bool
@@ -25,20 +28,21 @@ pub struct TextFormatterConfig {
style_tags : Map[String, TextStyle] style_tags : Map[String, TextStyle]
} }
///|
pub fn TextFormatterConfig::new( pub fn TextFormatterConfig::new(
show_timestamp~ : Bool = true, show_timestamp? : Bool = true,
show_level~ : Bool = true, show_level? : Bool = true,
show_target~ : Bool = true, show_target? : Bool = true,
show_fields~ : Bool = true, show_fields? : Bool = true,
separator~ : String = " ", separator? : String = " ",
field_separator~ : String = " ", field_separator? : String = " ",
template~ : String = "", template? : String = "",
color_mode~ : ColorMode = ColorMode::Never, color_mode? : ColorMode = ColorMode::Never,
color_support~ : ColorSupport = ColorSupport::TrueColor, color_support? : ColorSupport = ColorSupport::TrueColor,
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full, style_markup? : StyleMarkupMode = StyleMarkupMode::Full,
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled, target_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled, fields_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
style_tags~ : Map[String, TextStyle] = {}, style_tags? : Map[String, TextStyle] = {},
) -> TextFormatterConfig { ) -> TextFormatterConfig {
{ {
show_timestamp, show_timestamp,
@@ -57,15 +61,21 @@ pub fn TextFormatterConfig::new(
} }
} }
fn style_tag_registry_from_config(style_tags : Map[String, TextStyle]) -> StyleTagRegistry { ///|
fn style_tag_registry_from_config(
style_tags : Map[String, TextStyle],
) -> StyleTagRegistry {
let registry = style_tag_registry() let registry = style_tag_registry()
for name, style in style_tags { for name, style in style_tags {
ignore(registry.set_tag(name, style=style)) ignore(registry.set_tag(name, style~))
} }
registry registry
} }
pub fn TextFormatterConfig::to_formatter(self : TextFormatterConfig) -> TextFormatter { ///|
pub fn TextFormatterConfig::to_formatter(
self : TextFormatterConfig,
) -> TextFormatter {
text_formatter( text_formatter(
show_timestamp=self.show_timestamp, show_timestamp=self.show_timestamp,
show_level=self.show_level, show_level=self.show_level,
@@ -87,18 +97,21 @@ pub fn TextFormatterConfig::to_formatter(self : TextFormatterConfig) -> TextForm
) )
} }
///|
pub struct QueueConfig { pub struct QueueConfig {
max_pending : Int max_pending : Int
overflow : QueueOverflowPolicy overflow : QueueOverflowPolicy
} }
///|
pub fn QueueConfig::new( pub fn QueueConfig::new(
max_pending : Int, max_pending : Int,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest, overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> QueueConfig { ) -> QueueConfig {
{ max_pending, overflow } { max_pending, overflow }
} }
///|
pub struct SinkConfig { pub struct SinkConfig {
kind : SinkKind kind : SinkKind
path : String path : String
@@ -108,24 +121,19 @@ pub struct SinkConfig {
text_formatter : TextFormatterConfig text_formatter : TextFormatterConfig
} }
///|
pub fn SinkConfig::new( pub fn SinkConfig::new(
kind~ : SinkKind = SinkKind::Console, kind? : SinkKind = SinkKind::Console,
path~ : String = "", path? : String = "",
append~ : Bool = true, append? : Bool = true,
auto_flush~ : Bool = true, auto_flush? : Bool = true,
rotation~ : FileRotation? = None, rotation? : FileRotation? = None,
text_formatter~ : TextFormatterConfig = default_text_formatter_config(), text_formatter? : TextFormatterConfig = default_text_formatter_config(),
) -> SinkConfig { ) -> SinkConfig {
{ { kind, path, append, auto_flush, rotation, text_formatter }
kind,
path,
append,
auto_flush,
rotation,
text_formatter,
}
} }
///|
pub struct LoggerConfig { pub struct LoggerConfig {
min_level : @core.Level min_level : @core.Level
target : String target : String
@@ -134,34 +142,33 @@ pub struct LoggerConfig {
queue : QueueConfig? queue : QueueConfig?
} }
///|
pub fn LoggerConfig::new( pub fn LoggerConfig::new(
min_level~ : @core.Level = @core.Level::Info, min_level? : @core.Level = @core.Level::Info,
target~ : String = "", target? : String = "",
timestamp~ : Bool = false, timestamp? : Bool = false,
sink~ : SinkConfig = default_sink_config(), sink? : SinkConfig = default_sink_config(),
queue~ : QueueConfig? = None, queue? : QueueConfig? = None,
) -> LoggerConfig { ) -> LoggerConfig {
{ { min_level, target, timestamp, sink, queue }
min_level,
target,
timestamp,
sink,
queue,
}
} }
///|
pub fn default_text_formatter_config() -> TextFormatterConfig { pub fn default_text_formatter_config() -> TextFormatterConfig {
TextFormatterConfig::new() TextFormatterConfig::new()
} }
///|
pub fn default_sink_config() -> SinkConfig { pub fn default_sink_config() -> SinkConfig {
SinkConfig::new() SinkConfig::new()
} }
///|
pub fn default_logger_config() -> LoggerConfig { pub fn default_logger_config() -> LoggerConfig {
LoggerConfig::new() LoggerConfig::new()
} }
///|
fn expect_object( fn expect_object(
value : @json_parser.JsonValue, value : @json_parser.JsonValue,
context : String, context : String,
@@ -172,33 +179,40 @@ fn expect_object(
} }
} }
///|
fn get_string( fn get_string(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, @json_parser.JsonValue],
key : String, key : String,
default~ : String = "", default? : String = "",
) -> String raise ConfigError { ) -> String raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => default None => default
Some(value) => match value.as_string() { Some(value) =>
match value.as_string() {
Some(text) => text Some(text) => text
None => raise ConfigError::InvalidConfig("Expected string at key " + key) None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
} }
} }
} }
///|
fn get_optional_string( fn get_optional_string(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, @json_parser.JsonValue],
key : String, key : String,
) -> String? raise ConfigError { ) -> String? raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => None None => None
Some(value) => match value.as_string() { Some(value) =>
match value.as_string() {
Some(text) => Some(text) Some(text) => Some(text)
None => raise ConfigError::InvalidConfig("Expected string at key " + key) None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
} }
} }
} }
///|
fn get_bool( fn get_bool(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, @json_parser.JsonValue],
key : String, key : String,
@@ -206,13 +220,15 @@ fn get_bool(
) -> Bool raise ConfigError { ) -> Bool raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => default None => default
Some(value) => match value.as_bool() { Some(value) =>
match value.as_bool() {
Some(flag) => flag Some(flag) => flag
None => raise ConfigError::InvalidConfig("Expected bool at key " + key) None => raise ConfigError::InvalidConfig("Expected bool at key " + key)
} }
} }
} }
///|
fn get_int( fn get_int(
obj : Map[String, @json_parser.JsonValue], obj : Map[String, @json_parser.JsonValue],
key : String, key : String,
@@ -220,13 +236,16 @@ fn get_int(
) -> Int raise ConfigError { ) -> Int raise ConfigError {
match obj.get(key) { match obj.get(key) {
None => default None => default
Some(value) => match value.as_number() { Some(value) =>
match value.as_number() {
Some(number) => number.to_int() Some(number) => number.to_int()
None => raise ConfigError::InvalidConfig("Expected number at key " + key) None =>
raise ConfigError::InvalidConfig("Expected number at key " + key)
} }
} }
} }
///|
fn parse_level(name : String) -> @core.Level raise ConfigError { fn parse_level(name : String) -> @core.Level raise ConfigError {
match name.to_upper() { match name.to_upper() {
"TRACE" => @core.Level::Trace "TRACE" => @core.Level::Trace
@@ -238,15 +257,20 @@ fn parse_level(name : String) -> @core.Level raise ConfigError {
} }
} }
///|
fn parse_overflow(name : String) -> QueueOverflowPolicy raise ConfigError { fn parse_overflow(name : String) -> QueueOverflowPolicy raise ConfigError {
match name.to_upper() { match name.to_upper() {
"DROPNEWEST" => QueueOverflowPolicy::DropNewest "DROPNEWEST" => QueueOverflowPolicy::DropNewest
"DROPPOLDEST" => QueueOverflowPolicy::DropOldest "DROPPOLDEST" => QueueOverflowPolicy::DropOldest
"DROPOLDEST" => QueueOverflowPolicy::DropOldest "DROPOLDEST" => QueueOverflowPolicy::DropOldest
_ => raise ConfigError::InvalidConfig("Unsupported queue overflow policy: " + name) _ =>
raise ConfigError::InvalidConfig(
"Unsupported queue overflow policy: " + name,
)
} }
} }
///|
fn parse_sink_kind(name : String) -> SinkKind raise ConfigError { fn parse_sink_kind(name : String) -> SinkKind raise ConfigError {
match name.to_upper() { match name.to_upper() {
"CONSOLE" => SinkKind::Console "CONSOLE" => SinkKind::Console
@@ -259,6 +283,7 @@ fn parse_sink_kind(name : String) -> SinkKind raise ConfigError {
} }
} }
///|
fn parse_color_mode(name : String) -> ColorMode raise ConfigError { fn parse_color_mode(name : String) -> ColorMode raise ConfigError {
match name.to_upper() { match name.to_upper() {
"NEVER" => ColorMode::Never "NEVER" => ColorMode::Never
@@ -268,6 +293,7 @@ fn parse_color_mode(name : String) -> ColorMode raise ConfigError {
} }
} }
///|
fn parse_color_support(name : String) -> ColorSupport raise ConfigError { fn parse_color_support(name : String) -> ColorSupport raise ConfigError {
match name.to_upper() { match name.to_upper() {
"BASIC" => ColorSupport::Basic "BASIC" => ColorSupport::Basic
@@ -276,15 +302,18 @@ fn parse_color_support(name : String) -> ColorSupport raise ConfigError {
} }
} }
///|
fn parse_style_markup_mode(name : String) -> StyleMarkupMode raise ConfigError { fn parse_style_markup_mode(name : String) -> StyleMarkupMode raise ConfigError {
match name.to_upper() { match name.to_upper() {
"DISABLED" => StyleMarkupMode::Disabled "DISABLED" => StyleMarkupMode::Disabled
"BUILTIN" => StyleMarkupMode::Builtin "BUILTIN" => StyleMarkupMode::Builtin
"FULL" => StyleMarkupMode::Full "FULL" => StyleMarkupMode::Full
_ => raise ConfigError::InvalidConfig("Unsupported style markup mode: " + name) _ =>
raise ConfigError::InvalidConfig("Unsupported style markup mode: " + name)
} }
} }
///|
fn sink_kind_label(kind : SinkKind) -> String { fn sink_kind_label(kind : SinkKind) -> String {
match kind { match kind {
SinkKind::Console => "console" SinkKind::Console => "console"
@@ -294,7 +323,10 @@ fn sink_kind_label(kind : SinkKind) -> String {
} }
} }
fn parse_text_formatter_config(value : @json_parser.JsonValue) -> TextFormatterConfig raise ConfigError { ///|
fn parse_text_formatter_config(
value : @json_parser.JsonValue,
) -> TextFormatterConfig raise ConfigError {
let obj = expect_object(value, "text_formatter") let obj = expect_object(value, "text_formatter")
TextFormatterConfig::new( TextFormatterConfig::new(
show_timestamp=get_bool(obj, "show_timestamp", default=true), show_timestamp=get_bool(obj, "show_timestamp", default=true),
@@ -305,10 +337,18 @@ fn parse_text_formatter_config(value : @json_parser.JsonValue) -> TextFormatterC
field_separator=get_string(obj, "field_separator", default=" "), field_separator=get_string(obj, "field_separator", default=" "),
template=get_string(obj, "template", default=""), template=get_string(obj, "template", default=""),
color_mode=parse_color_mode(get_string(obj, "color_mode", default="never")), color_mode=parse_color_mode(get_string(obj, "color_mode", default="never")),
color_support=parse_color_support(get_string(obj, "color_support", default="truecolor")), color_support=parse_color_support(
style_markup=parse_style_markup_mode(get_string(obj, "style_markup", default="full")), get_string(obj, "color_support", default="truecolor"),
target_style_markup=parse_style_markup_mode(get_string(obj, "target_style_markup", default="disabled")), ),
fields_style_markup=parse_style_markup_mode(get_string(obj, "fields_style_markup", default="disabled")), style_markup=parse_style_markup_mode(
get_string(obj, "style_markup", default="full"),
),
target_style_markup=parse_style_markup_mode(
get_string(obj, "target_style_markup", default="disabled"),
),
fields_style_markup=parse_style_markup_mode(
get_string(obj, "fields_style_markup", default="disabled"),
),
style_tags=match obj.get("style_tags") { style_tags=match obj.get("style_tags") {
None => {} None => {}
Some(inner) => parse_style_tags_config(inner) Some(inner) => parse_style_tags_config(inner)
@@ -316,6 +356,7 @@ fn parse_text_formatter_config(value : @json_parser.JsonValue) -> TextFormatterC
) )
} }
///|
fn parse_text_style_config( fn parse_text_style_config(
value : @json_parser.JsonValue, value : @json_parser.JsonValue,
context : String, context : String,
@@ -331,16 +372,25 @@ fn parse_text_style_config(
) )
} }
fn parse_style_tags_config(value : @json_parser.JsonValue) -> Map[String, TextStyle] raise ConfigError { ///|
fn parse_style_tags_config(
value : @json_parser.JsonValue,
) -> Map[String, TextStyle] raise ConfigError {
let obj = expect_object(value, "text_formatter.style_tags") let obj = expect_object(value, "text_formatter.style_tags")
let style_tags : Map[String, TextStyle] = {} let style_tags : Map[String, TextStyle] = {}
for name, item in obj { for name, item in obj {
style_tags[name] = parse_text_style_config(item, "text_formatter.style_tags." + name) style_tags[name] = parse_text_style_config(
item,
"text_formatter.style_tags." + name,
)
} }
style_tags style_tags
} }
fn parse_queue_config(value : @json_parser.JsonValue) -> QueueConfig raise ConfigError { ///|
fn parse_queue_config(
value : @json_parser.JsonValue,
) -> QueueConfig raise ConfigError {
let obj = expect_object(value, "queue") let obj = expect_object(value, "queue")
QueueConfig::new( QueueConfig::new(
get_int(obj, "max_pending", default=0), get_int(obj, "max_pending", default=0),
@@ -348,7 +398,10 @@ fn parse_queue_config(value : @json_parser.JsonValue) -> QueueConfig raise Confi
) )
} }
fn parse_file_rotation_config(value : @json_parser.JsonValue) -> FileRotation raise ConfigError { ///|
fn parse_file_rotation_config(
value : @json_parser.JsonValue,
) -> FileRotation raise ConfigError {
let obj = expect_object(value, "sink.rotation") let obj = expect_object(value, "sink.rotation")
file_rotation( file_rotation(
get_int(obj, "max_bytes", default=1), get_int(obj, "max_bytes", default=1),
@@ -356,7 +409,10 @@ fn parse_file_rotation_config(value : @json_parser.JsonValue) -> FileRotation ra
) )
} }
fn parse_sink_config(value : @json_parser.JsonValue) -> SinkConfig raise ConfigError { ///|
fn parse_sink_config(
value : @json_parser.JsonValue,
) -> SinkConfig raise ConfigError {
let obj = expect_object(value, "sink") let obj = expect_object(value, "sink")
let kind = parse_sink_kind(get_string(obj, "kind", default="console")) let kind = parse_sink_kind(get_string(obj, "kind", default="console"))
let formatter = match obj.get("text_formatter") { let formatter = match obj.get("text_formatter") {
@@ -365,14 +421,15 @@ fn parse_sink_config(value : @json_parser.JsonValue) -> SinkConfig raise ConfigE
} }
let path = get_string(obj, "path", default="") let path = get_string(obj, "path", default="")
match kind { match kind {
SinkKind::File => if path == "" { SinkKind::File =>
if path == "" {
raise ConfigError::InvalidConfig("File sink requires non-empty path") raise ConfigError::InvalidConfig("File sink requires non-empty path")
} }
_ => () _ => ()
} }
SinkConfig::new( SinkConfig::new(
kind=kind, kind~,
path=path, path~,
append=get_bool(obj, "append", default=true), append=get_bool(obj, "append", default=true),
auto_flush=get_bool(obj, "auto_flush", default=true), auto_flush=get_bool(obj, "auto_flush", default=true),
rotation=match obj.get("rotation") { rotation=match obj.get("rotation") {
@@ -383,7 +440,10 @@ fn parse_sink_config(value : @json_parser.JsonValue) -> SinkConfig raise ConfigE
) )
} }
pub fn parse_logger_config_text(input : String) -> LoggerConfig raise ConfigError { ///|
pub fn parse_logger_config_text(
input : String,
) -> LoggerConfig raise ConfigError {
let root = @json_parser.parse(input) catch { let root = @json_parser.parse(input) catch {
e => raise ConfigError::InvalidConfig("Invalid JSON: " + e.to_string()) e => raise ConfigError::InvalidConfig("Invalid JSON: " + e.to_string())
} }
@@ -403,17 +463,24 @@ pub fn parse_logger_config_text(input : String) -> LoggerConfig raise ConfigErro
) )
} }
///|
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue { pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({ @json_parser.JsonValue::Object({
"max_pending": @json_parser.JsonValue::Number(queue.max_pending.to_double()), "max_pending": @json_parser.JsonValue::Number(queue.max_pending.to_double()),
"overflow": @json_parser.JsonValue::String(match queue.overflow { "overflow": @json_parser.JsonValue::String(
match queue.overflow {
QueueOverflowPolicy::DropNewest => "DropNewest" QueueOverflowPolicy::DropNewest => "DropNewest"
QueueOverflowPolicy::DropOldest => "DropOldest" QueueOverflowPolicy::DropOldest => "DropOldest"
}), },
),
}) })
} }
pub fn stringify_queue_config(queue : QueueConfig, pretty~ : Bool = false) -> String { ///|
pub fn stringify_queue_config(
queue : QueueConfig,
pretty? : Bool = false,
) -> String {
let value = queue_config_to_json(queue) let value = queue_config_to_json(queue)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) @json_parser.stringify_pretty(value, 2)
@@ -422,7 +489,10 @@ pub fn stringify_queue_config(queue : QueueConfig, pretty~ : Bool = false) -> St
} }
} }
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_parser.JsonValue { ///|
pub fn text_formatter_config_to_json(
config : TextFormatterConfig,
) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, @json_parser.JsonValue] = {
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp), "show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp),
"show_level": @json_parser.JsonValue::Bool(config.show_level), "show_level": @json_parser.JsonValue::Bool(config.show_level),
@@ -431,11 +501,21 @@ pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_pars
"separator": @json_parser.JsonValue::String(config.separator), "separator": @json_parser.JsonValue::String(config.separator),
"field_separator": @json_parser.JsonValue::String(config.field_separator), "field_separator": @json_parser.JsonValue::String(config.field_separator),
"template": @json_parser.JsonValue::String(config.template), "template": @json_parser.JsonValue::String(config.template),
"color_mode": @json_parser.JsonValue::String(color_mode_label(config.color_mode)), "color_mode": @json_parser.JsonValue::String(
"color_support": @json_parser.JsonValue::String(color_support_label(config.color_support)), color_mode_label(config.color_mode),
"style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.style_markup)), ),
"target_style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.target_style_markup)), "color_support": @json_parser.JsonValue::String(
"fields_style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.fields_style_markup)), color_support_label(config.color_support),
),
"style_markup": @json_parser.JsonValue::String(
style_markup_mode_label(config.style_markup),
),
"target_style_markup": @json_parser.JsonValue::String(
style_markup_mode_label(config.target_style_markup),
),
"fields_style_markup": @json_parser.JsonValue::String(
style_markup_mode_label(config.fields_style_markup),
),
} }
if config.style_tags.length() != 0 { if config.style_tags.length() != 0 {
obj["style_tags"] = style_tags_config_to_json(config.style_tags) obj["style_tags"] = style_tags_config_to_json(config.style_tags)
@@ -443,6 +523,7 @@ pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_pars
@json_parser.JsonValue::Object(obj) @json_parser.JsonValue::Object(obj)
} }
///|
fn text_style_config_to_json(style : TextStyle) -> @json_parser.JsonValue { fn text_style_config_to_json(style : TextStyle) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, @json_parser.JsonValue] = {
"bold": @json_parser.JsonValue::Bool(style.bold), "bold": @json_parser.JsonValue::Bool(style.bold),
@@ -461,7 +542,10 @@ fn text_style_config_to_json(style : TextStyle) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object(obj) @json_parser.JsonValue::Object(obj)
} }
fn style_tags_config_to_json(style_tags : Map[String, TextStyle]) -> @json_parser.JsonValue { ///|
fn style_tags_config_to_json(
style_tags : Map[String, TextStyle],
) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = {} let obj : Map[String, @json_parser.JsonValue] = {}
for name, style in style_tags { for name, style in style_tags {
obj[name] = text_style_config_to_json(style) obj[name] = text_style_config_to_json(style)
@@ -469,9 +553,10 @@ fn style_tags_config_to_json(style_tags : Map[String, TextStyle]) -> @json_parse
@json_parser.JsonValue::Object(obj) @json_parser.JsonValue::Object(obj)
} }
///|
pub fn stringify_text_formatter_config( pub fn stringify_text_formatter_config(
config : TextFormatterConfig, config : TextFormatterConfig,
pretty~ : Bool = false, pretty? : Bool = false,
) -> String { ) -> String {
let value = text_formatter_config_to_json(config) let value = text_formatter_config_to_json(config)
if pretty { if pretty {
@@ -481,13 +566,19 @@ pub fn stringify_text_formatter_config(
} }
} }
pub fn file_rotation_config_to_json(config : FileRotation) -> @json_parser.JsonValue { ///|
pub fn file_rotation_config_to_json(
config : FileRotation,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({ @json_parser.JsonValue::Object({
"max_bytes": @json_parser.JsonValue::Number(config.max_bytes.to_double()), "max_bytes": @json_parser.JsonValue::Number(config.max_bytes.to_double()),
"max_backups": @json_parser.JsonValue::Number(config.max_backups.to_double()), "max_backups": @json_parser.JsonValue::Number(
config.max_backups.to_double(),
),
}) })
} }
///|
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue { pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, @json_parser.JsonValue] = {
"kind": @json_parser.JsonValue::String(sink_kind_label(config.kind)), "kind": @json_parser.JsonValue::String(sink_kind_label(config.kind)),
@@ -503,7 +594,11 @@ pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object(obj) @json_parser.JsonValue::Object(obj)
} }
pub fn stringify_sink_config(config : SinkConfig, pretty~ : Bool = false) -> String { ///|
pub fn stringify_sink_config(
config : SinkConfig,
pretty? : Bool = false,
) -> String {
let value = sink_config_to_json(config) let value = sink_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) @json_parser.stringify_pretty(value, 2)
@@ -512,6 +607,7 @@ pub fn stringify_sink_config(config : SinkConfig, pretty~ : Bool = false) -> Str
} }
} }
///|
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue { pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, @json_parser.JsonValue] = {
"min_level": @json_parser.JsonValue::String(config.min_level.label()), "min_level": @json_parser.JsonValue::String(config.min_level.label()),
@@ -526,7 +622,11 @@ pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object(obj) @json_parser.JsonValue::Object(obj)
} }
pub fn stringify_logger_config(config : LoggerConfig, pretty~ : Bool = false) -> String { ///|
pub fn stringify_logger_config(
config : LoggerConfig,
pretty? : Bool = false,
) -> String {
let value = logger_config_to_json(config) let value = logger_config_to_json(config)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) @json_parser.stringify_pretty(value, 2)
+26 -5
View File
@@ -1,3 +1,4 @@
///|
fn string_to_c_bytes(str : String) -> Bytes { fn string_to_c_bytes(str : String) -> Bytes {
let res : Array[Byte] = [] let res : Array[Byte] = []
let len = str.length() let len = str.length()
@@ -31,14 +32,18 @@ fn string_to_c_bytes(str : String) -> Bytes {
Bytes::from_array(res) Bytes::from_array(res)
} }
///|
#external #external
type NativeFileHandle type NativeFileHandle
///|
#borrow(path, mode) #borrow(path, mode)
extern "C" fn file_open_ffi(path : Bytes, mode : Bytes) -> NativeFileHandle = "bitlogger_file_open" extern "C" fn file_open_ffi(path : Bytes, mode : Bytes) -> NativeFileHandle = "bitlogger_file_open"
///|
extern "C" fn file_is_null_ffi(handle : NativeFileHandle) -> Bool = "bitlogger_pointer_is_null" extern "C" fn file_is_null_ffi(handle : NativeFileHandle) -> Bool = "bitlogger_pointer_is_null"
///|
#borrow(buffer) #borrow(buffer)
extern "C" fn file_write_ffi( extern "C" fn file_write_ffi(
buffer : Bytes, buffer : Bytes,
@@ -47,32 +52,37 @@ extern "C" fn file_write_ffi(
handle : NativeFileHandle, handle : NativeFileHandle,
) -> Int = "bitlogger_file_write" ) -> Int = "bitlogger_file_write"
///|
extern "C" fn file_flush_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_flush" extern "C" fn file_flush_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_flush"
///|
extern "C" fn file_close_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_close" extern "C" fn file_close_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_close"
///|
extern "C" fn file_seek_ffi( extern "C" fn file_seek_ffi(
handle : NativeFileHandle, handle : NativeFileHandle,
offset : Int, offset : Int,
origin : Int, origin : Int,
) -> Int = "bitlogger_file_seek" ) -> Int = "bitlogger_file_seek"
///|
extern "C" fn file_tell_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_tell" extern "C" fn file_tell_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_tell"
///|
#borrow(from_path, to_path) #borrow(from_path, to_path)
extern "C" fn file_rename_ffi( extern "C" fn file_rename_ffi(from_path : Bytes, to_path : Bytes) -> Int = "bitlogger_file_rename"
from_path : Bytes,
to_path : Bytes,
) -> Int = "bitlogger_file_rename"
///|
#borrow(path) #borrow(path)
extern "C" fn file_remove_ffi(path : Bytes) -> Int = "bitlogger_file_remove" extern "C" fn file_remove_ffi(path : Bytes) -> Int = "bitlogger_file_remove"
///|
pub struct FileHandle { pub struct FileHandle {
path : String path : String
raw : NativeFileHandle raw : NativeFileHandle
} }
///|
pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? { pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
let mode = if append { "ab" } else { "wb" } let mode = if append { "ab" } else { "wb" }
let raw = file_open_ffi(string_to_c_bytes(path), string_to_c_bytes(mode)) let raw = file_open_ffi(string_to_c_bytes(path), string_to_c_bytes(mode))
@@ -83,20 +93,27 @@ pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
} }
} }
pub fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool { ///|
pub fn write_file_handle_internal(
handle : FileHandle,
content : String,
) -> Bool {
let bytes = string_to_c_bytes(content) let bytes = string_to_c_bytes(content)
let written = file_write_ffi(bytes, 1, bytes.length() - 1, handle.raw) let written = file_write_ffi(bytes, 1, bytes.length() - 1, handle.raw)
written == bytes.length() - 1 written == bytes.length() - 1
} }
///|
pub fn flush_file_handle_internal(handle : FileHandle) -> Bool { pub fn flush_file_handle_internal(handle : FileHandle) -> Bool {
file_flush_ffi(handle.raw) == 0 file_flush_ffi(handle.raw) == 0
} }
///|
pub fn close_file_handle_internal(handle : FileHandle) -> Bool { pub fn close_file_handle_internal(handle : FileHandle) -> Bool {
file_close_ffi(handle.raw) == 0 file_close_ffi(handle.raw) == 0
} }
///|
pub fn file_size_internal(handle : FileHandle) -> Int { pub fn file_size_internal(handle : FileHandle) -> Int {
ignore(file_seek_ffi(handle.raw, 0, 2)) ignore(file_seek_ffi(handle.raw, 0, 2))
let size = file_tell_ffi(handle.raw) let size = file_tell_ffi(handle.raw)
@@ -107,18 +124,22 @@ pub fn file_size_internal(handle : FileHandle) -> Int {
} }
} }
///|
pub fn rename_file_internal(from_path : String, to_path : String) -> Bool { pub fn rename_file_internal(from_path : String, to_path : String) -> Bool {
file_rename_ffi(string_to_c_bytes(from_path), string_to_c_bytes(to_path)) == 0 file_rename_ffi(string_to_c_bytes(from_path), string_to_c_bytes(to_path)) == 0
} }
///|
pub fn remove_file_internal(path : String) -> Bool { pub fn remove_file_internal(path : String) -> Bool {
file_remove_ffi(string_to_c_bytes(path)) == 0 file_remove_ffi(string_to_c_bytes(path)) == 0
} }
///|
pub fn string_byte_length_internal(content : String) -> Int { pub fn string_byte_length_internal(content : String) -> Int {
string_to_c_bytes(content).length() - 1 string_to_c_bytes(content).length() - 1
} }
///|
pub fn native_files_supported_internal() -> Bool { pub fn native_files_supported_internal() -> Bool {
true true
} }
+14 -1
View File
@@ -1,7 +1,9 @@
///|
pub struct FileHandle { pub struct FileHandle {
path : String path : String
} }
///|
pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? { pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
ignore(append) ignore(append)
ignore(path) ignore(path)
@@ -10,42 +12,53 @@ pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
None None
} }
pub fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool { ///|
pub fn write_file_handle_internal(
handle : FileHandle,
content : String,
) -> Bool {
ignore(handle) ignore(handle)
ignore(content) ignore(content)
false false
} }
///|
pub fn flush_file_handle_internal(handle : FileHandle) -> Bool { pub fn flush_file_handle_internal(handle : FileHandle) -> Bool {
ignore(handle) ignore(handle)
false false
} }
///|
pub fn close_file_handle_internal(handle : FileHandle) -> Bool { pub fn close_file_handle_internal(handle : FileHandle) -> Bool {
ignore(handle) ignore(handle)
false false
} }
///|
pub fn file_size_internal(handle : FileHandle) -> Int { pub fn file_size_internal(handle : FileHandle) -> Int {
ignore(handle) ignore(handle)
0 0
} }
///|
pub fn rename_file_internal(from_path : String, to_path : String) -> Bool { pub fn rename_file_internal(from_path : String, to_path : String) -> Bool {
ignore(from_path) ignore(from_path)
ignore(to_path) ignore(to_path)
false false
} }
///|
pub fn remove_file_internal(path : String) -> Bool { pub fn remove_file_internal(path : String) -> Bool {
ignore(path) ignore(path)
false false
} }
///|
pub fn string_byte_length_internal(content : String) -> Int { pub fn string_byte_length_internal(content : String) -> Int {
content.length() content.length()
} }
///|
pub fn native_files_supported_internal() -> Bool { pub fn native_files_supported_internal() -> Bool {
false false
} }
+16 -16
View File
@@ -1,29 +1,27 @@
///|
pub type RecordPredicate = (@core.Record) -> Bool pub type RecordPredicate = (@core.Record) -> Bool
///|
pub fn level_at_least(min_level : @core.Level) -> RecordPredicate { pub fn level_at_least(min_level : @core.Level) -> RecordPredicate {
fn(rec) { fn(rec) { rec.level.priority() >= min_level.priority() }
rec.level.priority() >= min_level.priority()
}
} }
///|
pub fn target_is(target : String) -> RecordPredicate { pub fn target_is(target : String) -> RecordPredicate {
fn(rec) { fn(rec) { rec.target == target }
rec.target == target
}
} }
///|
pub fn target_has_prefix(prefix : String) -> RecordPredicate { pub fn target_has_prefix(prefix : String) -> RecordPredicate {
fn(rec) { fn(rec) { rec.target.has_prefix(prefix) }
rec.target.has_prefix(prefix)
}
} }
///|
pub fn message_contains(fragment : String) -> RecordPredicate { pub fn message_contains(fragment : String) -> RecordPredicate {
fn(rec) { fn(rec) { rec.message.contains(fragment) }
rec.message.contains(fragment)
}
} }
///|
pub fn has_field(key : String) -> RecordPredicate { pub fn has_field(key : String) -> RecordPredicate {
fn(rec) { fn(rec) {
for field in rec.fields { for field in rec.fields {
@@ -35,6 +33,7 @@ pub fn has_field(key : String) -> RecordPredicate {
} }
} }
///|
pub fn field_equals(key : String, value : String) -> RecordPredicate { pub fn field_equals(key : String, value : String) -> RecordPredicate {
fn(rec) { fn(rec) {
for field in rec.fields { for field in rec.fields {
@@ -46,16 +45,16 @@ pub fn field_equals(key : String, value : String) -> RecordPredicate {
} }
} }
///|
pub fn not_(predicate : RecordPredicate) -> RecordPredicate { pub fn not_(predicate : RecordPredicate) -> RecordPredicate {
fn(rec) { fn(rec) { !predicate(rec) }
!(predicate(rec))
}
} }
///|
pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate { pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
fn(rec) { fn(rec) {
for predicate in predicates { for predicate in predicates {
if !(predicate(rec)) { if !predicate(rec) {
return false return false
} }
} }
@@ -63,6 +62,7 @@ pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
} }
} }
///|
pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate { pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
fn(rec) { fn(rec) {
for predicate in predicates { for predicate in predicates {
+249 -77
View File
@@ -1,22 +1,27 @@
///|
pub type RecordFormatter = (@core.Record) -> String pub type RecordFormatter = (@core.Record) -> String
///|
pub(all) enum ColorMode { pub(all) enum ColorMode {
Never Never
Auto Auto
Always Always
} }
///|
pub(all) enum ColorSupport { pub(all) enum ColorSupport {
Basic Basic
TrueColor TrueColor
} }
///|
pub(all) enum StyleMarkupMode { pub(all) enum StyleMarkupMode {
Disabled Disabled
Builtin Builtin
Full Full
} }
///|
pub struct TextStyle { pub struct TextStyle {
fg : String? fg : String?
bg : String? bg : String?
@@ -26,29 +31,34 @@ pub struct TextStyle {
underline : Bool underline : Bool
} }
///|
pub fn text_style( pub fn text_style(
fg~ : String? = None, fg? : String? = None,
bg~ : String? = None, bg? : String? = None,
bold~ : Bool = false, bold? : Bool = false,
dim~ : Bool = false, dim? : Bool = false,
italic~ : Bool = false, italic? : Bool = false,
underline~ : Bool = false, underline? : Bool = false,
) -> TextStyle { ) -> TextStyle {
{ fg, bg, bold, dim, italic, underline } { fg, bg, bold, dim, italic, underline }
} }
///|
pub struct StyleTagRegistry { pub struct StyleTagRegistry {
entries : Map[String, TextStyle] entries : Map[String, TextStyle]
} }
///|
fn normalize_style_tag_name(name : String) -> String { fn normalize_style_tag_name(name : String) -> String {
name.trim().to_lower().to_owned() name.trim().to_lower().to_owned()
} }
///|
pub fn style_tag_registry() -> StyleTagRegistry { pub fn style_tag_registry() -> StyleTagRegistry {
{ entries: {} } { entries: {} }
} }
///|
fn merge_text_style(base : TextStyle, overlay : TextStyle) -> TextStyle { fn merge_text_style(base : TextStyle, overlay : TextStyle) -> TextStyle {
{ {
fg: match overlay.fg { fg: match overlay.fg {
@@ -66,47 +76,62 @@ fn merge_text_style(base : TextStyle, overlay : TextStyle) -> TextStyle {
} }
} }
///|
pub fn StyleTagRegistry::set_tag( pub fn StyleTagRegistry::set_tag(
self : StyleTagRegistry, self : StyleTagRegistry,
name : String, name : String,
style~ : TextStyle = text_style(), style? : TextStyle = text_style(),
fg~ : String? = None, fg? : String? = None,
bg~ : String? = None, bg? : String? = None,
bold~ : Bool = false, bold? : Bool = false,
dim~ : Bool = false, dim? : Bool = false,
italic~ : Bool = false, italic? : Bool = false,
underline~ : Bool = false, underline? : Bool = false,
) -> StyleTagRegistry { ) -> StyleTagRegistry {
let overlay = text_style(fg=fg, bg=bg, bold=bold, dim=dim, italic=italic, underline=underline) let overlay = text_style(fg~, bg~, bold~, dim~, italic~, underline~)
self.entries[normalize_style_tag_name(name)] = merge_text_style(style, overlay) self.entries[normalize_style_tag_name(name)] = merge_text_style(
style, overlay,
)
self self
} }
pub fn StyleTagRegistry::get(self : StyleTagRegistry, name : String) -> TextStyle? { ///|
pub fn StyleTagRegistry::get(
self : StyleTagRegistry,
name : String,
) -> TextStyle? {
self.entries.get(normalize_style_tag_name(name)) self.entries.get(normalize_style_tag_name(name))
} }
pub fn StyleTagRegistry::contains(self : StyleTagRegistry, name : String) -> Bool { ///|
pub fn StyleTagRegistry::contains(
self : StyleTagRegistry,
name : String,
) -> Bool {
self.entries.contains(normalize_style_tag_name(name)) self.entries.contains(normalize_style_tag_name(name))
} }
///|
pub fn StyleTagRegistry::define_alias( pub fn StyleTagRegistry::define_alias(
self : StyleTagRegistry, self : StyleTagRegistry,
name : String, name : String,
target : String, target : String,
) -> StyleTagRegistry { ) -> StyleTagRegistry {
match self.get(target) { match self.get(target) {
Some(style) => self.set_tag(name, style=style) Some(style) => self.set_tag(name, style~)
None => match global_style_tag_registry().get(target) { None =>
Some(style) => self.set_tag(name, style=style) match global_style_tag_registry().get(target) {
None => match builtin_text_style_for_tag(normalize_style_tag_name(target)) { Some(style) => self.set_tag(name, style~)
Some(style) => self.set_tag(name, style=style) None =>
match builtin_text_style_for_tag(normalize_style_tag_name(target)) {
Some(style) => self.set_tag(name, style~)
None => self None => self
} }
} }
} }
} }
///|
pub fn default_style_tag_registry() -> StyleTagRegistry { pub fn default_style_tag_registry() -> StyleTagRegistry {
style_tag_registry() style_tag_registry()
.set_tag("black", fg=Some("black")) .set_tag("black", fg=Some("black"))
@@ -137,20 +162,27 @@ pub fn default_style_tag_registry() -> StyleTagRegistry {
.set_tag("u", underline=true) .set_tag("u", underline=true)
} }
let global_style_tag_registry_ref : Ref[StyleTagRegistry] = Ref(style_tag_registry()) ///|
let global_style_tag_registry_ref : Ref[StyleTagRegistry] = Ref(
style_tag_registry(),
)
///|
pub fn global_style_tag_registry() -> StyleTagRegistry { pub fn global_style_tag_registry() -> StyleTagRegistry {
global_style_tag_registry_ref.val global_style_tag_registry_ref.val
} }
///|
pub fn set_global_style_tag_registry(registry : StyleTagRegistry) -> Unit { pub fn set_global_style_tag_registry(registry : StyleTagRegistry) -> Unit {
global_style_tag_registry_ref.val = registry global_style_tag_registry_ref.val = registry
} }
///|
pub fn reset_global_style_tag_registry() -> Unit { pub fn reset_global_style_tag_registry() -> Unit {
global_style_tag_registry_ref.val = style_tag_registry() global_style_tag_registry_ref.val = style_tag_registry()
} }
///|
priv struct InlineStyle { priv struct InlineStyle {
fg_code : String? fg_code : String?
bg_code : String? bg_code : String?
@@ -162,24 +194,29 @@ priv struct InlineStyle {
underline : Bool underline : Bool
} }
///|
priv struct StyledSegment { priv struct StyledSegment {
text : String text : String
style : InlineStyle style : InlineStyle
} }
///|
priv struct StyleFrame { priv struct StyleFrame {
tag : String tag : String
style : InlineStyle style : InlineStyle
} }
///|
fn code_unit(ch : Char) -> Int { fn code_unit(ch : Char) -> Int {
ch.to_int() ch.to_int()
} }
///|
fn code_unit16(ch : UInt16) -> Int { fn code_unit16(ch : UInt16) -> Int {
ch.to_int() ch.to_int()
} }
///|
pub struct TextFormatter { pub struct TextFormatter {
show_timestamp : Bool show_timestamp : Bool
show_level : Bool show_level : Bool
@@ -196,20 +233,21 @@ pub struct TextFormatter {
style_tags : StyleTagRegistry? style_tags : StyleTagRegistry?
} }
///|
pub fn text_formatter( pub fn text_formatter(
show_timestamp~ : Bool = true, show_timestamp? : Bool = true,
show_level~ : Bool = true, show_level? : Bool = true,
show_target~ : Bool = true, show_target? : Bool = true,
show_fields~ : Bool = true, show_fields? : Bool = true,
separator~ : String = " ", separator? : String = " ",
field_separator~ : String = " ", field_separator? : String = " ",
template~ : String = "", template? : String = "",
color_mode~ : ColorMode = ColorMode::Never, color_mode? : ColorMode = ColorMode::Never,
color_support~ : ColorSupport = ColorSupport::TrueColor, color_support? : ColorSupport = ColorSupport::TrueColor,
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full, style_markup? : StyleMarkupMode = StyleMarkupMode::Full,
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled, target_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled, fields_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
style_tags~ : StyleTagRegistry? = None, style_tags? : StyleTagRegistry? = None,
) -> TextFormatter { ) -> TextFormatter {
{ {
show_timestamp, show_timestamp,
@@ -228,6 +266,7 @@ pub fn text_formatter(
} }
} }
///|
pub fn color_support_label(support : ColorSupport) -> String { pub fn color_support_label(support : ColorSupport) -> String {
match support { match support {
ColorSupport::Basic => "basic" ColorSupport::Basic => "basic"
@@ -235,7 +274,11 @@ pub fn color_support_label(support : ColorSupport) -> String {
} }
} }
pub fn TextFormatter::with_color_support(self : TextFormatter, color_support : ColorSupport) -> TextFormatter { ///|
pub fn TextFormatter::with_color_support(
self : TextFormatter,
color_support : ColorSupport,
) -> TextFormatter {
text_formatter( text_formatter(
show_timestamp=self.show_timestamp, show_timestamp=self.show_timestamp,
show_level=self.show_level, show_level=self.show_level,
@@ -245,7 +288,7 @@ pub fn TextFormatter::with_color_support(self : TextFormatter, color_support : C
field_separator=self.field_separator, field_separator=self.field_separator,
template=self.template, template=self.template,
color_mode=self.color_mode, color_mode=self.color_mode,
color_support=color_support, color_support~,
style_markup=self.style_markup, style_markup=self.style_markup,
target_style_markup=self.target_style_markup, target_style_markup=self.target_style_markup,
fields_style_markup=self.fields_style_markup, fields_style_markup=self.fields_style_markup,
@@ -253,6 +296,7 @@ pub fn TextFormatter::with_color_support(self : TextFormatter, color_support : C
) )
} }
///|
pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String { pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String {
match mode { match mode {
StyleMarkupMode::Disabled => "disabled" StyleMarkupMode::Disabled => "disabled"
@@ -261,7 +305,11 @@ pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String {
} }
} }
pub fn TextFormatter::with_style_markup(self : TextFormatter, style_markup : StyleMarkupMode) -> TextFormatter { ///|
pub fn TextFormatter::with_style_markup(
self : TextFormatter,
style_markup : StyleMarkupMode,
) -> TextFormatter {
text_formatter( text_formatter(
show_timestamp=self.show_timestamp, show_timestamp=self.show_timestamp,
show_level=self.show_level, show_level=self.show_level,
@@ -272,17 +320,21 @@ pub fn TextFormatter::with_style_markup(self : TextFormatter, style_markup : Sty
template=self.template, template=self.template,
color_mode=self.color_mode, color_mode=self.color_mode,
color_support=self.color_support, color_support=self.color_support,
style_markup=style_markup, style_markup~,
target_style_markup=self.target_style_markup, target_style_markup=self.target_style_markup,
fields_style_markup=self.fields_style_markup, fields_style_markup=self.fields_style_markup,
style_tags=self.style_tags, style_tags=self.style_tags,
) )
} }
pub fn TextFormatter::without_style_markup(self : TextFormatter) -> TextFormatter { ///|
pub fn TextFormatter::without_style_markup(
self : TextFormatter,
) -> TextFormatter {
self.with_style_markup(StyleMarkupMode::Disabled) self.with_style_markup(StyleMarkupMode::Disabled)
} }
///|
pub fn TextFormatter::with_target_style_markup( pub fn TextFormatter::with_target_style_markup(
self : TextFormatter, self : TextFormatter,
style_markup : StyleMarkupMode, style_markup : StyleMarkupMode,
@@ -304,6 +356,7 @@ pub fn TextFormatter::with_target_style_markup(
) )
} }
///|
pub fn TextFormatter::with_fields_style_markup( pub fn TextFormatter::with_fields_style_markup(
self : TextFormatter, self : TextFormatter,
style_markup : StyleMarkupMode, style_markup : StyleMarkupMode,
@@ -325,7 +378,11 @@ pub fn TextFormatter::with_fields_style_markup(
) )
} }
pub fn TextFormatter::with_style_tags(self : TextFormatter, style_tags : StyleTagRegistry) -> TextFormatter { ///|
pub fn TextFormatter::with_style_tags(
self : TextFormatter,
style_tags : StyleTagRegistry,
) -> TextFormatter {
text_formatter( text_formatter(
show_timestamp=self.show_timestamp, show_timestamp=self.show_timestamp,
show_level=self.show_level, show_level=self.show_level,
@@ -343,6 +400,7 @@ pub fn TextFormatter::with_style_tags(self : TextFormatter, style_tags : StyleTa
) )
} }
///|
pub fn color_mode_label(mode : ColorMode) -> String { pub fn color_mode_label(mode : ColorMode) -> String {
match mode { match mode {
ColorMode::Never => "never" ColorMode::Never => "never"
@@ -351,17 +409,20 @@ pub fn color_mode_label(mode : ColorMode) -> String {
} }
} }
///|
fn use_ansi_color(mode : ColorMode) -> Bool { fn use_ansi_color(mode : ColorMode) -> Bool {
match mode { match mode {
ColorMode::Never => false ColorMode::Never => false
ColorMode::Always => true ColorMode::Always => true
ColorMode::Auto => match @env.get_env_var("NO_COLOR") { ColorMode::Auto =>
match @env.get_env_var("NO_COLOR") {
Some(_) => false Some(_) => false
None => true None => true
} }
} }
} }
///|
fn ansi_wrap(text : String, code : String, enabled : Bool) -> String { fn ansi_wrap(text : String, code : String, enabled : Bool) -> String {
if !enabled || text == "" { if !enabled || text == "" {
text text
@@ -370,7 +431,12 @@ fn ansi_wrap(text : String, code : String, enabled : Bool) -> String {
} }
} }
fn ansi_wrap_with_style(text : String, style : InlineStyle, enabled : Bool) -> String { ///|
fn ansi_wrap_with_style(
text : String,
style : InlineStyle,
enabled : Bool,
) -> String {
if !enabled || text == "" { if !enabled || text == "" {
text text
} else { } else {
@@ -404,6 +470,7 @@ fn ansi_wrap_with_style(text : String, style : InlineStyle, enabled : Bool) -> S
} }
} }
///|
fn default_inline_style() -> InlineStyle { fn default_inline_style() -> InlineStyle {
{ {
fg_code: None, fg_code: None,
@@ -417,6 +484,7 @@ fn default_inline_style() -> InlineStyle {
} }
} }
///|
fn named_color_code(tag : String) -> String? { fn named_color_code(tag : String) -> String? {
match tag { match tag {
"black" => Some("30") "black" => Some("30")
@@ -439,6 +507,7 @@ fn named_color_code(tag : String) -> String? {
} }
} }
///|
fn named_bg_color_code(tag : String) -> String? { fn named_bg_color_code(tag : String) -> String? {
match tag { match tag {
"black" => Some("40") "black" => Some("40")
@@ -461,6 +530,7 @@ fn named_bg_color_code(tag : String) -> String? {
} }
} }
///|
fn basic_name_from_hex(value : String) -> String { fn basic_name_from_hex(value : String) -> String {
let r = hex_pair_to_int(value.unsafe_get(1), value.unsafe_get(2)) let r = hex_pair_to_int(value.unsafe_get(1), value.unsafe_get(2))
let g = hex_pair_to_int(value.unsafe_get(3), value.unsafe_get(4)) let g = hex_pair_to_int(value.unsafe_get(3), value.unsafe_get(4))
@@ -495,8 +565,7 @@ fn basic_name_from_hex(value : String) -> String {
} else { } else {
"green" "green"
} }
} else { } else if r > 96 && g < 96 {
if r > 96 && g < 96 {
"magenta" "magenta"
} else if g > 96 && r < 96 { } else if g > 96 && r < 96 {
"cyan" "cyan"
@@ -504,9 +573,9 @@ fn basic_name_from_hex(value : String) -> String {
"blue" "blue"
} }
} }
}
} }
///|
fn resolve_inline_color_code( fn resolve_inline_color_code(
formatter : TextFormatter, formatter : TextFormatter,
basic_name : String?, basic_name : String?,
@@ -514,21 +583,25 @@ fn resolve_inline_color_code(
) -> String { ) -> String {
match formatter.color_support { match formatter.color_support {
ColorSupport::TrueColor => truecolor_code ColorSupport::TrueColor => truecolor_code
ColorSupport::Basic => match basic_name { ColorSupport::Basic =>
match basic_name {
Some(name) => named_code_from_basic_name(name).unwrap_or(truecolor_code) Some(name) => named_code_from_basic_name(name).unwrap_or(truecolor_code)
None => truecolor_code None => truecolor_code
} }
} }
} }
///|
fn named_code_from_basic_name(name : String) -> String? { fn named_code_from_basic_name(name : String) -> String? {
named_color_code(name) named_color_code(name)
} }
///|
fn named_bg_code_from_basic_name(name : String) -> String? { fn named_bg_code_from_basic_name(name : String) -> String? {
named_bg_color_code(name) named_bg_color_code(name)
} }
///|
fn resolve_inline_bg_code( fn resolve_inline_bg_code(
formatter : TextFormatter, formatter : TextFormatter,
basic_name : String?, basic_name : String?,
@@ -536,13 +609,16 @@ fn resolve_inline_bg_code(
) -> String { ) -> String {
match formatter.color_support { match formatter.color_support {
ColorSupport::TrueColor => truecolor_code ColorSupport::TrueColor => truecolor_code
ColorSupport::Basic => match basic_name { ColorSupport::Basic =>
Some(name) => named_bg_code_from_basic_name(name).unwrap_or(truecolor_code) match basic_name {
Some(name) =>
named_bg_code_from_basic_name(name).unwrap_or(truecolor_code)
None => truecolor_code None => truecolor_code
} }
} }
} }
///|
fn is_hex_color(value : String) -> Bool { fn is_hex_color(value : String) -> Bool {
if value.length() != 7 { if value.length() != 7 {
return false return false
@@ -562,6 +638,7 @@ fn is_hex_color(value : String) -> Bool {
true true
} }
///|
fn hex_to_int(ch : UInt16) -> Int { fn hex_to_int(ch : UInt16) -> Int {
let code = code_unit16(ch) let code = code_unit16(ch)
if code >= code_unit('0') && code <= code_unit('9') { if code >= code_unit('0') && code <= code_unit('9') {
@@ -573,10 +650,12 @@ fn hex_to_int(ch : UInt16) -> Int {
} }
} }
///|
fn hex_pair_to_int(high : UInt16, low : UInt16) -> Int { fn hex_pair_to_int(high : UInt16, low : UInt16) -> Int {
hex_to_int(high) * 16 + hex_to_int(low) hex_to_int(high) * 16 + hex_to_int(low)
} }
///|
fn rgb_fg_code(value : String) -> String { fn rgb_fg_code(value : String) -> String {
let r = hex_pair_to_int(value.unsafe_get(1), value.unsafe_get(2)) let r = hex_pair_to_int(value.unsafe_get(1), value.unsafe_get(2))
let g = hex_pair_to_int(value.unsafe_get(3), value.unsafe_get(4)) let g = hex_pair_to_int(value.unsafe_get(3), value.unsafe_get(4))
@@ -584,6 +663,7 @@ fn rgb_fg_code(value : String) -> String {
"38;2;\{r};\{g};\{b}" "38;2;\{r};\{g};\{b}"
} }
///|
fn rgb_bg_code(value : String) -> String { fn rgb_bg_code(value : String) -> String {
let r = hex_pair_to_int(value.unsafe_get(4), value.unsafe_get(5)) let r = hex_pair_to_int(value.unsafe_get(4), value.unsafe_get(5))
let g = hex_pair_to_int(value.unsafe_get(6), value.unsafe_get(7)) let g = hex_pair_to_int(value.unsafe_get(6), value.unsafe_get(7))
@@ -591,6 +671,7 @@ fn rgb_bg_code(value : String) -> String {
"48;2;\{r};\{g};\{b}" "48;2;\{r};\{g};\{b}"
} }
///|
fn rgb_bg_code_from_hex(value : String) -> String { fn rgb_bg_code_from_hex(value : String) -> String {
let r = hex_pair_to_int(value.unsafe_get(1), value.unsafe_get(2)) let r = hex_pair_to_int(value.unsafe_get(1), value.unsafe_get(2))
let g = hex_pair_to_int(value.unsafe_get(3), value.unsafe_get(4)) let g = hex_pair_to_int(value.unsafe_get(3), value.unsafe_get(4))
@@ -598,6 +679,7 @@ fn rgb_bg_code_from_hex(value : String) -> String {
"48;2;\{r};\{g};\{b}" "48;2;\{r};\{g};\{b}"
} }
///|
fn inline_style_from_text_style( fn inline_style_from_text_style(
base : InlineStyle, base : InlineStyle,
style : TextStyle, style : TextStyle,
@@ -609,12 +691,21 @@ fn inline_style_from_text_style(
let normalized = normalize_style_tag_name(value) let normalized = normalize_style_tag_name(value)
match named_color_code(normalized) { match named_color_code(normalized) {
Some(code) => Some((Some(code), Some(normalized))) Some(code) => Some((Some(code), Some(normalized)))
None => if is_hex_color(value) { None =>
if is_hex_color(value) {
let basic_name = basic_name_from_hex(value) let basic_name = basic_name_from_hex(value)
Some(( Some(
Some(resolve_inline_color_code(formatter, Some(basic_name), rgb_fg_code(value))), (
Some(
resolve_inline_color_code(
formatter,
Some(basic_name), Some(basic_name),
)) rgb_fg_code(value),
),
),
Some(basic_name),
),
)
} else { } else {
None None
} }
@@ -634,12 +725,21 @@ fn inline_style_from_text_style(
} }
Some((Some(code), Some(bg_name))) Some((Some(code), Some(bg_name)))
} }
None => if is_hex_color(value) { None =>
if is_hex_color(value) {
let basic_name = basic_name_from_hex(value) let basic_name = basic_name_from_hex(value)
Some(( Some(
Some(resolve_inline_bg_code(formatter, Some(basic_name), rgb_bg_code_from_hex(value))), (
Some(
resolve_inline_bg_code(
formatter,
Some(basic_name), Some(basic_name),
)) rgb_bg_code_from_hex(value),
),
),
Some(basic_name),
),
)
} else { } else {
None None
} }
@@ -647,8 +747,10 @@ fn inline_style_from_text_style(
} }
} }
match fg { match fg {
Some((next_fg_code, next_fg_name)) => match bg { Some((next_fg_code, next_fg_name)) =>
Some((next_bg_code, next_bg_name)) => Some({ match bg {
Some((next_bg_code, next_bg_name)) =>
Some({
fg_code: next_fg_code, fg_code: next_fg_code,
bg_code: next_bg_code, bg_code: next_bg_code,
fg_basic_name: next_fg_name, fg_basic_name: next_fg_name,
@@ -664,6 +766,7 @@ fn inline_style_from_text_style(
} }
} }
///|
fn builtin_text_style_for_tag(tag : String) -> TextStyle? { fn builtin_text_style_for_tag(tag : String) -> TextStyle? {
match normalize_style_tag_name(tag) { match normalize_style_tag_name(tag) {
"black" => Some(text_style(fg=Some("black"))) "black" => Some(text_style(fg=Some("black")))
@@ -696,41 +799,63 @@ fn builtin_text_style_for_tag(tag : String) -> TextStyle? {
} }
} }
fn resolve_registered_text_style(tag : String, formatter : TextFormatter) -> TextStyle? { ///|
fn resolve_registered_text_style(
tag : String,
formatter : TextFormatter,
) -> TextStyle? {
let normalized = normalize_style_tag_name(tag) let normalized = normalize_style_tag_name(tag)
match formatter.style_markup { match formatter.style_markup {
StyleMarkupMode::Builtin => return builtin_text_style_for_tag(normalized) StyleMarkupMode::Builtin => return builtin_text_style_for_tag(normalized)
_ => () _ => ()
} }
match formatter.style_tags { match formatter.style_tags {
Some(registry) => match registry.get(normalized) { Some(registry) =>
match registry.get(normalized) {
Some(style) => Some(style) Some(style) => Some(style)
None => match global_style_tag_registry().get(normalized) { None =>
match global_style_tag_registry().get(normalized) {
Some(style) => Some(style) Some(style) => Some(style)
None => builtin_text_style_for_tag(normalized) None => builtin_text_style_for_tag(normalized)
} }
} }
None => match global_style_tag_registry().get(normalized) { None =>
match global_style_tag_registry().get(normalized) {
Some(style) => Some(style) Some(style) => Some(style)
None => builtin_text_style_for_tag(normalized) None => builtin_text_style_for_tag(normalized)
} }
} }
} }
fn apply_inline_tag(style : InlineStyle, tag : String, formatter : TextFormatter) -> InlineStyle? { ///|
fn apply_inline_tag(
style : InlineStyle,
tag : String,
formatter : TextFormatter,
) -> InlineStyle? {
let normalized = normalize_style_tag_name(tag) let normalized = normalize_style_tag_name(tag)
if is_hex_color(tag) { if is_hex_color(tag) {
let basic_name = basic_name_from_hex(tag) let basic_name = basic_name_from_hex(tag)
Some({ Some({
..style, ..style,
fg_code: Some(resolve_inline_color_code(formatter, Some(basic_name), rgb_fg_code(tag))), fg_code: Some(
resolve_inline_color_code(formatter, Some(basic_name), rgb_fg_code(tag)),
),
fg_basic_name: Some(basic_name), fg_basic_name: Some(basic_name),
}) })
} else if normalized.length() == 10 && normalized.has_prefix("bg:") && is_hex_color(normalized[3:].to_owned()) { } else if normalized.length() == 10 &&
normalized.has_prefix("bg:") &&
is_hex_color(normalized[3:].to_owned()) {
let basic_name = basic_name_from_hex(normalized[3:].to_owned()) let basic_name = basic_name_from_hex(normalized[3:].to_owned())
Some({ Some({
..style, ..style,
bg_code: Some(resolve_inline_bg_code(formatter, Some(basic_name), rgb_bg_code(normalized))), bg_code: Some(
resolve_inline_bg_code(
formatter,
Some(basic_name),
rgb_bg_code(normalized),
),
),
bg_basic_name: Some(basic_name), bg_basic_name: Some(basic_name),
}) })
} else { } else {
@@ -741,6 +866,7 @@ fn apply_inline_tag(style : InlineStyle, tag : String, formatter : TextFormatter
} }
} }
///|
fn push_plain_segment( fn push_plain_segment(
segments : Array[StyledSegment], segments : Array[StyledSegment],
buffer : StringBuilder, buffer : StringBuilder,
@@ -753,6 +879,7 @@ fn push_plain_segment(
} }
} }
///|
fn pop_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool { fn pop_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
let normalized = normalize_style_tag_name(tag) let normalized = normalize_style_tag_name(tag)
if stack.length() <= 1 { if stack.length() <= 1 {
@@ -769,6 +896,7 @@ fn pop_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
false false
} }
///|
fn has_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool { fn has_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
let normalized = normalize_style_tag_name(tag) let normalized = normalize_style_tag_name(tag)
if stack.length() <= 1 { if stack.length() <= 1 {
@@ -782,7 +910,11 @@ fn has_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
false false
} }
fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[StyledSegment] { ///|
fn parse_inline_markup(
input : String,
formatter : TextFormatter,
) -> Array[StyledSegment] {
let segments : Array[StyledSegment] = [] let segments : Array[StyledSegment] = []
let buffer = StringBuilder::new() let buffer = StringBuilder::new()
let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }] let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }]
@@ -845,7 +977,12 @@ fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[Style
segments segments
} }
fn render_styled_text(text : String, formatter : TextFormatter, mode : StyleMarkupMode) -> String { ///|
fn render_styled_text(
text : String,
formatter : TextFormatter,
mode : StyleMarkupMode,
) -> String {
match mode { match mode {
StyleMarkupMode::Disabled => return text StyleMarkupMode::Disabled => return text
_ => () _ => ()
@@ -860,10 +997,12 @@ fn render_styled_text(text : String, formatter : TextFormatter, mode : StyleMark
out.to_string() out.to_string()
} }
///|
fn render_inline_markup(message : String, formatter : TextFormatter) -> String { fn render_inline_markup(message : String, formatter : TextFormatter) -> String {
render_styled_text(message, formatter, formatter.style_markup) render_styled_text(message, formatter, formatter.style_markup)
} }
///|
fn level_ansi_code(level : @core.Level) -> String { fn level_ansi_code(level : @core.Level) -> String {
match level { match level {
@core.Level::Trace => "90" @core.Level::Trace => "90"
@@ -874,6 +1013,7 @@ fn level_ansi_code(level : @core.Level) -> String {
} }
} }
///|
fn fields_to_json(fields : Array[@core.Field]) -> Json { fn fields_to_json(fields : Array[@core.Field]) -> Json {
let obj : Map[String, Json] = {} let obj : Map[String, Json] = {}
for item in fields { for item in fields {
@@ -882,58 +1022,89 @@ fn fields_to_json(fields : Array[@core.Field]) -> Json {
Json::object(obj) Json::object(obj)
} }
///|
fn timestamp_text(rec : @core.Record, formatter : TextFormatter) -> String { fn timestamp_text(rec : @core.Record, formatter : TextFormatter) -> String {
if formatter.show_timestamp && rec.timestamp_ms != 0UL { if formatter.show_timestamp && rec.timestamp_ms != 0UL {
ansi_wrap(rec.timestamp_ms.to_string(), "90", use_ansi_color(formatter.color_mode)) ansi_wrap(
rec.timestamp_ms.to_string(),
"90",
use_ansi_color(formatter.color_mode),
)
} else { } else {
"" ""
} }
} }
///|
fn level_text(rec : @core.Record, formatter : TextFormatter) -> String { fn level_text(rec : @core.Record, formatter : TextFormatter) -> String {
if formatter.show_level { if formatter.show_level {
ansi_wrap(rec.level.label(), level_ansi_code(rec.level), use_ansi_color(formatter.color_mode)) ansi_wrap(
rec.level.label(),
level_ansi_code(rec.level),
use_ansi_color(formatter.color_mode),
)
} else { } else {
"" ""
} }
} }
///|
fn target_text(rec : @core.Record, formatter : TextFormatter) -> String { fn target_text(rec : @core.Record, formatter : TextFormatter) -> String {
if formatter.show_target && rec.target != "" { if formatter.show_target && rec.target != "" {
let rendered = render_styled_text(rec.target, formatter, formatter.target_style_markup) let rendered = render_styled_text(
rec.target,
formatter,
formatter.target_style_markup,
)
ansi_wrap(rendered, "34", use_ansi_color(formatter.color_mode)) ansi_wrap(rendered, "34", use_ansi_color(formatter.color_mode))
} else { } else {
"" ""
} }
} }
///|
fn format_field_text(field : @core.Field, formatter : TextFormatter) -> String { fn format_field_text(field : @core.Field, formatter : TextFormatter) -> String {
let value = render_styled_text(field.value, formatter, formatter.fields_style_markup) let value = render_styled_text(
field.value,
formatter,
formatter.fields_style_markup,
)
"\{field.key}=\{value}" "\{field.key}=\{value}"
} }
///|
fn fields_text(rec : @core.Record, formatter : TextFormatter) -> String { fn fields_text(rec : @core.Record, formatter : TextFormatter) -> String {
if formatter.show_fields && rec.fields.length() != 0 { if formatter.show_fields && rec.fields.length() != 0 {
let content = rec.fields.map(fn(field) { format_field_text(field, formatter) }).join(formatter.field_separator) let content = rec.fields
.map(fn(field) { format_field_text(field, formatter) })
.join(formatter.field_separator)
ansi_wrap(content, "35", use_ansi_color(formatter.color_mode)) ansi_wrap(content, "35", use_ansi_color(formatter.color_mode))
} else { } else {
"" ""
} }
} }
///|
fn render_template(rec : @core.Record, formatter : TextFormatter) -> String { fn render_template(rec : @core.Record, formatter : TextFormatter) -> String {
formatter.template formatter.template
.replace_all(old="{timestamp}", new=timestamp_text(rec, formatter)) .replace_all(old="{timestamp}", new=timestamp_text(rec, formatter))
.replace_all(old="{timestamp_ms}", new=timestamp_text(rec, formatter)) .replace_all(old="{timestamp_ms}", new=timestamp_text(rec, formatter))
.replace_all(old="{level}", new=level_text(rec, formatter)) .replace_all(old="{level}", new=level_text(rec, formatter))
.replace_all(old="{target}", new=target_text(rec, formatter)) .replace_all(old="{target}", new=target_text(rec, formatter))
.replace_all(old="{message}", new=render_inline_markup(rec.message, formatter)) .replace_all(
old="{message}",
new=render_inline_markup(rec.message, formatter),
)
.replace_all(old="{fields}", new=fields_text(rec, formatter)) .replace_all(old="{fields}", new=fields_text(rec, formatter))
.trim() .trim()
.to_owned() .to_owned()
} }
pub fn format_text(rec : @core.Record, formatter~ : TextFormatter = text_formatter()) -> String { ///|
pub fn format_text(
rec : @core.Record,
formatter? : TextFormatter = text_formatter(),
) -> String {
if formatter.template != "" { if formatter.template != "" {
return render_template(rec, formatter) return render_template(rec, formatter)
} }
@@ -957,6 +1128,7 @@ pub fn format_text(rec : @core.Record, formatter~ : TextFormatter = text_formatt
} }
} }
///|
pub fn format_json(rec : @core.Record) -> String { pub fn format_json(rec : @core.Record) -> String {
let obj : Map[String, Json] = { let obj : Map[String, Json] = {
"level": Json::string(rec.level.label()), "level": Json::string(rec.level.label()),
+3 -3
View File
@@ -1,7 +1,7 @@
import { import {
"Nanaloveyuki/BitLogger/src/core" @core, "Nanaloveyuki/BitLogger/src/core",
"maria/json_parser" @json_parser, "maria/json_parser",
"moonbitlang/core/env" @env, "moonbitlang/core/env",
"moonbitlang/core/json", "moonbitlang/core/json",
"moonbitlang/core/ref", "moonbitlang/core/ref",
} }
+23 -12
View File
@@ -1,21 +1,22 @@
///|
pub type RecordPatch = (@core.Record) -> @core.Record pub type RecordPatch = (@core.Record) -> @core.Record
///|
pub fn identity_patch() -> RecordPatch { pub fn identity_patch() -> RecordPatch {
fn(rec) { rec } fn(rec) { rec }
} }
///|
pub fn set_target(target : String) -> RecordPatch { pub fn set_target(target : String) -> RecordPatch {
fn(rec) { fn(rec) { rec.with_target(target) }
rec.with_target(target)
}
} }
///|
pub fn prefix_message(prefix : String) -> RecordPatch { pub fn prefix_message(prefix : String) -> RecordPatch {
fn(rec) { fn(rec) { rec.with_message("\{prefix}\{rec.message}") }
rec.with_message("\{prefix}\{rec.message}")
}
} }
///|
pub fn append_fields(extra_fields : Array[@core.Field]) -> RecordPatch { pub fn append_fields(extra_fields : Array[@core.Field]) -> RecordPatch {
fn(rec) { fn(rec) {
if extra_fields.length() == 0 { if extra_fields.length() == 0 {
@@ -28,30 +29,40 @@ pub fn append_fields(extra_fields : Array[@core.Field]) -> RecordPatch {
} }
} }
pub fn redact_field(key : String, placeholder~ : String = "***") -> RecordPatch { ///|
pub fn redact_field(key : String, placeholder? : String = "***") -> RecordPatch {
fn(rec) { fn(rec) {
rec.with_fields(rec.fields.map(fn(field) { rec.with_fields(
rec.fields.map(fn(field) {
if field.key == key { if field.key == key {
field.with_value(placeholder) field.with_value(placeholder)
} else { } else {
field field
} }
})) }),
)
} }
} }
pub fn redact_fields(keys : Array[String], placeholder~ : String = "***") -> RecordPatch { ///|
pub fn redact_fields(
keys : Array[String],
placeholder? : String = "***",
) -> RecordPatch {
fn(rec) { fn(rec) {
rec.with_fields(rec.fields.map(fn(field) { rec.with_fields(
rec.fields.map(fn(field) {
if keys.contains(field.key) { if keys.contains(field.key) {
field.with_value(placeholder) field.with_value(placeholder)
} else { } else {
field field
} }
})) }),
)
} }
} }
///|
pub fn compose_patches(patches : Array[RecordPatch]) -> RecordPatch { pub fn compose_patches(patches : Array[RecordPatch]) -> RecordPatch {
fn(rec) { fn(rec) {
let mut current = rec let mut current = rec
+55 -16
View File
@@ -1,3 +1,4 @@
///|
pub struct RuntimeFileState { pub struct RuntimeFileState {
file : FileSinkState file : FileSinkState
queued : Bool queued : Bool
@@ -5,16 +6,20 @@ pub struct RuntimeFileState {
dropped_count : Int dropped_count : Int
} }
///|
pub fn RuntimeFileState::new( pub fn RuntimeFileState::new(
file : FileSinkState, file : FileSinkState,
queued~ : Bool = false, queued? : Bool = false,
pending_count~ : Int = 0, pending_count? : Int = 0,
dropped_count~ : Int = 0, dropped_count? : Int = 0,
) -> RuntimeFileState { ) -> RuntimeFileState {
{ file, queued, pending_count, dropped_count } { file, queued, pending_count, dropped_count }
} }
fn file_sink_policy_to_json_value(policy : FileSinkPolicy) -> @json_parser.JsonValue { ///|
fn file_sink_policy_to_json_value(
policy : FileSinkPolicy,
) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, @json_parser.JsonValue] = {
"append": @json_parser.JsonValue::Bool(policy.append), "append": @json_parser.JsonValue::Bool(policy.append),
"auto_flush": @json_parser.JsonValue::Bool(policy.auto_flush), "auto_flush": @json_parser.JsonValue::Bool(policy.auto_flush),
@@ -26,11 +31,18 @@ fn file_sink_policy_to_json_value(policy : FileSinkPolicy) -> @json_parser.JsonV
@json_parser.JsonValue::Object(obj) @json_parser.JsonValue::Object(obj)
} }
pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> @json_parser.JsonValue { ///|
pub fn file_sink_policy_to_json(
policy : FileSinkPolicy,
) -> @json_parser.JsonValue {
file_sink_policy_to_json_value(policy) file_sink_policy_to_json_value(policy)
} }
pub fn stringify_file_sink_policy(policy : FileSinkPolicy, pretty~ : Bool = false) -> String { ///|
pub fn stringify_file_sink_policy(
policy : FileSinkPolicy,
pretty? : Bool = false,
) -> String {
let value = file_sink_policy_to_json_value(policy) let value = file_sink_policy_to_json_value(policy)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) @json_parser.stringify_pretty(value, 2)
@@ -39,16 +51,27 @@ pub fn stringify_file_sink_policy(policy : FileSinkPolicy, pretty~ : Bool = fals
} }
} }
fn file_sink_state_to_json_value(state : FileSinkState) -> @json_parser.JsonValue { ///|
fn file_sink_state_to_json_value(
state : FileSinkState,
) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = { let obj : Map[String, @json_parser.JsonValue] = {
"path": @json_parser.JsonValue::String(state.path), "path": @json_parser.JsonValue::String(state.path),
"available": @json_parser.JsonValue::Bool(state.available), "available": @json_parser.JsonValue::Bool(state.available),
"append": @json_parser.JsonValue::Bool(state.append), "append": @json_parser.JsonValue::Bool(state.append),
"auto_flush": @json_parser.JsonValue::Bool(state.auto_flush), "auto_flush": @json_parser.JsonValue::Bool(state.auto_flush),
"open_failures": @json_parser.JsonValue::Number(state.open_failures.to_double()), "open_failures": @json_parser.JsonValue::Number(
"write_failures": @json_parser.JsonValue::Number(state.write_failures.to_double()), state.open_failures.to_double(),
"flush_failures": @json_parser.JsonValue::Number(state.flush_failures.to_double()), ),
"rotation_failures": @json_parser.JsonValue::Number(state.rotation_failures.to_double()), "write_failures": @json_parser.JsonValue::Number(
state.write_failures.to_double(),
),
"flush_failures": @json_parser.JsonValue::Number(
state.flush_failures.to_double(),
),
"rotation_failures": @json_parser.JsonValue::Number(
state.rotation_failures.to_double(),
),
} }
match state.rotation { match state.rotation {
None => obj["rotation"] = @json_parser.JsonValue::Null None => obj["rotation"] = @json_parser.JsonValue::Null
@@ -57,11 +80,16 @@ fn file_sink_state_to_json_value(state : FileSinkState) -> @json_parser.JsonValu
@json_parser.JsonValue::Object(obj) @json_parser.JsonValue::Object(obj)
} }
///|
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue { pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue {
file_sink_state_to_json_value(state) file_sink_state_to_json_value(state)
} }
pub fn stringify_file_sink_state(state : FileSinkState, pretty~ : Bool = false) -> String { ///|
pub fn stringify_file_sink_state(
state : FileSinkState,
pretty? : Bool = false,
) -> String {
let value = file_sink_state_to_json_value(state) let value = file_sink_state_to_json_value(state)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) @json_parser.stringify_pretty(value, 2)
@@ -70,16 +98,27 @@ pub fn stringify_file_sink_state(state : FileSinkState, pretty~ : Bool = false)
} }
} }
pub fn runtime_file_state_to_json(state : RuntimeFileState) -> @json_parser.JsonValue { ///|
pub fn runtime_file_state_to_json(
state : RuntimeFileState,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({ @json_parser.JsonValue::Object({
"file": file_sink_state_to_json_value(state.file), "file": file_sink_state_to_json_value(state.file),
"queued": @json_parser.JsonValue::Bool(state.queued), "queued": @json_parser.JsonValue::Bool(state.queued),
"pending_count": @json_parser.JsonValue::Number(state.pending_count.to_double()), "pending_count": @json_parser.JsonValue::Number(
"dropped_count": @json_parser.JsonValue::Number(state.dropped_count.to_double()), state.pending_count.to_double(),
),
"dropped_count": @json_parser.JsonValue::Number(
state.dropped_count.to_double(),
),
}) })
} }
pub fn stringify_runtime_file_state(state : RuntimeFileState, pretty~ : Bool = false) -> String { ///|
pub fn stringify_runtime_file_state(
state : RuntimeFileState,
pretty? : Bool = false,
) -> String {
let value = runtime_file_state_to_json(state) let value = runtime_file_state_to_json(state)
if pretty { if pretty {
@json_parser.stringify_pretty(value, 2) @json_parser.stringify_pretty(value, 2)
+29 -14
View File
@@ -1,8 +1,10 @@
///|
pub struct FileRotation { pub struct FileRotation {
max_bytes : Int max_bytes : Int
max_backups : Int max_backups : Int
} }
///|
pub struct FileSinkState { pub struct FileSinkState {
path : String path : String
available : Bool available : Bool
@@ -15,30 +17,33 @@ pub struct FileSinkState {
rotation_failures : Int rotation_failures : Int
} }
///|
pub struct FileSinkPolicy { pub struct FileSinkPolicy {
append : Bool append : Bool
auto_flush : Bool auto_flush : Bool
rotation : FileRotation? rotation : FileRotation?
} }
///|
pub fn FileSinkPolicy::new( pub fn FileSinkPolicy::new(
append~ : Bool = true, append? : Bool = true,
auto_flush~ : Bool = true, auto_flush? : Bool = true,
rotation~ : FileRotation? = None, rotation? : FileRotation? = None,
) -> FileSinkPolicy { ) -> FileSinkPolicy {
{ append, auto_flush, rotation } { append, auto_flush, rotation }
} }
///|
pub fn FileSinkState::new( pub fn FileSinkState::new(
path : String, path : String,
available~ : Bool = false, available? : Bool = false,
append~ : Bool = true, append? : Bool = true,
auto_flush~ : Bool = true, auto_flush? : Bool = true,
rotation~ : FileRotation? = None, rotation? : FileRotation? = None,
open_failures~ : Int = 0, open_failures? : Int = 0,
write_failures~ : Int = 0, write_failures? : Int = 0,
flush_failures~ : Int = 0, flush_failures? : Int = 0,
rotation_failures~ : Int = 0, rotation_failures? : Int = 0,
) -> FileSinkState { ) -> FileSinkState {
{ {
path, path,
@@ -53,13 +58,23 @@ pub fn FileSinkState::new(
} }
} }
pub fn file_rotation(max_bytes : Int, max_backups~ : Int = 1) -> FileRotation { ///|
pub fn file_rotation(max_bytes : Int, max_backups? : Int = 1) -> FileRotation {
{ {
max_bytes: if max_bytes <= 0 { 1 } else { max_bytes }, max_bytes: if max_bytes <= 0 {
max_backups: if max_backups <= 0 { 1 } else { max_backups }, 1
} else {
max_bytes
},
max_backups: if max_backups <= 0 {
1
} else {
max_backups
},
} }
} }
///|
pub(all) enum QueueOverflowPolicy { pub(all) enum QueueOverflowPolicy {
DropNewest DropNewest
DropOldest DropOldest