mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-07-24 00:42:18 +00:00
🔊 Update 1.0.0
This commit is contained in:
@@ -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
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
Versioned BitLogger change summaries.
|
||||
|
||||
- [1.0.0](./1.0.0.md)
|
||||
- [0.5.3](./0.5.3.md)
|
||||
- [0.5.2](./0.5.2.md)
|
||||
- [0.5.1](./0.5.1.md)
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
///|
|
||||
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(@lib_async.stringify_async_runtime_state(@lib_async.async_runtime_state(), pretty=true))
|
||||
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(
|
||||
@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 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)
|
||||
.with_context_fields([@lib.field("service", "bitlogger")])
|
||||
.with_filter(@lib.target_has_prefix("async"))
|
||||
.with_patch(@lib.compose_patches([
|
||||
@lib.prefix_message("[async] "),
|
||||
@lib.redact_fields(["token"]),
|
||||
]))
|
||||
.with_patch(
|
||||
@lib.compose_patches([
|
||||
@lib.prefix_message("[async] "),
|
||||
@lib.redact_fields(["token"]),
|
||||
]),
|
||||
)
|
||||
|
||||
println(@lib_async.stringify_async_logger_state(logger.state(), pretty=true))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @lib,
|
||||
"Nanaloveyuki/BitLogger/src-async" @lib_async,
|
||||
"moonbitlang/async" @async,
|
||||
"moonbitlang/async",
|
||||
}
|
||||
|
||||
supported_targets = "+native"
|
||||
|
||||
+74
-32
@@ -1,10 +1,14 @@
|
||||
///|
|
||||
fn main {
|
||||
let preset_logger = @lib.build_logger(
|
||||
@lib.with_queue(
|
||||
@lib.text_console(
|
||||
min_level=@lib.Level::Info,
|
||||
target="preset",
|
||||
text_formatter=@lib.TextFormatterConfig::new(show_timestamp=false, separator=" | "),
|
||||
text_formatter=@lib.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
separator=" | ",
|
||||
),
|
||||
),
|
||||
max_pending=2,
|
||||
overflow=@lib.QueueOverflowPolicy::DropOldest,
|
||||
@@ -20,8 +24,11 @@ fn main {
|
||||
@lib.info("hello from BitLogger", fields=[@lib.field("mode", "demo")])
|
||||
|
||||
// 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")
|
||||
.with_context_fields([@lib.field("service", "bitlogger")])
|
||||
let logger = @lib.Logger::new(
|
||||
@lib.console_sink(),
|
||||
min_level=@lib.Level::Trace,
|
||||
target="custom",
|
||||
).with_context_fields([@lib.field("service", "bitlogger")])
|
||||
logger.debug("custom logger ready", fields=[@lib.field("sink", "console")])
|
||||
|
||||
let bound_logger = @lib.Logger::new(
|
||||
@@ -31,7 +38,11 @@ fn main {
|
||||
).bind(@lib.fields([("service", "bitlogger"), ("scope", "audit")]))
|
||||
bound_logger.info("bound logger ready", fields=[@lib.field("mode", "demo")])
|
||||
|
||||
let json_logger = @lib.Logger::new(@lib.json_console_sink(), min_level=@lib.Level::Info, target="json")
|
||||
let json_logger = @lib.Logger::new(
|
||||
@lib.json_console_sink(),
|
||||
min_level=@lib.Level::Info,
|
||||
target="json",
|
||||
)
|
||||
json_logger.info("json output", fields=[@lib.field("kind", "example")])
|
||||
|
||||
let fanout_logger = @lib.Logger::new(
|
||||
@@ -55,12 +66,18 @@ fn main {
|
||||
split_logger.info("normal output")
|
||||
split_logger.warn("warning output")
|
||||
|
||||
let timed_logger = @lib.Logger::new(@lib.console_sink(), min_level=@lib.Level::Info, target="timed")
|
||||
.with_timestamp()
|
||||
let timed_logger = @lib.Logger::new(
|
||||
@lib.console_sink(),
|
||||
min_level=@lib.Level::Info,
|
||||
target="timed",
|
||||
).with_timestamp()
|
||||
timed_logger.info("timestamp enabled", fields=[@lib.field("kind", "time")])
|
||||
|
||||
let child_logger = @lib.Logger::new(@lib.console_sink(), min_level=@lib.Level::Info, target="app")
|
||||
.child("worker")
|
||||
let child_logger = @lib.Logger::new(
|
||||
@lib.console_sink(),
|
||||
min_level=@lib.Level::Info,
|
||||
target="app",
|
||||
).child("worker")
|
||||
child_logger.info("child target ready")
|
||||
|
||||
let callback_logger = @lib.Logger::new(
|
||||
@@ -90,8 +107,8 @@ fn main {
|
||||
color_mode=@lib.ColorMode::Always,
|
||||
).with_style_tags(
|
||||
@lib.default_style_tag_registry()
|
||||
.set_tag("accent", fg=Some("#4cc9f0"), bold=true)
|
||||
.define_alias("danger", "red"),
|
||||
.set_tag("accent", fg=Some("#4cc9f0"), bold=true)
|
||||
.define_alias("danger", "red"),
|
||||
)
|
||||
let styled_logger = @lib.Logger::new(
|
||||
@lib.text_console_sink(styled_formatter),
|
||||
@@ -109,25 +126,36 @@ fn main {
|
||||
min_level=@lib.Level::Info,
|
||||
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.close())
|
||||
}
|
||||
|
||||
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 two")
|
||||
buffered.flush()
|
||||
|
||||
let filtered = @lib.filter_sink(
|
||||
@lib.console_sink(),
|
||||
fn(rec) {
|
||||
rec.target == "kept"
|
||||
},
|
||||
let filtered = @lib.filter_sink(@lib.console_sink(), fn(rec) {
|
||||
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")
|
||||
dropped_logger.info("filter dropped this")
|
||||
|
||||
@@ -135,10 +163,12 @@ fn main {
|
||||
@lib.console_sink(),
|
||||
min_level=@lib.Level::Info,
|
||||
target="service",
|
||||
).with_filter(@lib.all_of([
|
||||
@lib.target_has_prefix("service"),
|
||||
@lib.message_contains("kept"),
|
||||
]))
|
||||
).with_filter(
|
||||
@lib.all_of([
|
||||
@lib.target_has_prefix("service"),
|
||||
@lib.message_contains("kept"),
|
||||
]),
|
||||
)
|
||||
filtered_logger.info("logger filter dropped this")
|
||||
filtered_logger.child("api").info("logger filter kept this")
|
||||
|
||||
@@ -146,12 +176,17 @@ fn main {
|
||||
@lib.console_sink(),
|
||||
min_level=@lib.Level::Info,
|
||||
target="auth",
|
||||
).with_patch(@lib.compose_patches([
|
||||
@lib.prefix_message("[safe] "),
|
||||
@lib.redact_fields(["token"]),
|
||||
@lib.append_fields([@lib.field("service", "bitlogger")]),
|
||||
]))
|
||||
patched_logger.info("login", fields=[@lib.field("user", "alice"), @lib.field("token", "secret")])
|
||||
).with_patch(
|
||||
@lib.compose_patches([
|
||||
@lib.prefix_message("[safe] "),
|
||||
@lib.redact_fields(["token"]),
|
||||
@lib.append_fields([@lib.field("service", "bitlogger")]),
|
||||
]),
|
||||
)
|
||||
patched_logger.info("login", fields=[
|
||||
@lib.field("user", "alice"),
|
||||
@lib.field("token", "secret"),
|
||||
])
|
||||
|
||||
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\"}}",
|
||||
@@ -165,7 +200,10 @@ fn main {
|
||||
config_logger.info("configured from json")
|
||||
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))
|
||||
|
||||
let formatter_config = @lib.TextFormatterConfig::new(
|
||||
@@ -195,13 +233,17 @@ fn main {
|
||||
|
||||
let file_runtime_logger = @lib.build_logger(
|
||||
@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)),
|
||||
),
|
||||
)
|
||||
file_runtime_logger.info("runtime file logger ready")
|
||||
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 => ()
|
||||
}
|
||||
ignore(file_runtime_logger.file_close())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
fn main {
|
||||
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\"}}",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
fn main {
|
||||
let logger = @lib.build_logger(
|
||||
@lib.console(min_level=@lib.Level::Info, target="demo.console"),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
///|
|
||||
fn main {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
fn main {
|
||||
let config = @lib.with_queue(
|
||||
@lib.text_console(
|
||||
@@ -36,10 +37,14 @@ fn main {
|
||||
}
|
||||
|
||||
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.file_close())
|
||||
} 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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
fn main {
|
||||
let logger = @lib.build_logger(
|
||||
@lib.text_console(
|
||||
@@ -6,9 +7,7 @@ fn main {
|
||||
text_formatter=@lib.TextFormatterConfig::new(
|
||||
show_timestamp=false,
|
||||
color_mode=@lib.ColorMode::Always,
|
||||
style_tags={
|
||||
"accent": @lib.text_style(fg=Some("#4cc9f0"), bold=true),
|
||||
},
|
||||
style_tags={ "accent": @lib.text_style(fg=Some("#4cc9f0"), bold=true) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
fn main {
|
||||
let logger = @lib.build_logger(
|
||||
@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"),
|
||||
])
|
||||
}
|
||||
|
||||
+1568
-741
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,26 @@
|
||||
///|
|
||||
pub type ApplicationAsyncLogger = AsyncLogger[@bitlogger.RuntimeSink]
|
||||
|
||||
pub type ApplicationTextAsyncLogger = AsyncLogger[@bitlogger.FormattedConsoleSink]
|
||||
///|
|
||||
pub type ApplicationTextAsyncLogger = AsyncLogger[
|
||||
@bitlogger.FormattedConsoleSink,
|
||||
]
|
||||
|
||||
///|
|
||||
pub fn build_application_async_logger(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> ApplicationAsyncLogger {
|
||||
build_async_logger(config)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn build_application_text_async_logger(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> ApplicationTextAsyncLogger {
|
||||
build_async_text_logger(config)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn parse_and_build_application_async_logger(
|
||||
input : String,
|
||||
) -> ApplicationAsyncLogger raise {
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
///|
|
||||
pub fn async_runtime_mode() -> AsyncRuntimeMode {
|
||||
@utils.native_worker_async_runtime_mode()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_runtime_supports_background_worker() -> Bool {
|
||||
ignore(all_async_runtime_modes())
|
||||
true
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_runtime_guard_closed_on_log() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_runtime_shutdown_clears_pending_after_wait_idle() -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_runtime_shutdown_waits_for_worker() -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,84 +1,125 @@
|
||||
///|
|
||||
pub(all) suberror AsyncLoggerClosed {
|
||||
AsyncLoggerClosed
|
||||
}
|
||||
|
||||
///|
|
||||
pub type AsyncOverflowPolicy = @utils.AsyncOverflowPolicy
|
||||
|
||||
///|
|
||||
pub type AsyncFlushPolicy = @utils.AsyncFlushPolicy
|
||||
|
||||
///|
|
||||
pub type AsyncRuntimeMode = @utils.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 AsyncLoggerState = @utils.AsyncLoggerState
|
||||
|
||||
///|
|
||||
pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String {
|
||||
@utils.async_runtime_mode_label(mode)
|
||||
}
|
||||
|
||||
///|
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_async_runtime_state(
|
||||
state : AsyncRuntimeState,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_async_logger_state(
|
||||
state : AsyncLoggerState,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> String {
|
||||
@utils.stringify_async_logger_state(state, pretty=pretty)
|
||||
@utils.stringify_async_logger_state(state, pretty~)
|
||||
}
|
||||
|
||||
///|
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_logger_build_config_to_json(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> @json_parser.JsonValue {
|
||||
@utils.async_logger_build_config_to_json(config)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_async_logger_build_config(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> String {
|
||||
@utils.stringify_async_logger_build_config(config, pretty=pretty)
|
||||
@utils.stringify_async_logger_build_config(config, pretty~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct AsyncLogger[S] {
|
||||
min_level : @bitlogger.Level
|
||||
target : String
|
||||
@@ -101,12 +142,13 @@ pub struct AsyncLogger[S] {
|
||||
last_error : Ref[String]
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] async_logger(
|
||||
sink : S,
|
||||
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
|
||||
min_level~ : @bitlogger.Level = @bitlogger.Level::Info,
|
||||
target~ : String = "",
|
||||
flush~ : (S) -> Int raise = fn(_) { 0 },
|
||||
config? : AsyncLoggerConfig = AsyncLoggerConfig::new(),
|
||||
min_level? : @bitlogger.Level = @bitlogger.Level::Info,
|
||||
target? : String = "",
|
||||
flush? : (S) -> Int raise = fn(_) { 0 },
|
||||
) -> AsyncLogger[S] {
|
||||
{
|
||||
min_level,
|
||||
@@ -131,6 +173,7 @@ pub fn[S] async_logger(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn queue_kind_of(config : AsyncLoggerConfig) -> @aqueue.Kind {
|
||||
let limit = if config.max_pending < 0 { 0 } else { config.max_pending }
|
||||
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 }
|
||||
}
|
||||
|
||||
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(
|
||||
self : AsyncLogger[S],
|
||||
fields : Array[@bitlogger.Field],
|
||||
@@ -155,39 +207,33 @@ pub fn[S] AsyncLogger::with_context_fields(
|
||||
{ ..self, context_fields: fields }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::with_filter(
|
||||
self : AsyncLogger[S],
|
||||
predicate : (@bitlogger.Record) -> Bool,
|
||||
) -> AsyncLogger[S] {
|
||||
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(
|
||||
self : AsyncLogger[S],
|
||||
patch : @bitlogger.RecordPatch,
|
||||
) -> AsyncLogger[S] {
|
||||
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(
|
||||
self : AsyncLogger[S],
|
||||
min_level : @bitlogger.Level,
|
||||
) -> AsyncLogger[S] {
|
||||
{ ..self, min_level }
|
||||
{ ..self, min_level, }
|
||||
}
|
||||
|
||||
///|
|
||||
fn combine_targets(parent : String, child : String) -> String {
|
||||
if parent == "" {
|
||||
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) }
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
fn merge_fields(
|
||||
left : Array[@bitlogger.Field],
|
||||
right : Array[@bitlogger.Field],
|
||||
@@ -219,32 +274,27 @@ fn merge_fields(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] AsyncLogger::log(
|
||||
self : AsyncLogger[S],
|
||||
level : @bitlogger.Level,
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
target? : String = "",
|
||||
) -> Unit {
|
||||
guard !(async_runtime_guard_closed_on_log() && self.is_closed()) else {
|
||||
()
|
||||
}
|
||||
guard self.is_enabled(level) else {
|
||||
()
|
||||
}
|
||||
guard !(async_runtime_guard_closed_on_log() && self.is_closed()) else { () }
|
||||
guard self.is_enabled(level) else { () }
|
||||
let actual_target = if target == "" { self.target } else { target }
|
||||
let timestamp_ms = if self.timestamp { @env.now() } else { 0UL }
|
||||
let rec = @bitlogger.Record::new(
|
||||
level,
|
||||
message,
|
||||
timestamp_ms=timestamp_ms,
|
||||
timestamp_ms~,
|
||||
target=actual_target,
|
||||
fields=merge_fields(self.context_fields, fields),
|
||||
)
|
||||
let rec = (self.patch)(rec)
|
||||
guard (self.filter)(rec) else {
|
||||
()
|
||||
}
|
||||
guard (self.filter)(rec) else { () }
|
||||
let accepted = self.queue.try_put(rec) catch {
|
||||
err if err is AsyncLoggerClosed => false
|
||||
err => raise err
|
||||
@@ -254,7 +304,7 @@ pub async fn[S] AsyncLogger::log(
|
||||
} else {
|
||||
match self.overflow {
|
||||
AsyncOverflowPolicy::Blocking => {
|
||||
let accepted = (async fn() -> Bool raise {
|
||||
let accepted = (async fn() -> Bool {
|
||||
self.queue.put(rec)
|
||||
true
|
||||
})() catch {
|
||||
@@ -265,81 +315,93 @@ pub async fn[S] AsyncLogger::log(
|
||||
self.pending_count.val += 1
|
||||
}
|
||||
}
|
||||
AsyncOverflowPolicy::DropOldest | AsyncOverflowPolicy::DropNewest => {
|
||||
AsyncOverflowPolicy::DropOldest | AsyncOverflowPolicy::DropNewest =>
|
||||
self.dropped_count.val += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] AsyncLogger::trace(
|
||||
self : AsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> Unit {
|
||||
self.log(@bitlogger.Level::Trace, message, fields=fields)
|
||||
self.log(@bitlogger.Level::Trace, message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] AsyncLogger::debug(
|
||||
self : AsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> Unit {
|
||||
self.log(@bitlogger.Level::Debug, message, fields=fields)
|
||||
self.log(@bitlogger.Level::Debug, message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] AsyncLogger::info(
|
||||
self : AsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> Unit {
|
||||
self.log(@bitlogger.Level::Info, message, fields=fields)
|
||||
self.log(@bitlogger.Level::Info, message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] AsyncLogger::warn(
|
||||
self : AsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> Unit {
|
||||
self.log(@bitlogger.Level::Warn, message, fields=fields)
|
||||
self.log(@bitlogger.Level::Warn, message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] AsyncLogger::error(
|
||||
self : AsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> 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 {
|
||||
self.pending_count.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::dropped_count(self : AsyncLogger[S]) -> Int {
|
||||
self.dropped_count.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::is_closed(self : AsyncLogger[S]) -> Bool {
|
||||
self.is_closed.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::is_running(self : AsyncLogger[S]) -> Bool {
|
||||
self.is_running.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::has_failed(self : AsyncLogger[S]) -> Bool {
|
||||
self.has_failed.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::last_error(self : AsyncLogger[S]) -> String {
|
||||
self.last_error.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::flush_policy(self : AsyncLogger[S]) -> AsyncFlushPolicy {
|
||||
self.flush_policy
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] AsyncLogger::state(self : AsyncLogger[S]) -> AsyncLoggerState {
|
||||
AsyncLoggerState::new(
|
||||
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
|
||||
if clear {
|
||||
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.queue.close(error=AsyncLoggerClosed, clear=clear)
|
||||
self.queue.close(error=AsyncLoggerClosed, clear~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit {
|
||||
while self.pending_count() > 0 {
|
||||
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 {
|
||||
self.close(clear=true)
|
||||
} else {
|
||||
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)
|
||||
} else {
|
||||
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 {
|
||||
while true {
|
||||
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 {
|
||||
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 => 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.has_failed.val = false
|
||||
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
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn build_async_logger(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> AsyncLogger[@bitlogger.RuntimeSink] {
|
||||
@@ -475,9 +554,14 @@ pub fn build_async_logger(
|
||||
).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(
|
||||
@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,
|
||||
min_level=config.logger.min_level,
|
||||
target=config.logger.target,
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
///|
|
||||
pub fn async_runtime_mode() -> AsyncRuntimeMode {
|
||||
@utils.compatibility_async_runtime_mode()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_runtime_supports_background_worker() -> Bool {
|
||||
ignore(all_async_runtime_modes())
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_runtime_guard_closed_on_log() -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_runtime_shutdown_clears_pending_after_wait_idle() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_runtime_shutdown_waits_for_worker() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,61 +1,67 @@
|
||||
///|
|
||||
pub struct LibraryAsyncLogger[S] {
|
||||
inner : AsyncLogger[S]
|
||||
}
|
||||
|
||||
///|
|
||||
fn[S] library_async_logger(logger : AsyncLogger[S]) -> LibraryAsyncLogger[S] {
|
||||
{ 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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::new(
|
||||
sink : S,
|
||||
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
|
||||
min_level~ : @bitlogger.Level = @bitlogger.Level::Info,
|
||||
target~ : String = "",
|
||||
flush~ : (S) -> Int raise = fn(_) { 0 },
|
||||
config? : AsyncLoggerConfig = AsyncLoggerConfig::new(),
|
||||
min_level? : @bitlogger.Level = @bitlogger.Level::Info,
|
||||
target? : String = "",
|
||||
flush? : (S) -> Int raise = fn(_) { 0 },
|
||||
) -> LibraryAsyncLogger[S] {
|
||||
library_async_logger(
|
||||
async_logger(
|
||||
sink,
|
||||
config=config,
|
||||
min_level=min_level,
|
||||
target=target,
|
||||
flush=flush,
|
||||
),
|
||||
)
|
||||
library_async_logger(async_logger(sink, config~, min_level~, target~, 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
|
||||
}
|
||||
|
||||
///|
|
||||
fn[S] configured_library_async_logger(
|
||||
logger : AsyncLogger[S],
|
||||
) -> LibraryAsyncLogger[S] {
|
||||
library_async_logger(logger)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn build_library_async_logger(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> LibraryAsyncLogger[@bitlogger.RuntimeSink] {
|
||||
configured_library_async_logger(build_async_logger(config))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn build_library_async_text_logger(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> LibraryAsyncLogger[@bitlogger.FormattedConsoleSink] {
|
||||
configured_library_async_logger(build_async_text_logger(config))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn parse_and_build_library_async_logger(
|
||||
input : String,
|
||||
) -> LibraryAsyncLogger[@bitlogger.RuntimeSink] raise {
|
||||
build_library_async_logger(parse_async_logger_build_config_text(input))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::with_target(
|
||||
self : LibraryAsyncLogger[S],
|
||||
target : String,
|
||||
@@ -63,6 +69,7 @@ pub fn[S] LibraryAsyncLogger::with_target(
|
||||
library_async_logger(self.inner.with_target(target))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::child(
|
||||
self : LibraryAsyncLogger[S],
|
||||
target : String,
|
||||
@@ -70,6 +77,7 @@ pub fn[S] LibraryAsyncLogger::child(
|
||||
library_async_logger(self.inner.child(target))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::with_context_fields(
|
||||
self : LibraryAsyncLogger[S],
|
||||
fields : Array[@bitlogger.Field],
|
||||
@@ -77,6 +85,7 @@ pub fn[S] LibraryAsyncLogger::with_context_fields(
|
||||
library_async_logger(self.inner.with_context_fields(fields))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::bind(
|
||||
self : LibraryAsyncLogger[S],
|
||||
fields : Array[@bitlogger.Field],
|
||||
@@ -84,6 +93,7 @@ pub fn[S] LibraryAsyncLogger::bind(
|
||||
self.with_context_fields(fields)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryAsyncLogger::is_enabled(
|
||||
self : LibraryAsyncLogger[S],
|
||||
level : @bitlogger.Level,
|
||||
@@ -91,47 +101,55 @@ pub fn[S] LibraryAsyncLogger::is_enabled(
|
||||
self.inner.is_enabled(level)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] LibraryAsyncLogger::log(
|
||||
self : LibraryAsyncLogger[S],
|
||||
level : @bitlogger.Level,
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
target? : String = "",
|
||||
) -> Unit {
|
||||
self.inner.log(level, message, fields=fields, target=target)
|
||||
self.inner.log(level, message, fields~, target~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] LibraryAsyncLogger::info(
|
||||
self : LibraryAsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> Unit {
|
||||
self.inner.info(message, fields=fields)
|
||||
self.inner.info(message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] LibraryAsyncLogger::warn(
|
||||
self : LibraryAsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> Unit {
|
||||
self.inner.warn(message, fields=fields)
|
||||
self.inner.warn(message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] LibraryAsyncLogger::error(
|
||||
self : LibraryAsyncLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[@bitlogger.Field] = [],
|
||||
fields? : Array[@bitlogger.Field] = [],
|
||||
) -> 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()
|
||||
}
|
||||
|
||||
///|
|
||||
pub async fn[S] LibraryAsyncLogger::shutdown(
|
||||
self : LibraryAsyncLogger[S],
|
||||
clear? : Bool = false,
|
||||
) -> Unit {
|
||||
self.inner.shutdown(clear=clear)
|
||||
self.inner.shutdown(clear~)
|
||||
}
|
||||
|
||||
+12
-6
@@ -1,17 +1,23 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @bitlogger,
|
||||
"Nanaloveyuki/BitLogger/src-async/utils" @utils,
|
||||
"maria/json_parser" @json_parser,
|
||||
"moonbitlang/async" @async,
|
||||
"moonbitlang/async/aqueue" @aqueue,
|
||||
"moonbitlang/core/env" @env,
|
||||
"Nanaloveyuki/BitLogger/src-async/utils",
|
||||
"maria/json_parser",
|
||||
"moonbitlang/async",
|
||||
"moonbitlang/async/aqueue",
|
||||
"moonbitlang/core/env",
|
||||
"moonbitlang/core/ref",
|
||||
}
|
||||
|
||||
options(
|
||||
targets: {
|
||||
"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" ],
|
||||
"async_logger_native.mbt": [ "native", "llvm" ],
|
||||
"async_logger_stub.mbt": [ "js", "wasm", "wasm-gc" ],
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
///|
|
||||
pub(all) enum AsyncOverflowPolicy {
|
||||
Blocking
|
||||
DropOldest
|
||||
DropNewest
|
||||
}
|
||||
|
||||
///|
|
||||
pub(all) enum AsyncFlushPolicy {
|
||||
Never
|
||||
Batch
|
||||
Shutdown
|
||||
}
|
||||
|
||||
///|
|
||||
pub enum AsyncRuntimeMode {
|
||||
NativeWorker
|
||||
Compatibility
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String {
|
||||
match mode {
|
||||
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 {
|
||||
AsyncRuntimeMode::NativeWorker
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn compatibility_async_runtime_mode() -> AsyncRuntimeMode {
|
||||
AsyncRuntimeMode::Compatibility
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct AsyncRuntimeState {
|
||||
mode : AsyncRuntimeMode
|
||||
background_worker : Bool
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn AsyncRuntimeState::new(
|
||||
mode : AsyncRuntimeMode,
|
||||
background_worker : Bool,
|
||||
@@ -42,6 +50,7 @@ pub fn AsyncRuntimeState::new(
|
||||
{ mode, background_worker }
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct AsyncLoggerState {
|
||||
runtime : AsyncRuntimeState
|
||||
pending_count : Int
|
||||
@@ -53,6 +62,7 @@ pub struct AsyncLoggerState {
|
||||
flush_policy : AsyncFlushPolicy
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn AsyncLoggerState::new(
|
||||
runtime : AsyncRuntimeState,
|
||||
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({
|
||||
"mode": @json_parser.JsonValue::String(async_runtime_mode_label(state.mode)),
|
||||
"background_worker": @json_parser.JsonValue::Bool(state.background_worker),
|
||||
})
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_async_runtime_state(
|
||||
state : AsyncRuntimeState,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> String {
|
||||
let value = async_runtime_state_to_json(state)
|
||||
if pretty {
|
||||
@@ -94,6 +108,7 @@ pub fn stringify_async_runtime_state(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn async_flush_policy_label(policy : AsyncFlushPolicy) -> String {
|
||||
match policy {
|
||||
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({
|
||||
"runtime": async_runtime_state_to_json(state.runtime),
|
||||
"pending_count": @json_parser.JsonValue::Number(state.pending_count.to_double()),
|
||||
"dropped_count": @json_parser.JsonValue::Number(state.dropped_count.to_double()),
|
||||
"pending_count": @json_parser.JsonValue::Number(
|
||||
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_running": @json_parser.JsonValue::Bool(state.is_running),
|
||||
"has_failed": @json_parser.JsonValue::Bool(state.has_failed),
|
||||
"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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_async_logger_state(
|
||||
state : AsyncLoggerState,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> String {
|
||||
let value = async_logger_state_to_json_value(state)
|
||||
if pretty {
|
||||
@@ -131,6 +159,7 @@ pub fn stringify_async_logger_state(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct AsyncLoggerConfig {
|
||||
max_pending : Int
|
||||
overflow : AsyncOverflowPolicy
|
||||
@@ -139,22 +168,32 @@ pub struct AsyncLoggerConfig {
|
||||
flush : AsyncFlushPolicy
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn AsyncLoggerConfig::new(
|
||||
max_pending~ : Int = 0,
|
||||
overflow~ : AsyncOverflowPolicy = AsyncOverflowPolicy::Blocking,
|
||||
max_batch~ : Int = 1,
|
||||
linger_ms~ : Int = 0,
|
||||
flush~ : AsyncFlushPolicy = AsyncFlushPolicy::Never,
|
||||
max_pending? : Int = 0,
|
||||
overflow? : AsyncOverflowPolicy = AsyncOverflowPolicy::Blocking,
|
||||
max_batch? : Int = 1,
|
||||
linger_ms? : Int = 0,
|
||||
flush? : AsyncFlushPolicy = AsyncFlushPolicy::Never,
|
||||
) -> AsyncLoggerConfig {
|
||||
{
|
||||
max_pending,
|
||||
overflow,
|
||||
max_batch: if max_batch <= 1 { 1 } else { max_batch },
|
||||
linger_ms: if linger_ms < 0 { 0 } else { linger_ms },
|
||||
max_batch: if max_batch <= 1 {
|
||||
1
|
||||
} else {
|
||||
max_batch
|
||||
},
|
||||
linger_ms: if linger_ms < 0 {
|
||||
0
|
||||
} else {
|
||||
linger_ms
|
||||
},
|
||||
flush,
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn parse_async_overflow(name : String) -> AsyncOverflowPolicy raise {
|
||||
match name.to_upper() {
|
||||
"BLOCKING" => AsyncOverflowPolicy::Blocking
|
||||
@@ -165,6 +204,7 @@ fn parse_async_overflow(name : String) -> AsyncOverflowPolicy raise {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn parse_async_flush(name : String) -> AsyncFlushPolicy raise {
|
||||
match name.to_upper() {
|
||||
"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 obj = match root.as_object() {
|
||||
Some(obj) => obj
|
||||
None => raise Failure::Failure("Expected object for async logger config")
|
||||
}
|
||||
let max_pending = match obj.get("max_pending") {
|
||||
Some(value) => match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None => raise Failure::Failure("Expected number at async_config.max_pending")
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise Failure::Failure("Expected number at async_config.max_pending")
|
||||
}
|
||||
None => 0
|
||||
}
|
||||
let overflow = match obj.get("overflow") {
|
||||
Some(value) => match value.as_string() {
|
||||
Some(text) => parse_async_overflow(text)
|
||||
None => raise Failure::Failure("Expected string at async_config.overflow")
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => parse_async_overflow(text)
|
||||
None =>
|
||||
raise Failure::Failure("Expected string at async_config.overflow")
|
||||
}
|
||||
None => AsyncOverflowPolicy::Blocking
|
||||
}
|
||||
let max_batch = match obj.get("max_batch") {
|
||||
Some(value) => match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None => raise Failure::Failure("Expected number at async_config.max_batch")
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise Failure::Failure("Expected number at async_config.max_batch")
|
||||
}
|
||||
None => 1
|
||||
}
|
||||
let linger_ms = match obj.get("linger_ms") {
|
||||
Some(value) => match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None => raise Failure::Failure("Expected number at async_config.linger_ms")
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise Failure::Failure("Expected number at async_config.linger_ms")
|
||||
}
|
||||
None => 0
|
||||
}
|
||||
let flush = match obj.get("flush") {
|
||||
Some(value) => match value.as_string() {
|
||||
Some(text) => parse_async_flush(text)
|
||||
None => raise Failure::Failure("Expected string at async_config.flush")
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => parse_async_flush(text)
|
||||
None => raise Failure::Failure("Expected string at async_config.flush")
|
||||
}
|
||||
None => AsyncFlushPolicy::Never
|
||||
}
|
||||
AsyncLoggerConfig::new(
|
||||
max_pending=max_pending,
|
||||
overflow=overflow,
|
||||
max_batch=max_batch,
|
||||
linger_ms=linger_ms,
|
||||
flush=flush,
|
||||
max_pending~,
|
||||
overflow~,
|
||||
max_batch~,
|
||||
linger_ms~,
|
||||
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({
|
||||
"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()),
|
||||
"linger_ms": @json_parser.JsonValue::Number(config.linger_ms.to_double()),
|
||||
"overflow": @json_parser.JsonValue::String(match config.overflow {
|
||||
AsyncOverflowPolicy::Blocking => "Blocking"
|
||||
AsyncOverflowPolicy::DropOldest => "DropOldest"
|
||||
AsyncOverflowPolicy::DropNewest => "DropNewest"
|
||||
}),
|
||||
"flush": @json_parser.JsonValue::String(match config.flush {
|
||||
AsyncFlushPolicy::Never => "Never"
|
||||
AsyncFlushPolicy::Batch => "Batch"
|
||||
AsyncFlushPolicy::Shutdown => "Shutdown"
|
||||
}),
|
||||
"overflow": @json_parser.JsonValue::String(
|
||||
match config.overflow {
|
||||
AsyncOverflowPolicy::Blocking => "Blocking"
|
||||
AsyncOverflowPolicy::DropOldest => "DropOldest"
|
||||
AsyncOverflowPolicy::DropNewest => "DropNewest"
|
||||
},
|
||||
),
|
||||
"flush": @json_parser.JsonValue::String(
|
||||
match config.flush {
|
||||
AsyncFlushPolicy::Never => "Never"
|
||||
AsyncFlushPolicy::Batch => "Batch"
|
||||
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)
|
||||
if pretty {
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
@@ -252,35 +317,45 @@ pub fn stringify_async_logger_config(config : AsyncLoggerConfig, pretty~ : Bool
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct AsyncLoggerBuildConfig {
|
||||
logger : @bitlogger.LoggerConfig
|
||||
async_config : AsyncLoggerConfig
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn AsyncLoggerBuildConfig::new(
|
||||
logger~ : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(),
|
||||
async_config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
|
||||
logger? : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(),
|
||||
async_config? : AsyncLoggerConfig = AsyncLoggerConfig::new(),
|
||||
) -> AsyncLoggerBuildConfig {
|
||||
{ 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 obj = match root.as_object() {
|
||||
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") {
|
||||
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()
|
||||
}
|
||||
let async_config = match obj.get("async_config") {
|
||||
Some(value) => parse_async_logger_config_text(@json_parser.stringify(value))
|
||||
None => AsyncLoggerConfig::new()
|
||||
}
|
||||
AsyncLoggerBuildConfig::new(logger=logger, async_config=async_config)
|
||||
AsyncLoggerBuildConfig::new(logger~, async_config~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn async_logger_build_config_to_json(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
) -> @json_parser.JsonValue {
|
||||
@@ -290,9 +365,10 @@ pub fn async_logger_build_config_to_json(
|
||||
})
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_async_logger_build_config(
|
||||
config : AsyncLoggerBuildConfig,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> String {
|
||||
let value = async_logger_build_config_to_json(config)
|
||||
if pretty {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src" @bitlogger,
|
||||
"maria/json_parser" @json_parser,
|
||||
"maria/json_parser",
|
||||
}
|
||||
|
||||
+1798
-576
File diff suppressed because it is too large
Load Diff
+269
-154
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
test "default logger can be reconfigured" {
|
||||
set_default_min_level(Level::Debug)
|
||||
set_default_target("global")
|
||||
@@ -6,6 +7,7 @@ test "default logger can be reconfigured" {
|
||||
inspect(logger.target, content="global")
|
||||
}
|
||||
|
||||
///|
|
||||
test "default logger snapshots shared defaults at creation time" {
|
||||
set_default_min_level(Level::Warn)
|
||||
set_default_target("before")
|
||||
@@ -24,24 +26,30 @@ test "default logger snapshots shared defaults at creation time" {
|
||||
inspect(after.target, content="after")
|
||||
}
|
||||
|
||||
///|
|
||||
test "logger can enable timestamps" {
|
||||
let logger = Logger::new(console_sink(), min_level=Level::Info, target="time")
|
||||
.with_timestamp()
|
||||
let logger = Logger::new(console_sink(), min_level=Level::Info, target="time").with_timestamp()
|
||||
inspect(logger.timestamp, content="true")
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter can customize visible parts" {
|
||||
let rec = record(
|
||||
Level::Info,
|
||||
"hello",
|
||||
timestamp_ms=123UL,
|
||||
target="svc.api",
|
||||
fields=[field("user", "alice"), field("request_id", "42")],
|
||||
let rec = record(Level::Info, "hello", timestamp_ms=123UL, target="svc.api", fields=[
|
||||
field("user", "alice"),
|
||||
field("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",
|
||||
)
|
||||
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" {
|
||||
let rec = record(
|
||||
Level::Warn,
|
||||
@@ -59,6 +67,7 @@ test "text formatter can emit message only" {
|
||||
inspect(format_text(rec, formatter=message_only), content="just message")
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter supports template rendering" {
|
||||
let rec = record(
|
||||
Level::Info,
|
||||
@@ -73,20 +82,22 @@ test "text formatter supports template rendering" {
|
||||
template="[{level}] {target} {message} :: {fields} @{timestamp}",
|
||||
)
|
||||
inspect(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
content="[INFO] svc.api template hello :: user=alice,request_id=42 @123",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter can render ansi level colors" {
|
||||
let rec = record(Level::Error, "boom", target="svc")
|
||||
let formatter = text_formatter(color_mode=ColorMode::Always)
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter auto color respects NO_COLOR" {
|
||||
let rec = record(Level::Warn, "boom", target="svc")
|
||||
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" {
|
||||
let rec = record(Level::Info, "<red>boom</>", target="svc")
|
||||
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" {
|
||||
let rec = record(Level::Info, "<red>boom</> <b>bold</>", target="svc")
|
||||
inspect(
|
||||
@@ -117,46 +130,83 @@ test "text formatter strips inline tags in plain mode" {
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter supports nested inline tags" {
|
||||
let rec = record(Level::Info, "<red><b>fatal</></>")
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter supports named closing tags" {
|
||||
let rec = record(Level::Info, "<red>boom</red>")
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter supports mixed short and named closing tags" {
|
||||
let rec = record(Level::Info, "<red><b>fatal</b></red>")
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter keeps unmatched named closing tags as plain text" {
|
||||
let rec = record(Level::Info, "boom</red>")
|
||||
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>",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter supports hex inline colors" {
|
||||
let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>")
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter can downgrade hex colors to basic ansi" {
|
||||
let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>")
|
||||
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" {
|
||||
let rec = record(Level::Info, "<unknown>boom</>")
|
||||
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</>",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter can disable style markup parsing" {
|
||||
let rec = record(Level::Info, "<red>boom</> <b>bold</>", target="svc")
|
||||
inspect(
|
||||
@@ -192,6 +247,7 @@ test "text formatter can disable style markup parsing" {
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter builtin style markup mode ignores custom tags" {
|
||||
let formatter = text_formatter(
|
||||
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</>")
|
||||
inspect(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
content="<brand>custom</> \u{001b}[31mbuiltin\u{001b}[0m",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
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::Full), content="full")
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter supports custom style tags" {
|
||||
let formatter = text_formatter(
|
||||
show_level=false,
|
||||
@@ -224,83 +285,103 @@ test "text formatter supports custom style tags" {
|
||||
)
|
||||
let rec = record(Level::Info, "<accent>api</>")
|
||||
inspect(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
content="\u{001b}[38;2;76;201;240;1mapi\u{001b}[0m",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter can override builtin style tags" {
|
||||
let formatter = text_formatter(
|
||||
show_level=false,
|
||||
show_target=false,
|
||||
color_mode=ColorMode::Always,
|
||||
).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</>")
|
||||
inspect(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
content="\u{001b}[38;2;255;90;95;4malert\u{001b}[0m",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "formatter style tags take priority over global style tags" {
|
||||
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(
|
||||
show_level=false,
|
||||
show_target=false,
|
||||
color_mode=ColorMode::Always,
|
||||
).with_style_tags(
|
||||
style_tag_registry().set_tag("accent", fg=Some("#abcdef")),
|
||||
)
|
||||
).with_style_tags(style_tag_registry().set_tag("accent", fg=Some("#abcdef")))
|
||||
let rec = record(Level::Info, "<accent>tag</>")
|
||||
inspect(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
content="\u{001b}[38;2;171;205;239mtag\u{001b}[0m",
|
||||
)
|
||||
set_global_style_tag_registry(previous)
|
||||
}
|
||||
|
||||
///|
|
||||
test "global style tags apply when formatter has no local 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</>")
|
||||
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",
|
||||
)
|
||||
set_global_style_tag_registry(previous)
|
||||
}
|
||||
|
||||
///|
|
||||
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()
|
||||
let rec = record(Level::Info, "<success>ok</>")
|
||||
inspect(
|
||||
format_text(
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "style tag alias can reuse builtin tags" {
|
||||
let formatter = text_formatter(
|
||||
show_level=false,
|
||||
show_target=false,
|
||||
color_mode=ColorMode::Always,
|
||||
).with_style_tags(
|
||||
style_tag_registry().define_alias("danger", "red"),
|
||||
)
|
||||
).with_style_tags(style_tag_registry().define_alias("danger", "red"))
|
||||
let rec = record(Level::Info, "<danger>boom</>")
|
||||
inspect(
|
||||
format_text(rec, formatter=formatter),
|
||||
content="\u{001b}[31mboom\u{001b}[0m",
|
||||
)
|
||||
inspect(format_text(rec, formatter~), content="\u{001b}[31mboom\u{001b}[0m")
|
||||
}
|
||||
|
||||
///|
|
||||
test "builtin semantic style tags are available" {
|
||||
let formatter = text_formatter(
|
||||
show_level=false,
|
||||
@@ -308,28 +389,37 @@ test "builtin semantic style tags are available" {
|
||||
color_mode=ColorMode::Always,
|
||||
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(
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "builtin semantic style tags can still be overridden" {
|
||||
let formatter = text_formatter(
|
||||
show_level=false,
|
||||
show_target=false,
|
||||
color_mode=ColorMode::Always,
|
||||
).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</>")
|
||||
inspect(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
content="\u{001b}[38;2;0;255;170;4mok\u{001b}[0m",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter can enable markup for target separately" {
|
||||
let formatter = text_formatter(
|
||||
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</>")
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter can enable markup for field values separately" {
|
||||
let formatter = text_formatter(
|
||||
show_level=false,
|
||||
@@ -350,13 +441,16 @@ test "text formatter can enable markup for field values separately" {
|
||||
color_mode=ColorMode::Always,
|
||||
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(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
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" {
|
||||
let formatter = text_formatter(
|
||||
show_level=false,
|
||||
@@ -364,13 +458,16 @@ test "text formatter leaves field keys raw when field markup is enabled" {
|
||||
color_mode=ColorMode::Always,
|
||||
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(
|
||||
format_text(rec, formatter=formatter),
|
||||
format_text(rec, formatter~),
|
||||
content="hello \u{001b}[35m<danger>status</>=ok\u{001b}[0m",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "text formatter template respects disabled fields" {
|
||||
let rec = record(Level::Warn, "just message", target="svc")
|
||||
let formatter = text_formatter(
|
||||
@@ -378,26 +475,30 @@ test "text formatter template respects disabled fields" {
|
||||
show_fields=false,
|
||||
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" {
|
||||
let rendered : Ref[String] = Ref("")
|
||||
let sink = text_callback_sink(
|
||||
text_formatter(show_timestamp=false, separator=" | "),
|
||||
fn(text) {
|
||||
rendered.val = text
|
||||
},
|
||||
fn(text) { rendered.val = text },
|
||||
)
|
||||
let logger = Logger::new(sink, min_level=Level::Info, target="svc")
|
||||
logger.info("hello", fields=[field("user", "alice")])
|
||||
inspect(rendered.val, content="[INFO] | [svc] | hello | user=alice")
|
||||
}
|
||||
|
||||
///|
|
||||
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" {
|
||||
let sink = file_sink("bitlogger-test.log")
|
||||
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.append == sink.append_mode(), 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.auto_flush_enabled(), content="true")
|
||||
inspect(sink.rotation_enabled(), content="false")
|
||||
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.flush_failures(), content="0")
|
||||
if sink.is_available() {
|
||||
@@ -424,8 +531,12 @@ test "file sink availability reflects backend support" {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
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() {
|
||||
inspect(sink.state().available, content="true")
|
||||
ignore(sink.close())
|
||||
@@ -449,6 +560,7 @@ test "file sink unavailable backend keeps stable fallback state" {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
test "file sink rotation config normalizes invalid inputs" {
|
||||
let rotation = file_rotation(0, max_backups=0)
|
||||
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" {
|
||||
let sink = file_sink("bitlogger-setters.log")
|
||||
let default_policy = sink.default_policy()
|
||||
@@ -490,7 +603,10 @@ test "file sink setters update auto flush and rotation state" {
|
||||
sink.clear_rotation()
|
||||
inspect(sink.rotation_enabled(), content="false")
|
||||
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")
|
||||
let state = sink.state()
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "file sink reset policy restores configured defaults" {
|
||||
let sink = file_sink(
|
||||
"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" {
|
||||
let sink = file_sink("bitlogger-set-policy.log")
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "file sink tracks rotation failures on unavailable backend" {
|
||||
let path = "bitlogger-rotate.log"
|
||||
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" {
|
||||
let sink = file_sink("bitlogger-reopen.log")
|
||||
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" {
|
||||
let path = "bitlogger-rotate-native.log"
|
||||
ignore(remove_file_internal(path))
|
||||
@@ -666,20 +787,18 @@ test "file sink can rotate on native backend" {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
test "json formatter keeps structured shape" {
|
||||
let rec = record(
|
||||
Level::Error,
|
||||
"failed",
|
||||
timestamp_ms=55UL,
|
||||
target="svc",
|
||||
fields=[field("code", "500")],
|
||||
)
|
||||
let rec = record(Level::Error, "failed", timestamp_ms=55UL, target="svc", fields=[
|
||||
field("code", "500"),
|
||||
])
|
||||
inspect(
|
||||
format_json(rec),
|
||||
content="{\"level\":\"ERROR\",\"message\":\"failed\",\"fields\":{\"code\":\"500\"},\"timestamp_ms\":\"55\",\"target\":\"svc\"}",
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
test "callback sink receives record" {
|
||||
let captured_target : Ref[String] = Ref("")
|
||||
let captured_message : Ref[String] = Ref("")
|
||||
@@ -696,20 +815,15 @@ test "callback sink receives record" {
|
||||
inspect(captured_message.val, content="hello")
|
||||
}
|
||||
|
||||
///|
|
||||
test "split sink routes records by predicate" {
|
||||
let left_messages : Ref[Array[String]] = Ref([])
|
||||
let right_messages : Ref[Array[String]] = Ref([])
|
||||
let logger = Logger::new(
|
||||
split_sink(
|
||||
callback_sink(fn(rec) {
|
||||
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) { left_messages.val.push(rec.message) }),
|
||||
callback_sink(fn(rec) { right_messages.val.push(rec.message) }),
|
||||
fn(rec) { rec.target == "audit" },
|
||||
),
|
||||
min_level=Level::Info,
|
||||
target="main",
|
||||
@@ -722,6 +836,7 @@ test "split sink routes records by predicate" {
|
||||
inspect(right_messages.val[0], content="drop to right")
|
||||
}
|
||||
|
||||
///|
|
||||
test "split_by_level routes warn and error separately" {
|
||||
let high_messages : Ref[Array[String]] = Ref([])
|
||||
let low_messages : Ref[Array[String]] = Ref([])
|
||||
@@ -748,21 +863,22 @@ test "split_by_level routes warn and error separately" {
|
||||
inspect(low_messages.val[0], content="INFO:info")
|
||||
}
|
||||
|
||||
///|
|
||||
test "callback sink sees child target and context logger shape" {
|
||||
let captured_target : Ref[String] = Ref("")
|
||||
let captured_message : Ref[String] = Ref("")
|
||||
let captured_field_count : Ref[Int] = Ref(0)
|
||||
let captured_timestamp : Ref[UInt64] = Ref(0UL)
|
||||
let logger = Logger::new(
|
||||
callback_sink(fn(rec) {
|
||||
captured_target.val = rec.target
|
||||
captured_message.val = rec.message
|
||||
captured_field_count.val = rec.fields.length()
|
||||
captured_timestamp.val = rec.timestamp_ms
|
||||
}),
|
||||
min_level=Level::Info,
|
||||
target="app",
|
||||
)
|
||||
callback_sink(fn(rec) {
|
||||
captured_target.val = rec.target
|
||||
captured_message.val = rec.message
|
||||
captured_field_count.val = rec.fields.length()
|
||||
captured_timestamp.val = rec.timestamp_ms
|
||||
}),
|
||||
min_level=Level::Info,
|
||||
target="app",
|
||||
)
|
||||
.child("worker")
|
||||
.with_context_fields([field("service", "bitlogger")])
|
||||
.with_timestamp()
|
||||
@@ -773,6 +889,7 @@ test "callback sink sees child target and context logger shape" {
|
||||
inspect(captured_timestamp.val > 0UL, content="true")
|
||||
}
|
||||
|
||||
///|
|
||||
test "bind aliases context fields ergonomically" {
|
||||
let captured_target : Ref[String] = Ref("")
|
||||
let captured_message : Ref[String] = Ref("")
|
||||
@@ -798,12 +915,11 @@ test "bind aliases context fields ergonomically" {
|
||||
inspect(captured_fields.val[2].value, content="test")
|
||||
}
|
||||
|
||||
///|
|
||||
test "buffered sink flushes manually" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let sink = buffered_sink(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
flush_limit=10,
|
||||
)
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "buffered sink flushes automatically at limit" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let sink = buffered_sink(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
flush_limit=2,
|
||||
)
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "filter sink only forwards matching records" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let sink = filter_sink(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
fn(rec) {
|
||||
rec.target == "kept"
|
||||
},
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
fn(rec) { rec.target == "kept" },
|
||||
)
|
||||
let kept = Logger::new(sink, min_level=Level::Info, target="kept")
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "logger with_filter composes naturally" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let logger = Logger::new(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
min_level=Level::Info,
|
||||
target="app",
|
||||
)
|
||||
.with_filter(fn(rec) {
|
||||
rec.target == "app.worker"
|
||||
})
|
||||
).with_filter(fn(rec) { rec.target == "app.worker" })
|
||||
logger.info("drop at app")
|
||||
logger.child("worker").info("keep at worker")
|
||||
inspect(flushed_messages.val.length(), content="1")
|
||||
inspect(flushed_messages.val[0], content="keep at worker")
|
||||
}
|
||||
|
||||
///|
|
||||
test "filter helpers support target level and message composition" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let logger = Logger::new(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
min_level=Level::Trace,
|
||||
target="service",
|
||||
).with_filter(all_of([
|
||||
target_has_prefix("service"),
|
||||
level_at_least(Level::Info),
|
||||
message_contains("visible"),
|
||||
]))
|
||||
).with_filter(
|
||||
all_of([
|
||||
target_has_prefix("service"),
|
||||
level_at_least(Level::Info),
|
||||
message_contains("visible"),
|
||||
]),
|
||||
)
|
||||
logger.debug("visible debug")
|
||||
logger.info("hidden 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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "field helpers can match and negate records" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let logger = Logger::new(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
min_level=Level::Info,
|
||||
target="fields",
|
||||
).with_filter(all_of([
|
||||
has_field("request_id"),
|
||||
field_equals("kind", "audit"),
|
||||
not_(target_is("fields.drop")),
|
||||
]))
|
||||
).with_filter(
|
||||
all_of([
|
||||
has_field("request_id"),
|
||||
field_equals("kind", "audit"),
|
||||
not_(target_is("fields.drop")),
|
||||
]),
|
||||
)
|
||||
logger.info("missing field")
|
||||
logger.info("wrong kind", fields=[field("request_id", "1"), field("kind", "trace")])
|
||||
logger.child("drop").info("blocked target", fields=[field("request_id", "2"), field("kind", "audit")])
|
||||
logger.info("wrong kind", fields=[
|
||||
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")])
|
||||
inspect(flushed_messages.val.length(), content="1")
|
||||
inspect(flushed_messages.val[0], content="kept")
|
||||
}
|
||||
|
||||
///|
|
||||
test "any_of helper accepts multiple predicates" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let logger = Logger::new(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
min_level=Level::Info,
|
||||
target="multi",
|
||||
).with_filter(any_of([
|
||||
target_is("multi.keep"),
|
||||
field_equals("force", "true"),
|
||||
]))
|
||||
).with_filter(
|
||||
any_of([target_is("multi.keep"), field_equals("force", "true")]),
|
||||
)
|
||||
logger.info("drop")
|
||||
logger.child("keep").info("keep by target")
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "patch sink can rewrite message target and fields" {
|
||||
let captured_target : 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,
|
||||
target="auth",
|
||||
).with_patch(compose_patches([
|
||||
set_target("audit.auth"),
|
||||
prefix_message("[safe] "),
|
||||
redact_field("token"),
|
||||
append_fields([field("service", "bitlogger")]),
|
||||
]))
|
||||
).with_patch(
|
||||
compose_patches([
|
||||
set_target("audit.auth"),
|
||||
prefix_message("[safe] "),
|
||||
redact_field("token"),
|
||||
append_fields([field("service", "bitlogger")]),
|
||||
]),
|
||||
)
|
||||
logger.info("login", fields=[field("token", "secret"), field("user", "alice")])
|
||||
inspect(captured_target.val, content="audit.auth")
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "patch helpers can redact multiple fields" {
|
||||
let captured_fields : Ref[Array[Field]] = Ref([])
|
||||
let logger = Logger::new(
|
||||
callback_sink(fn(rec) {
|
||||
captured_fields.val = rec.fields
|
||||
}),
|
||||
callback_sink(fn(rec) { captured_fields.val = rec.fields }),
|
||||
min_level=Level::Info,
|
||||
target="audit",
|
||||
).with_patch(redact_fields(["token", "password"], placeholder="[redacted]"))
|
||||
logger.info(
|
||||
"credentials",
|
||||
fields=[field("token", "abc"), field("password", "123"), field("user", "alice")],
|
||||
)
|
||||
logger.info("credentials", fields=[
|
||||
field("token", "abc"),
|
||||
field("password", "123"),
|
||||
field("user", "alice"),
|
||||
])
|
||||
inspect(captured_fields.val.length(), content="3")
|
||||
inspect(captured_fields.val[0].value, content="[redacted]")
|
||||
inspect(captured_fields.val[1].value, content="[redacted]")
|
||||
inspect(captured_fields.val[2].value, content="alice")
|
||||
}
|
||||
|
||||
///|
|
||||
test "queued sink drains in order" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let sink = queued_sink(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
)
|
||||
let logger = Logger::new(sink, min_level=Level::Info, target="queue")
|
||||
logger.info("one")
|
||||
@@ -1007,12 +1125,11 @@ test "queued sink drains in order" {
|
||||
inspect(flushed_messages.val[2], content="three")
|
||||
}
|
||||
|
||||
///|
|
||||
test "queued sink can drop newest when full" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let sink = queued_sink(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
max_pending=2,
|
||||
overflow=QueueOverflowPolicy::DropNewest,
|
||||
)
|
||||
@@ -1028,12 +1145,11 @@ test "queued sink can drop newest when full" {
|
||||
inspect(flushed_messages.val[1], content="two")
|
||||
}
|
||||
|
||||
///|
|
||||
test "queued sink can drop oldest when full" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let sink = queued_sink(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
max_pending=2,
|
||||
overflow=QueueOverflowPolicy::DropOldest,
|
||||
)
|
||||
@@ -1049,15 +1165,14 @@ test "queued sink can drop oldest when full" {
|
||||
inspect(flushed_messages.val[1], content="three")
|
||||
}
|
||||
|
||||
///|
|
||||
test "logger with_queue preserves chaining ergonomics" {
|
||||
let flushed_messages : Ref[Array[String]] = Ref([])
|
||||
let logger = Logger::new(
|
||||
callback_sink(fn(rec) {
|
||||
flushed_messages.val.push(rec.message)
|
||||
}),
|
||||
min_level=Level::Info,
|
||||
target="service",
|
||||
)
|
||||
callback_sink(fn(rec) { flushed_messages.val.push(rec.message) }),
|
||||
min_level=Level::Info,
|
||||
target="service",
|
||||
)
|
||||
.with_patch(prefix_message("[queued] "))
|
||||
.with_queue(max_pending=2, overflow=QueueOverflowPolicy::DropOldest)
|
||||
logger.info("one")
|
||||
|
||||
+8
-2
@@ -19,13 +19,17 @@ BitLogger 是一个使用 MoonBit 编写的结构化日志库.
|
||||
|
||||
## Example / 示例
|
||||
|
||||
```moonbit
|
||||
```moonbit nocheck
|
||||
///|
|
||||
test {
|
||||
let logger = build_logger(
|
||||
text_console(
|
||||
min_level=Level::Debug,
|
||||
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")])
|
||||
@@ -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` 是 portable-first 示例集
|
||||
- `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` 入口能力
|
||||
- `async_basic` 当前依赖 `moonbitlang/async`,其上游 README 目前只明确承诺 Linux/MacOS 的 native/LLVM 支持;在上游明确支持之前,不要把 Windows native 当作这个示例的支持基线
|
||||
- package-level API docs / 单接口 API 文档:
|
||||
- `../docs/api/`
|
||||
- common entry points / 常用入口:
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
///|
|
||||
pub type ApplicationLogger = ConfiguredLogger
|
||||
|
||||
///|
|
||||
pub fn build_application_logger(config : LoggerConfig) -> ApplicationLogger {
|
||||
build_logger(config)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn parse_and_build_application_logger(
|
||||
input : String,
|
||||
) -> ApplicationLogger raise ConfigError {
|
||||
|
||||
+45
-11
@@ -1,66 +1,100 @@
|
||||
///|
|
||||
pub type ConfigError = @utils.ConfigError
|
||||
|
||||
///|
|
||||
pub type SinkKind = @utils.SinkKind
|
||||
|
||||
///|
|
||||
pub type TextFormatterConfig = @utils.TextFormatterConfig
|
||||
|
||||
///|
|
||||
pub type QueueConfig = @utils.QueueConfig
|
||||
|
||||
///|
|
||||
pub type SinkConfig = @utils.SinkConfig
|
||||
|
||||
///|
|
||||
pub type LoggerConfig = @utils.LoggerConfig
|
||||
|
||||
///|
|
||||
pub fn default_text_formatter_config() -> TextFormatterConfig {
|
||||
@utils.default_text_formatter_config()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_sink_config() -> SinkConfig {
|
||||
@utils.default_sink_config()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_logger_config() -> LoggerConfig {
|
||||
@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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
|
||||
@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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_text_formatter_config(
|
||||
config : TextFormatterConfig,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
|
||||
@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 {
|
||||
@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~)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
pub(all) enum Level {
|
||||
Trace
|
||||
Debug
|
||||
@@ -6,6 +7,7 @@ pub(all) enum Level {
|
||||
Error
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Level::priority(self : Level) -> Int {
|
||||
match self {
|
||||
Level::Trace => 10
|
||||
@@ -16,6 +18,7 @@ pub fn Level::priority(self : Level) -> Int {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Level::label(self : Level) -> String {
|
||||
match self {
|
||||
Level::Trace => "TRACE"
|
||||
@@ -26,6 +29,7 @@ pub fn Level::label(self : Level) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Level::enabled(self : Level, min_level : Level) -> Bool {
|
||||
self.priority() >= min_level.priority()
|
||||
}
|
||||
|
||||
+16
-8
@@ -1,18 +1,20 @@
|
||||
///|
|
||||
pub struct Field {
|
||||
key : String
|
||||
value : String
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn field(key : String, value : String) -> Field {
|
||||
{ key, value }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn fields(entries : Array[(String, String)]) -> Array[Field] {
|
||||
entries.map(fn(entry) {
|
||||
field(entry.0, entry.1)
|
||||
})
|
||||
entries.map(fn(entry) { field(entry.0, entry.1) })
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct Record {
|
||||
level : Level
|
||||
timestamp_ms : UInt64
|
||||
@@ -21,30 +23,34 @@ pub struct Record {
|
||||
fields : Array[Field]
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Record::new(
|
||||
level : Level,
|
||||
message : String,
|
||||
timestamp_ms~ : UInt64 = 0UL,
|
||||
target~ : String = "",
|
||||
fields~ : Array[Field] = [],
|
||||
timestamp_ms? : UInt64 = 0UL,
|
||||
target? : String = "",
|
||||
fields? : Array[Field] = [],
|
||||
) -> Record {
|
||||
{ level, timestamp_ms, target, message, fields }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Field::with_value(self : Field, value : String) -> Field {
|
||||
field(self.key, value)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Record::with_target(self : Record, target : String) -> Record {
|
||||
Record::new(
|
||||
self.level,
|
||||
self.message,
|
||||
timestamp_ms=self.timestamp_ms,
|
||||
target=target,
|
||||
target~,
|
||||
fields=self.fields,
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Record::with_message(self : Record, message : String) -> Record {
|
||||
Record::new(
|
||||
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 {
|
||||
Record::new(
|
||||
self.level,
|
||||
self.message,
|
||||
timestamp_ms=self.timestamp_ms,
|
||||
target=self.target,
|
||||
fields=fields,
|
||||
fields~,
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn Record::copy(self : Record) -> Record {
|
||||
Record::new(
|
||||
self.level,
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
///|
|
||||
type FileHandle = @utils.FileHandle
|
||||
|
||||
///|
|
||||
fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
||||
@utils.open_file_handle_internal(path, append)
|
||||
}
|
||||
|
||||
///|
|
||||
fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool {
|
||||
@utils.write_file_handle_internal(handle, content)
|
||||
}
|
||||
|
||||
///|
|
||||
fn flush_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
@utils.flush_file_handle_internal(handle)
|
||||
}
|
||||
|
||||
///|
|
||||
fn close_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
@utils.close_file_handle_internal(handle)
|
||||
}
|
||||
|
||||
///|
|
||||
fn file_size_internal(handle : FileHandle) -> Int {
|
||||
@utils.file_size_internal(handle)
|
||||
}
|
||||
|
||||
///|
|
||||
fn rename_file_internal(from_path : String, to_path : String) -> Bool {
|
||||
@utils.rename_file_internal(from_path, to_path)
|
||||
}
|
||||
|
||||
///|
|
||||
fn remove_file_internal(path : String) -> Bool {
|
||||
@utils.remove_file_internal(path)
|
||||
}
|
||||
|
||||
///|
|
||||
fn string_byte_length_internal(content : String) -> Int {
|
||||
@utils.string_byte_length_internal(content)
|
||||
}
|
||||
|
||||
///|
|
||||
fn native_files_supported_internal() -> Bool {
|
||||
@utils.native_files_supported_internal()
|
||||
}
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
///|
|
||||
type FileHandle = @utils.FileHandle
|
||||
|
||||
///|
|
||||
fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
||||
@utils.open_file_handle_internal(path, append)
|
||||
}
|
||||
|
||||
///|
|
||||
fn write_file_handle_internal(handle : FileHandle, content : String) -> Bool {
|
||||
@utils.write_file_handle_internal(handle, content)
|
||||
}
|
||||
|
||||
///|
|
||||
fn flush_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
@utils.flush_file_handle_internal(handle)
|
||||
}
|
||||
|
||||
///|
|
||||
fn close_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
@utils.close_file_handle_internal(handle)
|
||||
}
|
||||
|
||||
///|
|
||||
fn file_size_internal(handle : FileHandle) -> Int {
|
||||
@utils.file_size_internal(handle)
|
||||
}
|
||||
|
||||
///|
|
||||
fn rename_file_internal(from_path : String, to_path : String) -> Bool {
|
||||
@utils.rename_file_internal(from_path, to_path)
|
||||
}
|
||||
|
||||
///|
|
||||
fn remove_file_internal(path : String) -> Bool {
|
||||
@utils.remove_file_internal(path)
|
||||
}
|
||||
|
||||
///|
|
||||
fn string_byte_length_internal(content : String) -> Int {
|
||||
@utils.string_byte_length_internal(content)
|
||||
}
|
||||
|
||||
///|
|
||||
fn native_files_supported_internal() -> Bool {
|
||||
@utils.native_files_supported_internal()
|
||||
}
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
///|
|
||||
pub type RecordPredicate = @utils.RecordPredicate
|
||||
|
||||
///|
|
||||
pub fn level_at_least(min_level : Level) -> RecordPredicate {
|
||||
@utils.level_at_least(min_level)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn target_is(target : String) -> RecordPredicate {
|
||||
@utils.target_is(target)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn target_has_prefix(prefix : String) -> RecordPredicate {
|
||||
@utils.target_has_prefix(prefix)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn message_contains(fragment : String) -> RecordPredicate {
|
||||
@utils.message_contains(fragment)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn has_field(key : String) -> RecordPredicate {
|
||||
@utils.has_field(key)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn field_equals(key : String, value : String) -> RecordPredicate {
|
||||
@utils.field_equals(key, value)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn not_(predicate : RecordPredicate) -> RecordPredicate {
|
||||
@utils.not_(predicate)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
|
||||
@utils.all_of(predicates)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
|
||||
@utils.any_of(predicates)
|
||||
}
|
||||
|
||||
+57
-42
@@ -1,103 +1,118 @@
|
||||
///|
|
||||
pub type RecordFormatter = @utils.RecordFormatter
|
||||
|
||||
///|
|
||||
pub type ColorMode = @utils.ColorMode
|
||||
|
||||
///|
|
||||
pub type ColorSupport = @utils.ColorSupport
|
||||
|
||||
///|
|
||||
pub type StyleMarkupMode = @utils.StyleMarkupMode
|
||||
|
||||
///|
|
||||
pub type TextStyle = @utils.TextStyle
|
||||
|
||||
///|
|
||||
pub fn text_style(
|
||||
fg~ : String? = None,
|
||||
bg~ : String? = None,
|
||||
bold~ : Bool = false,
|
||||
dim~ : Bool = false,
|
||||
italic~ : Bool = false,
|
||||
underline~ : Bool = false,
|
||||
fg? : String? = None,
|
||||
bg? : String? = None,
|
||||
bold? : Bool = false,
|
||||
dim? : Bool = false,
|
||||
italic? : Bool = false,
|
||||
underline? : Bool = false,
|
||||
) -> TextStyle {
|
||||
@utils.text_style(
|
||||
fg=fg,
|
||||
bg=bg,
|
||||
bold=bold,
|
||||
dim=dim,
|
||||
italic=italic,
|
||||
underline=underline,
|
||||
)
|
||||
@utils.text_style(fg~, bg~, bold~, dim~, italic~, underline~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub type StyleTagRegistry = @utils.StyleTagRegistry
|
||||
|
||||
///|
|
||||
pub fn style_tag_registry() -> StyleTagRegistry {
|
||||
@utils.style_tag_registry()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_style_tag_registry() -> StyleTagRegistry {
|
||||
@utils.default_style_tag_registry()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn global_style_tag_registry() -> StyleTagRegistry {
|
||||
@utils.global_style_tag_registry()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn set_global_style_tag_registry(registry : StyleTagRegistry) -> Unit {
|
||||
@utils.set_global_style_tag_registry(registry)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn reset_global_style_tag_registry() -> Unit {
|
||||
@utils.reset_global_style_tag_registry()
|
||||
}
|
||||
|
||||
///|
|
||||
pub type TextFormatter = @utils.TextFormatter
|
||||
|
||||
///|
|
||||
pub fn text_formatter(
|
||||
show_timestamp~ : Bool = true,
|
||||
show_level~ : Bool = true,
|
||||
show_target~ : Bool = true,
|
||||
show_fields~ : Bool = true,
|
||||
separator~ : String = " ",
|
||||
field_separator~ : String = " ",
|
||||
template~ : String = "",
|
||||
color_mode~ : ColorMode = ColorMode::Never,
|
||||
color_support~ : ColorSupport = ColorSupport::TrueColor,
|
||||
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
||||
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
style_tags~ : StyleTagRegistry? = None,
|
||||
show_timestamp? : Bool = true,
|
||||
show_level? : Bool = true,
|
||||
show_target? : Bool = true,
|
||||
show_fields? : Bool = true,
|
||||
separator? : String = " ",
|
||||
field_separator? : String = " ",
|
||||
template? : String = "",
|
||||
color_mode? : ColorMode = ColorMode::Never,
|
||||
color_support? : ColorSupport = ColorSupport::TrueColor,
|
||||
style_markup? : StyleMarkupMode = StyleMarkupMode::Full,
|
||||
target_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
fields_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
style_tags? : StyleTagRegistry? = None,
|
||||
) -> TextFormatter {
|
||||
@utils.text_formatter(
|
||||
show_timestamp=show_timestamp,
|
||||
show_level=show_level,
|
||||
show_target=show_target,
|
||||
show_fields=show_fields,
|
||||
separator=separator,
|
||||
field_separator=field_separator,
|
||||
template=template,
|
||||
color_mode=color_mode,
|
||||
color_support=color_support,
|
||||
style_markup=style_markup,
|
||||
target_style_markup=target_style_markup,
|
||||
fields_style_markup=fields_style_markup,
|
||||
style_tags=style_tags,
|
||||
show_timestamp~,
|
||||
show_level~,
|
||||
show_target~,
|
||||
show_fields~,
|
||||
separator~,
|
||||
field_separator~,
|
||||
template~,
|
||||
color_mode~,
|
||||
color_support~,
|
||||
style_markup~,
|
||||
target_style_markup~,
|
||||
fields_style_markup~,
|
||||
style_tags~,
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn color_support_label(support : ColorSupport) -> String {
|
||||
@utils.color_support_label(support)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String {
|
||||
@utils.style_markup_mode_label(mode)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn color_mode_label(mode : ColorMode) -> String {
|
||||
@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 {
|
||||
@utils.format_json(rec)
|
||||
}
|
||||
|
||||
+13
-1
@@ -1,15 +1,27 @@
|
||||
///|
|
||||
let default_console_sink : ConsoleSink = console_sink()
|
||||
|
||||
///|
|
||||
let default_min_level_ref : Ref[Level] = Ref(Level::Info)
|
||||
|
||||
///|
|
||||
let default_target_ref : Ref[String] = Ref("")
|
||||
|
||||
///|
|
||||
pub fn set_default_min_level(level : Level) -> Unit {
|
||||
default_min_level_ref.val = level
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn set_default_target(target : String) -> Unit {
|
||||
default_target_ref.val = target
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_logger() -> Logger[ConsoleSink] {
|
||||
Logger::new(default_console_sink, min_level=default_min_level_ref.val, target=default_target_ref.val)
|
||||
Logger::new(
|
||||
default_console_sink,
|
||||
min_level=default_min_level_ref.val,
|
||||
target=default_target_ref.val,
|
||||
)
|
||||
}
|
||||
|
||||
+22
-12
@@ -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 +1,2 @@
|
||||
///|
|
||||
pub type Level = @core.Level
|
||||
|
||||
+47
-16
@@ -1,49 +1,70 @@
|
||||
///|
|
||||
pub struct LibraryLogger[S] {
|
||||
inner : Logger[S]
|
||||
}
|
||||
|
||||
///|
|
||||
fn[S] library_logger(logger : Logger[S]) -> LibraryLogger[S] {
|
||||
{ inner: logger }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] Logger::to_library_logger(self : Logger[S]) -> LibraryLogger[S] {
|
||||
library_logger(self)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryLogger::new(
|
||||
sink : S,
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
min_level? : Level = Level::Info,
|
||||
target? : String = "",
|
||||
) -> 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] {
|
||||
self.inner
|
||||
}
|
||||
|
||||
fn configured_library_logger(logger : ConfiguredLogger) -> LibraryLogger[RuntimeSink] {
|
||||
///|
|
||||
fn configured_library_logger(
|
||||
logger : ConfiguredLogger,
|
||||
) -> LibraryLogger[RuntimeSink] {
|
||||
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))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn parse_and_build_library_logger(
|
||||
input : String,
|
||||
) -> LibraryLogger[RuntimeSink] raise ConfigError {
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryLogger::with_context_fields(
|
||||
self : LibraryLogger[S],
|
||||
fields : Array[Field],
|
||||
@@ -51,6 +72,7 @@ pub fn[S] LibraryLogger::with_context_fields(
|
||||
library_logger(self.inner.with_context_fields(fields))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] LibraryLogger::bind(
|
||||
self : LibraryLogger[S],
|
||||
fields : Array[Field],
|
||||
@@ -58,44 +80,53 @@ pub fn[S] LibraryLogger::bind(
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S : Sink] LibraryLogger::log(
|
||||
self : LibraryLogger[S],
|
||||
level : Level,
|
||||
message : String,
|
||||
fields~ : Array[Field] = [],
|
||||
fields? : Array[Field] = [],
|
||||
target? : String = "",
|
||||
) -> Unit {
|
||||
self.inner.log(level, message, fields=fields, target=target)
|
||||
self.inner.log(level, message, fields~, target~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S : Sink] LibraryLogger::info(
|
||||
self : LibraryLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[Field] = [],
|
||||
fields? : Array[Field] = [],
|
||||
) -> Unit {
|
||||
self.inner.info(message, fields=fields)
|
||||
self.inner.info(message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S : Sink] LibraryLogger::warn(
|
||||
self : LibraryLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[Field] = [],
|
||||
fields? : Array[Field] = [],
|
||||
) -> Unit {
|
||||
self.inner.warn(message, fields=fields)
|
||||
self.inner.warn(message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S : Sink] LibraryLogger::error(
|
||||
self : LibraryLogger[S],
|
||||
message : String,
|
||||
fields~ : Array[Field] = [],
|
||||
fields? : Array[Field] = [],
|
||||
) -> Unit {
|
||||
self.inner.error(message, fields=fields)
|
||||
self.inner.error(message, fields~)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_library_logger() -> LibraryLogger[ConsoleSink] {
|
||||
library_logger(default_logger())
|
||||
}
|
||||
|
||||
+47
-12
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
pub struct Logger[S] {
|
||||
min_level : Level
|
||||
sink : S
|
||||
@@ -5,14 +6,21 @@ pub struct Logger[S] {
|
||||
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 }
|
||||
}
|
||||
|
||||
///|
|
||||
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 {
|
||||
if parent == "" {
|
||||
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] {
|
||||
{ ..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,
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
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(
|
||||
self : Logger[S],
|
||||
max_pending~ : Int = 0,
|
||||
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
max_pending? : Int = 0,
|
||||
overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
) -> Logger[QueuedSink[S]] {
|
||||
{
|
||||
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,
|
||||
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 }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] Logger::is_enabled(self : Logger[S], level : Level) -> Bool {
|
||||
level.enabled(self.min_level)
|
||||
}
|
||||
|
||||
+38
-12
@@ -1,8 +1,9 @@
|
||||
///|
|
||||
pub fn[S : Sink] Logger::log(
|
||||
self : Logger[S],
|
||||
level : Level,
|
||||
message : String,
|
||||
fields~ : Array[Field] = [],
|
||||
fields? : Array[Field] = [],
|
||||
target? : String = "",
|
||||
) -> Unit {
|
||||
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 timestamp_ms = if self.timestamp { @env.now() } else { 0UL }
|
||||
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
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src/core" @core,
|
||||
"Nanaloveyuki/BitLogger/src/utils" @utils,
|
||||
"maria/json_parser" @json_parser,
|
||||
"Nanaloveyuki/BitLogger/src/core",
|
||||
"Nanaloveyuki/BitLogger/src/utils",
|
||||
"maria/json_parser",
|
||||
"moonbitlang/core/array",
|
||||
"moonbitlang/core/builtin",
|
||||
"moonbitlang/core/env" @env,
|
||||
"moonbitlang/core/env",
|
||||
"moonbitlang/core/json",
|
||||
"moonbitlang/core/queue" @queue,
|
||||
"moonbitlang/core/queue",
|
||||
"moonbitlang/core/ref",
|
||||
}
|
||||
|
||||
|
||||
+15
-4
@@ -1,29 +1,40 @@
|
||||
///|
|
||||
pub type RecordPatch = @utils.RecordPatch
|
||||
|
||||
///|
|
||||
pub fn identity_patch() -> RecordPatch {
|
||||
@utils.identity_patch()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn set_target(target : String) -> RecordPatch {
|
||||
@utils.set_target(target)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn prefix_message(prefix : String) -> RecordPatch {
|
||||
@utils.prefix_message(prefix)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn append_fields(extra_fields : Array[Field]) -> RecordPatch {
|
||||
@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 {
|
||||
@utils.compose_patches(patches)
|
||||
}
|
||||
|
||||
+46
-40
@@ -1,89 +1,95 @@
|
||||
///|
|
||||
pub fn console(
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
min_level? : Level = Level::Info,
|
||||
target? : String = "",
|
||||
timestamp? : Bool = false,
|
||||
) -> LoggerConfig {
|
||||
LoggerConfig::new(
|
||||
min_level=min_level,
|
||||
target=target,
|
||||
timestamp=timestamp,
|
||||
min_level~,
|
||||
target~,
|
||||
timestamp~,
|
||||
sink=SinkConfig::new(kind=SinkKind::Console),
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn json_console(
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
min_level? : Level = Level::Info,
|
||||
target? : String = "",
|
||||
timestamp? : Bool = false,
|
||||
) -> LoggerConfig {
|
||||
LoggerConfig::new(
|
||||
min_level=min_level,
|
||||
target=target,
|
||||
timestamp=timestamp,
|
||||
min_level~,
|
||||
target~,
|
||||
timestamp~,
|
||||
sink=SinkConfig::new(kind=SinkKind::JsonConsole),
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn text_console(
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
|
||||
min_level? : Level = Level::Info,
|
||||
target? : String = "",
|
||||
timestamp? : Bool = false,
|
||||
text_formatter? : TextFormatterConfig = default_text_formatter_config(),
|
||||
) -> LoggerConfig {
|
||||
LoggerConfig::new(
|
||||
min_level=min_level,
|
||||
target=target,
|
||||
timestamp=timestamp,
|
||||
sink=SinkConfig::new(kind=SinkKind::TextConsole, text_formatter=text_formatter),
|
||||
min_level~,
|
||||
target~,
|
||||
timestamp~,
|
||||
sink=SinkConfig::new(kind=SinkKind::TextConsole, text_formatter~),
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn file(
|
||||
path : String,
|
||||
min_level~ : Level = Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
append~ : Bool = true,
|
||||
auto_flush~ : Bool = true,
|
||||
rotation~ : FileRotation? = None,
|
||||
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
|
||||
min_level? : Level = Level::Info,
|
||||
target? : String = "",
|
||||
timestamp? : Bool = false,
|
||||
append? : Bool = true,
|
||||
auto_flush? : Bool = true,
|
||||
rotation? : FileRotation? = None,
|
||||
text_formatter? : TextFormatterConfig = default_text_formatter_config(),
|
||||
) -> LoggerConfig raise ConfigError {
|
||||
if path == "" {
|
||||
raise ConfigError::InvalidConfig("File sink requires non-empty path")
|
||||
}
|
||||
LoggerConfig::new(
|
||||
min_level=min_level,
|
||||
target=target,
|
||||
timestamp=timestamp,
|
||||
min_level~,
|
||||
target~,
|
||||
timestamp~,
|
||||
sink=SinkConfig::new(
|
||||
kind=SinkKind::File,
|
||||
path=path,
|
||||
append=append,
|
||||
auto_flush=auto_flush,
|
||||
rotation=rotation,
|
||||
text_formatter=text_formatter,
|
||||
path~,
|
||||
append~,
|
||||
auto_flush~,
|
||||
rotation~,
|
||||
text_formatter~,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn with_queue(
|
||||
config : LoggerConfig,
|
||||
max_pending~ : Int = 0,
|
||||
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
max_pending? : Int = 0,
|
||||
overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
) -> LoggerConfig {
|
||||
LoggerConfig::new(
|
||||
min_level=config.min_level,
|
||||
target=config.target,
|
||||
timestamp=config.timestamp,
|
||||
sink=config.sink,
|
||||
queue=Some(QueueConfig::new(max_pending, overflow=overflow)),
|
||||
queue=Some(QueueConfig::new(max_pending, overflow~)),
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn with_file_rotation(
|
||||
config : LoggerConfig,
|
||||
max_bytes : Int,
|
||||
max_backups~ : Int = 1,
|
||||
max_backups? : Int = 1,
|
||||
) -> LoggerConfig {
|
||||
match config.sink.kind {
|
||||
SinkKind::File =>
|
||||
@@ -96,7 +102,7 @@ pub fn with_file_rotation(
|
||||
path=config.sink.path,
|
||||
append=config.sink.append,
|
||||
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,
|
||||
),
|
||||
queue=config.queue,
|
||||
|
||||
+97
-45
@@ -1,39 +1,56 @@
|
||||
///|
|
||||
test "logger config presets use expected defaults" {
|
||||
let console_config = console()
|
||||
inspect(console_config.min_level.label(), content="INFO")
|
||||
inspect(console_config.target, content="")
|
||||
inspect(console_config.timestamp, content="false")
|
||||
inspect(match console_config.sink.kind {
|
||||
SinkKind::Console => "Console"
|
||||
_ => "other"
|
||||
}, content="Console")
|
||||
inspect(
|
||||
match console_config.sink.kind {
|
||||
SinkKind::Console => "Console"
|
||||
_ => "other"
|
||||
},
|
||||
content="Console",
|
||||
)
|
||||
inspect(console_config.queue is None, content="true")
|
||||
|
||||
let json_config = json_console()
|
||||
inspect(match json_config.sink.kind {
|
||||
SinkKind::JsonConsole => "JsonConsole"
|
||||
_ => "other"
|
||||
}, content="JsonConsole")
|
||||
inspect(
|
||||
match json_config.sink.kind {
|
||||
SinkKind::JsonConsole => "JsonConsole"
|
||||
_ => "other"
|
||||
},
|
||||
content="JsonConsole",
|
||||
)
|
||||
inspect(json_config.queue is None, content="true")
|
||||
|
||||
let text_config = text_console()
|
||||
inspect(match text_config.sink.kind {
|
||||
SinkKind::TextConsole => "TextConsole"
|
||||
_ => "other"
|
||||
}, content="TextConsole")
|
||||
inspect(
|
||||
match text_config.sink.kind {
|
||||
SinkKind::TextConsole => "TextConsole"
|
||||
_ => "other"
|
||||
},
|
||||
content="TextConsole",
|
||||
)
|
||||
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" {
|
||||
let config = file("bitlogger.log")
|
||||
inspect(config.min_level.label(), content="INFO")
|
||||
inspect(config.target, content="")
|
||||
inspect(config.timestamp, content="false")
|
||||
inspect(match config.sink.kind {
|
||||
SinkKind::File => "File"
|
||||
_ => "other"
|
||||
}, content="File")
|
||||
inspect(
|
||||
match config.sink.kind {
|
||||
SinkKind::File => "File"
|
||||
_ => "other"
|
||||
},
|
||||
content="File",
|
||||
)
|
||||
inspect(config.sink.path, content="bitlogger.log")
|
||||
inspect(config.sink.append, 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")
|
||||
}
|
||||
|
||||
///|
|
||||
test "file preset rejects empty path like parser file sink config" {
|
||||
let preset_error = (fn() -> String raise ConfigError {
|
||||
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")
|
||||
|
||||
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"
|
||||
})() catch {
|
||||
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")
|
||||
}
|
||||
|
||||
///|
|
||||
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(
|
||||
"service.log",
|
||||
min_level=Level::Warn,
|
||||
@@ -86,10 +110,13 @@ test "preset helpers compose queue and file rotation without losing config" {
|
||||
match config.queue {
|
||||
Some(queue) => {
|
||||
inspect(queue.max_pending, content="32")
|
||||
inspect(match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
}, content="DropOldest")
|
||||
inspect(
|
||||
match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
},
|
||||
content="DropOldest",
|
||||
)
|
||||
}
|
||||
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" {
|
||||
let config = with_queue(
|
||||
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 {
|
||||
Some(queue) => {
|
||||
inspect(queue.max_pending, content="8")
|
||||
inspect(match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
}, content="DropNewest")
|
||||
inspect(
|
||||
match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
},
|
||||
content="DropNewest",
|
||||
)
|
||||
}
|
||||
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" {
|
||||
let console_config = with_file_rotation(console(target="console"), 64, max_backups=2)
|
||||
inspect(match console_config.sink.kind {
|
||||
SinkKind::Console => "Console"
|
||||
_ => "other"
|
||||
}, content="Console")
|
||||
let console_config = with_file_rotation(
|
||||
console(target="console"),
|
||||
64,
|
||||
max_backups=2,
|
||||
)
|
||||
inspect(
|
||||
match console_config.sink.kind {
|
||||
SinkKind::Console => "Console"
|
||||
_ => "other"
|
||||
},
|
||||
content="Console",
|
||||
)
|
||||
inspect(console_config.target, content="console")
|
||||
inspect(console_config.sink.rotation is None, content="true")
|
||||
|
||||
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,
|
||||
max_backups=4,
|
||||
)
|
||||
inspect(match text_config.sink.kind {
|
||||
SinkKind::TextConsole => "TextConsole"
|
||||
_ => "other"
|
||||
}, content="TextConsole")
|
||||
inspect(
|
||||
match text_config.sink.kind {
|
||||
SinkKind::TextConsole => "TextConsole"
|
||||
_ => "other"
|
||||
},
|
||||
content="TextConsole",
|
||||
)
|
||||
inspect(text_config.target, content="text")
|
||||
inspect(text_config.sink.text_formatter.separator, content=" :: ")
|
||||
inspect(text_config.sink.rotation is None, content="true")
|
||||
}
|
||||
|
||||
///|
|
||||
test "file rotation helper keeps queued non-file presets unchanged" {
|
||||
let config = with_queue(
|
||||
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)
|
||||
inspect(rotated.min_level.label(), content="ERROR")
|
||||
inspect(rotated.target, content="json")
|
||||
inspect(match rotated.sink.kind {
|
||||
SinkKind::JsonConsole => "JsonConsole"
|
||||
_ => "other"
|
||||
}, content="JsonConsole")
|
||||
inspect(
|
||||
match rotated.sink.kind {
|
||||
SinkKind::JsonConsole => "JsonConsole"
|
||||
_ => "other"
|
||||
},
|
||||
content="JsonConsole",
|
||||
)
|
||||
inspect(rotated.sink.rotation is None, content="true")
|
||||
match rotated.queue {
|
||||
Some(queue) => {
|
||||
inspect(queue.max_pending, content="4")
|
||||
inspect(match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
}, content="DropOldest")
|
||||
inspect(
|
||||
match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
},
|
||||
content="DropOldest",
|
||||
)
|
||||
}
|
||||
None => inspect(false, content="true")
|
||||
}
|
||||
|
||||
+9
-4
@@ -1,21 +1,26 @@
|
||||
///|
|
||||
pub type Field = @core.Field
|
||||
|
||||
///|
|
||||
pub fn field(key : String, value : String) -> Field {
|
||||
@core.field(key, value)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn fields(entries : Array[(String, String)]) -> Array[Field] {
|
||||
@core.fields(entries)
|
||||
}
|
||||
|
||||
///|
|
||||
pub type Record = @core.Record
|
||||
|
||||
///|
|
||||
fn record(
|
||||
level : Level,
|
||||
message : String,
|
||||
timestamp_ms~ : UInt64 = 0UL,
|
||||
target~ : String = "",
|
||||
fields~ : Array[Field] = [],
|
||||
timestamp_ms? : UInt64 = 0UL,
|
||||
target? : String = "",
|
||||
fields? : Array[Field] = [],
|
||||
) -> Record {
|
||||
@core.Record::new(level, message, timestamp_ms=timestamp_ms, target=target, fields=fields)
|
||||
@core.Record::new(level, message, timestamp_ms~, target~, fields~)
|
||||
}
|
||||
|
||||
+134
-37
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
pub fn RuntimeSink::file_available(self : RuntimeSink) -> Bool {
|
||||
match self {
|
||||
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 {
|
||||
File(sink) => sink.reopen(append=append)
|
||||
QueuedFile(sink) => sink.sink.reopen(append=append)
|
||||
File(sink) => sink.reopen(append~)
|
||||
QueuedFile(sink) => sink.sink.reopen(append~)
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::file_reopen_with_current_policy(self : RuntimeSink) -> Bool {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
File(sink) => {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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? {
|
||||
match self {
|
||||
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 {
|
||||
File(sink) => {
|
||||
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 {
|
||||
File(sink) => {
|
||||
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 {
|
||||
File(sink) => {
|
||||
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 {
|
||||
match self {
|
||||
File(sink) => {
|
||||
@@ -148,6 +178,7 @@ pub fn RuntimeSink::file_clear_rotation(self : RuntimeSink) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::file_flush(self : RuntimeSink) -> Bool {
|
||||
match self {
|
||||
File(sink) => sink.flush()
|
||||
@@ -159,6 +190,7 @@ pub fn RuntimeSink::file_flush(self : RuntimeSink) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::file_close(self : RuntimeSink) -> Bool {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
File(sink) => {
|
||||
@@ -230,6 +268,7 @@ pub fn RuntimeSink::file_reset_policy(self : RuntimeSink) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::file_policy(self : RuntimeSink) -> FileSinkPolicy {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
File(sink) => sink.policy_matches_default()
|
||||
@@ -254,91 +295,125 @@ pub fn RuntimeSink::file_policy_matches_default(self : RuntimeSink) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::file_state(self : RuntimeSink) -> FileSinkState {
|
||||
match self {
|
||||
File(sink) => sink.state()
|
||||
QueuedFile(sink) => sink.sink.state()
|
||||
_ => FileSinkState::new(
|
||||
"",
|
||||
available=false,
|
||||
append=false,
|
||||
auto_flush=false,
|
||||
rotation=None,
|
||||
open_failures=0,
|
||||
write_failures=0,
|
||||
flush_failures=0,
|
||||
rotation_failures=0,
|
||||
)
|
||||
_ =>
|
||||
FileSinkState::new(
|
||||
"",
|
||||
available=false,
|
||||
append=false,
|
||||
auto_flush=false,
|
||||
rotation=None,
|
||||
open_failures=0,
|
||||
write_failures=0,
|
||||
flush_failures=0,
|
||||
rotation_failures=0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::file_runtime_state(self : RuntimeSink) -> RuntimeFileState? {
|
||||
match self {
|
||||
File(sink) => Some(RuntimeFileState::new(sink.state()))
|
||||
QueuedFile(sink) => Some(
|
||||
RuntimeFileState::new(
|
||||
sink.sink.state(),
|
||||
queued=true,
|
||||
pending_count=sink.pending_count(),
|
||||
dropped_count=sink.dropped_count(),
|
||||
),
|
||||
)
|
||||
QueuedFile(sink) =>
|
||||
Some(
|
||||
RuntimeFileState::new(
|
||||
sink.sink.state(),
|
||||
queued=true,
|
||||
pending_count=sink.pending_count(),
|
||||
dropped_count=sink.dropped_count(),
|
||||
),
|
||||
)
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_available(self : ConfiguredLogger) -> Bool {
|
||||
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()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_reopen_append(self : ConfiguredLogger) -> Bool {
|
||||
self.sink.file_reopen_append()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_reopen_truncate(self : ConfiguredLogger) -> Bool {
|
||||
self.sink.file_reopen_truncate()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_append_mode(self : ConfiguredLogger) -> Bool {
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_path(self : ConfiguredLogger) -> String {
|
||||
self.sink.file_path()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_auto_flush(self : ConfiguredLogger) -> Bool {
|
||||
self.sink.file_auto_flush()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_rotation_enabled(self : ConfiguredLogger) -> Bool {
|
||||
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()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_set_rotation(
|
||||
self : ConfiguredLogger,
|
||||
rotation : FileRotation?,
|
||||
@@ -346,58 +421,80 @@ pub fn ConfiguredLogger::file_set_rotation(
|
||||
self.sink.file_set_rotation(rotation)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_clear_rotation(self : ConfiguredLogger) -> Bool {
|
||||
self.sink.file_clear_rotation()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_flush(self : ConfiguredLogger) -> Bool {
|
||||
self.sink.file_flush()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_close(self : ConfiguredLogger) -> Bool {
|
||||
self.sink.file_close()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_open_failures(self : ConfiguredLogger) -> Int {
|
||||
self.sink.file_open_failures()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_write_failures(self : ConfiguredLogger) -> Int {
|
||||
self.sink.file_write_failures()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_flush_failures(self : ConfiguredLogger) -> Int {
|
||||
self.sink.file_flush_failures()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_rotation_failures(self : ConfiguredLogger) -> Int {
|
||||
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()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_reset_policy(self : ConfiguredLogger) -> Bool {
|
||||
self.sink.file_reset_policy()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_policy(self : ConfiguredLogger) -> FileSinkPolicy {
|
||||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::file_state(self : ConfiguredLogger) -> FileSinkState {
|
||||
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()
|
||||
}
|
||||
|
||||
+110
-45
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
pub(all) enum RuntimeSink {
|
||||
Console(ConsoleSink)
|
||||
JsonConsole(JsonConsoleSink)
|
||||
@@ -9,33 +10,54 @@ pub(all) enum RuntimeSink {
|
||||
QueuedFile(QueuedSink[FileSink])
|
||||
}
|
||||
|
||||
///|
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
@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)
|
||||
}
|
||||
|
||||
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 {
|
||||
Console(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 {
|
||||
match self {
|
||||
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 {
|
||||
Console(_) => 0
|
||||
JsonConsole(_) => 0
|
||||
TextConsole(_) => 0
|
||||
File(sink) => if sink.flush() { 1 } else { 0 }
|
||||
QueuedConsole(sink) => sink.drain(max_items=max_items)
|
||||
QueuedJsonConsole(sink) => sink.drain(max_items=max_items)
|
||||
QueuedTextConsole(sink) => sink.drain(max_items=max_items)
|
||||
QueuedFile(sink) => sink.drain(max_items=max_items)
|
||||
QueuedConsole(sink) => sink.drain(max_items~)
|
||||
QueuedJsonConsole(sink) => sink.drain(max_items~)
|
||||
QueuedTextConsole(sink) => sink.drain(max_items~)
|
||||
QueuedFile(sink) => sink.drain(max_items~)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::close(self : RuntimeSink) -> Bool {
|
||||
match self {
|
||||
Console(_) => true
|
||||
@@ -87,6 +112,7 @@ pub fn RuntimeSink::close(self : RuntimeSink) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::pending_count(self : RuntimeSink) -> Int {
|
||||
match self {
|
||||
Console(_) => 0
|
||||
@@ -100,6 +126,7 @@ pub fn RuntimeSink::pending_count(self : RuntimeSink) -> Int {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeSink::dropped_count(self : RuntimeSink) -> Int {
|
||||
match self {
|
||||
Console(_) => 0
|
||||
@@ -113,63 +140,96 @@ pub fn RuntimeSink::dropped_count(self : RuntimeSink) -> Int {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub type ConfiguredLogger = Logger[RuntimeSink]
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::flush(self : ConfiguredLogger) -> Int {
|
||||
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 {
|
||||
self.sink.close()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::pending_count(self : ConfiguredLogger) -> Int {
|
||||
self.sink.pending_count()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn ConfiguredLogger::dropped_count(self : ConfiguredLogger) -> Int {
|
||||
self.sink.dropped_count()
|
||||
}
|
||||
|
||||
///|
|
||||
fn build_runtime_sink(config : SinkConfig) -> RuntimeSink {
|
||||
match config.kind {
|
||||
SinkKind::Console => RuntimeSink::Console(console_sink())
|
||||
SinkKind::JsonConsole => RuntimeSink::JsonConsole(json_console_sink())
|
||||
SinkKind::TextConsole => RuntimeSink::TextConsole(
|
||||
text_console_sink(config.text_formatter.to_formatter()),
|
||||
)
|
||||
SinkKind::File => RuntimeSink::File(
|
||||
file_sink(
|
||||
config.path,
|
||||
append=config.append,
|
||||
auto_flush=config.auto_flush,
|
||||
rotation=config.rotation,
|
||||
formatter=fn(rec) {
|
||||
format_text(rec, formatter=config.text_formatter.to_formatter())
|
||||
},
|
||||
),
|
||||
)
|
||||
SinkKind::TextConsole =>
|
||||
RuntimeSink::TextConsole(
|
||||
text_console_sink(config.text_formatter.to_formatter()),
|
||||
)
|
||||
SinkKind::File =>
|
||||
RuntimeSink::File(
|
||||
file_sink(
|
||||
config.path,
|
||||
append=config.append,
|
||||
auto_flush=config.auto_flush,
|
||||
rotation=config.rotation,
|
||||
formatter=fn(rec) {
|
||||
format_text(rec, formatter=config.text_formatter.to_formatter())
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn apply_queue_config(sink : RuntimeSink, queue : QueueConfig) -> RuntimeSink {
|
||||
match sink {
|
||||
Console(inner) => RuntimeSink::QueuedConsole(
|
||||
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow),
|
||||
)
|
||||
JsonConsole(inner) => RuntimeSink::QueuedJsonConsole(
|
||||
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow),
|
||||
)
|
||||
TextConsole(inner) => RuntimeSink::QueuedTextConsole(
|
||||
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow),
|
||||
)
|
||||
File(inner) => RuntimeSink::QueuedFile(
|
||||
queued_sink(inner, max_pending=queue.max_pending, overflow=queue.overflow),
|
||||
)
|
||||
Console(inner) =>
|
||||
RuntimeSink::QueuedConsole(
|
||||
queued_sink(
|
||||
inner,
|
||||
max_pending=queue.max_pending,
|
||||
overflow=queue.overflow,
|
||||
),
|
||||
)
|
||||
JsonConsole(inner) =>
|
||||
RuntimeSink::QueuedJsonConsole(
|
||||
queued_sink(
|
||||
inner,
|
||||
max_pending=queue.max_pending,
|
||||
overflow=queue.overflow,
|
||||
),
|
||||
)
|
||||
TextConsole(inner) =>
|
||||
RuntimeSink::QueuedTextConsole(
|
||||
queued_sink(
|
||||
inner,
|
||||
max_pending=queue.max_pending,
|
||||
overflow=queue.overflow,
|
||||
),
|
||||
)
|
||||
File(inner) =>
|
||||
RuntimeSink::QueuedFile(
|
||||
queued_sink(
|
||||
inner,
|
||||
max_pending=queue.max_pending,
|
||||
overflow=queue.overflow,
|
||||
),
|
||||
)
|
||||
QueuedConsole(_) => sink
|
||||
QueuedJsonConsole(_) => sink
|
||||
QueuedTextConsole(_) => sink
|
||||
@@ -177,16 +237,21 @@ fn apply_queue_config(sink : RuntimeSink, queue : QueueConfig) -> RuntimeSink {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn build_logger(config : LoggerConfig) -> ConfiguredLogger {
|
||||
let sink = build_runtime_sink(config.sink)
|
||||
let actual_sink = match config.queue {
|
||||
None => sink
|
||||
Some(queue) => apply_queue_config(sink, queue)
|
||||
}
|
||||
Logger::new(actual_sink, min_level=config.min_level, target=config.target)
|
||||
.with_timestamp(enabled=config.timestamp)
|
||||
Logger::new(actual_sink, min_level=config.min_level, target=config.target).with_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))
|
||||
}
|
||||
|
||||
+78
-29
@@ -1,26 +1,32 @@
|
||||
///|
|
||||
pub trait Sink {
|
||||
write(Self, Record) -> Unit
|
||||
fn write(Self, Record) -> Unit
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct ConsoleSink {
|
||||
_dummy : Unit
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn console_sink() -> ConsoleSink {
|
||||
{ _dummy: () }
|
||||
}
|
||||
|
||||
pub impl Sink for ConsoleSink with write(self, rec) {
|
||||
///|
|
||||
pub impl Sink for ConsoleSink with fn write(self, rec) {
|
||||
ignore(self)
|
||||
println(format_text(rec))
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct ContextSink[S] {
|
||||
sink : S
|
||||
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 {
|
||||
rec.fields
|
||||
} 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))
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct JsonConsoleSink {
|
||||
_dummy : Unit
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn json_console_sink() -> JsonConsoleSink {
|
||||
{ _dummy: () }
|
||||
}
|
||||
|
||||
pub impl Sink for JsonConsoleSink with write(self, rec) {
|
||||
///|
|
||||
pub impl Sink for JsonConsoleSink with fn write(self, rec) {
|
||||
ignore(self)
|
||||
println(format_json(rec))
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct FormattedConsoleSink {
|
||||
formatter : RecordFormatter
|
||||
}
|
||||
|
||||
pub fn formatted_console_sink(formatter : RecordFormatter) -> FormattedConsoleSink {
|
||||
///|
|
||||
pub fn formatted_console_sink(
|
||||
formatter : RecordFormatter,
|
||||
) -> FormattedConsoleSink {
|
||||
{ formatter, }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn text_console_sink(formatter : TextFormatter) -> FormattedConsoleSink {
|
||||
formatted_console_sink(fn(rec) {
|
||||
format_text(rec, formatter=formatter)
|
||||
})
|
||||
formatted_console_sink(fn(rec) { format_text(rec, formatter~) })
|
||||
}
|
||||
|
||||
pub impl Sink for FormattedConsoleSink with write(self, rec) {
|
||||
///|
|
||||
pub impl Sink for FormattedConsoleSink with fn write(self, rec) {
|
||||
println((self.formatter)(rec))
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct FormattedCallbackSink {
|
||||
formatter : RecordFormatter
|
||||
callback : (String) -> Unit
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn formatted_callback_sink(
|
||||
formatter : RecordFormatter,
|
||||
callback : (String) -> Unit,
|
||||
@@ -74,54 +89,63 @@ pub fn formatted_callback_sink(
|
||||
{ formatter, callback }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn text_callback_sink(
|
||||
formatter : TextFormatter,
|
||||
callback : (String) -> Unit,
|
||||
) -> FormattedCallbackSink {
|
||||
formatted_callback_sink(fn(rec) {
|
||||
format_text(rec, formatter=formatter)
|
||||
}, callback)
|
||||
formatted_callback_sink(fn(rec) { format_text(rec, 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))
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct FanoutSink[A, B] {
|
||||
left : A
|
||||
right : B
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[A, B] fanout_sink(left : A, right : B) -> FanoutSink[A, B] {
|
||||
{ left, right }
|
||||
}
|
||||
|
||||
pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with write(self, rec) {
|
||||
///|
|
||||
pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with fn write(self, rec) {
|
||||
self.left.write(rec)
|
||||
self.right.write(rec.copy())
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct SplitSink[A, B] {
|
||||
left : A
|
||||
right : B
|
||||
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 }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[A, B] split_by_level(
|
||||
left : A,
|
||||
right : B,
|
||||
min_level~ : Level = Level::Warn,
|
||||
min_level? : Level = Level::Warn,
|
||||
) -> SplitSink[A, B] {
|
||||
split_sink(left, right, fn(rec) {
|
||||
rec.level.enabled(min_level)
|
||||
})
|
||||
split_sink(left, right, fn(rec) { 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) {
|
||||
self.left.write(rec)
|
||||
} else {
|
||||
@@ -129,33 +153,40 @@ pub impl[A : Sink, B : Sink] Sink for SplitSink[A, B] with write(self, rec) {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct CallbackSink {
|
||||
callback : (Record) -> Unit
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn callback_sink(callback : (Record) -> Unit) -> CallbackSink {
|
||||
{ callback, }
|
||||
}
|
||||
|
||||
pub impl Sink for CallbackSink with write(self, rec) {
|
||||
///|
|
||||
pub impl Sink for CallbackSink with fn write(self, rec) {
|
||||
(self.callback)(rec)
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct BufferedSink[S] {
|
||||
sink : S
|
||||
buffer : Ref[Array[Record]]
|
||||
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 }
|
||||
{ sink, buffer: Ref([]), flush_limit: actual_limit }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] BufferedSink::pending_count(self : BufferedSink[S]) -> Int {
|
||||
self.buffer.val.length()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S : Sink] BufferedSink::flush(self : BufferedSink[S]) -> Unit {
|
||||
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)
|
||||
if self.buffer.val.length() >= self.flush_limit {
|
||||
self.flush()
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub type QueueOverflowPolicy = @utils.QueueOverflowPolicy
|
||||
|
||||
///|
|
||||
pub struct QueuedSink[S] {
|
||||
sink : S
|
||||
queue : @queue.Queue[Record]
|
||||
@@ -185,10 +219,11 @@ pub struct QueuedSink[S] {
|
||||
dropped_count : Ref[Int]
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] queued_sink(
|
||||
sink : S,
|
||||
max_pending~ : Int = 0,
|
||||
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
max_pending? : Int = 0,
|
||||
overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
) -> QueuedSink[S] {
|
||||
{
|
||||
sink,
|
||||
@@ -199,15 +234,21 @@ pub fn[S] queued_sink(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] QueuedSink::pending_count(self : QueuedSink[S]) -> Int {
|
||||
self.queue.length()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] QueuedSink::dropped_count(self : QueuedSink[S]) -> Int {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
if !full {
|
||||
self.queue.push(rec)
|
||||
@@ -245,30 +288,36 @@ pub impl[S] Sink for QueuedSink[S] with write(self, rec) {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct FilterSink[S] {
|
||||
sink : S
|
||||
predicate : (Record) -> Bool
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] filter_sink(sink : S, predicate : (Record) -> Bool) -> FilterSink[S] {
|
||||
{ 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) {
|
||||
self.sink.write(rec)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct PatchSink[S] {
|
||||
sink : S
|
||||
patch : RecordPatch
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn[S] patch_sink(sink : S, patch : RecordPatch) -> PatchSink[S] {
|
||||
{ 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))
|
||||
}
|
||||
|
||||
+79
-33
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
pub struct FileSink {
|
||||
path : String
|
||||
append : Ref[Bool]
|
||||
@@ -14,28 +15,32 @@ pub struct FileSink {
|
||||
rotation_failures : Ref[Int]
|
||||
}
|
||||
|
||||
///|
|
||||
pub type FileRotation = @utils.FileRotation
|
||||
|
||||
///|
|
||||
pub type FileSinkState = @utils.FileSinkState
|
||||
|
||||
///|
|
||||
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 {
|
||||
native_files_supported_internal()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn file_sink(
|
||||
path : String,
|
||||
append~ : Bool = true,
|
||||
auto_flush~ : Bool = true,
|
||||
rotation~ : FileRotation? = None,
|
||||
formatter~ : RecordFormatter = fn(rec) {
|
||||
format_text(rec)
|
||||
},
|
||||
append? : Bool = true,
|
||||
auto_flush? : Bool = true,
|
||||
rotation? : FileRotation? = None,
|
||||
formatter? : RecordFormatter = fn(rec) { format_text(rec) },
|
||||
) -> FileSink {
|
||||
let handle = open_file_handle_internal(path, append)
|
||||
{
|
||||
@@ -55,10 +60,12 @@ pub fn file_sink(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::is_available(self : FileSink) -> Bool {
|
||||
self.handle.val is Some(_)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::flush(self : FileSink) -> Bool {
|
||||
match self.handle.val {
|
||||
None => false
|
||||
@@ -72,48 +79,62 @@ pub fn FileSink::flush(self : FileSink) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::append_mode(self : FileSink) -> Bool {
|
||||
self.append.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::set_append_mode(self : FileSink, append : Bool) -> Unit {
|
||||
self.append.val = append
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::path(self : FileSink) -> String {
|
||||
self.path
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::auto_flush_enabled(self : FileSink) -> Bool {
|
||||
self.auto_flush.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::rotation_enabled(self : FileSink) -> Bool {
|
||||
self.rotation.val is Some(_)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::rotation_config(self : FileSink) -> FileRotation? {
|
||||
self.rotation.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::set_auto_flush(self : FileSink, enabled : Bool) -> Unit {
|
||||
self.auto_flush.val = enabled
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::set_policy(self : FileSink, policy : FileSinkPolicy) -> Unit {
|
||||
self.append.val = policy.append
|
||||
self.auto_flush.val = policy.auto_flush
|
||||
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
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::clear_rotation(self : FileSink) -> Unit {
|
||||
self.rotation.val = None
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::close(self : FileSink) -> Bool {
|
||||
match self.handle.val {
|
||||
None => false
|
||||
@@ -125,22 +146,27 @@ pub fn FileSink::close(self : FileSink) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::rotation_failures(self : FileSink) -> Int {
|
||||
self.rotation_failures.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::open_failures(self : FileSink) -> Int {
|
||||
self.open_failures.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::write_failures(self : FileSink) -> Int {
|
||||
self.write_failures.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::flush_failures(self : FileSink) -> Int {
|
||||
self.flush_failures.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::reset_failure_counters(self : FileSink) -> Unit {
|
||||
self.open_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
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::reset_policy(self : FileSink) -> Unit {
|
||||
self.append.val = self.default_append
|
||||
self.auto_flush.val = self.default_auto_flush
|
||||
self.rotation.val = self.default_rotation
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::policy(self : FileSink) -> FileSinkPolicy {
|
||||
FileSinkPolicy::new(
|
||||
append=self.append.val,
|
||||
@@ -162,6 +190,7 @@ pub fn FileSink::policy(self : FileSink) -> FileSinkPolicy {
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::default_policy(self : FileSink) -> FileSinkPolicy {
|
||||
FileSinkPolicy::new(
|
||||
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 {
|
||||
let current = self.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)
|
||||
}
|
||||
|
||||
fn policy_rotation_equals_internal(left : FileRotation?, right : FileRotation?) -> Bool {
|
||||
///|
|
||||
fn policy_rotation_equals_internal(
|
||||
left : FileRotation?,
|
||||
right : FileRotation?,
|
||||
) -> Bool {
|
||||
match (left, right) {
|
||||
(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
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::state(self : FileSink) -> FileSinkState {
|
||||
FileSinkState::new(
|
||||
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)
|
||||
self.append.val = append_mode
|
||||
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 {
|
||||
self.reopen()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::reopen_append(self : FileSink) -> Bool {
|
||||
self.reopen(append=Some(true))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSink::reopen_truncate(self : FileSink) -> Bool {
|
||||
self.reopen(append=Some(false))
|
||||
}
|
||||
|
||||
///|
|
||||
fn rotated_file_path(path : String, index : Int) -> String {
|
||||
"\{path}.\{index}"
|
||||
}
|
||||
|
||||
///|
|
||||
fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
|
||||
let closed = match sink.handle.val {
|
||||
None => true
|
||||
@@ -249,7 +291,9 @@ fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
|
||||
return false
|
||||
}
|
||||
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; {
|
||||
let from_path = rotated_file_path(sink.path, index)
|
||||
let to_path = rotated_file_path(sink.path, index + 1)
|
||||
@@ -264,40 +308,42 @@ fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
|
||||
sink.handle.val is Some(_)
|
||||
}
|
||||
|
||||
///|
|
||||
fn rotate_if_needed_internal(sink : FileSink, next_line_bytes : Int) -> Bool {
|
||||
match sink.rotation.val {
|
||||
None => true
|
||||
Some(rotation) => match sink.handle.val {
|
||||
None => false
|
||||
Some(handle) => {
|
||||
let size = file_size_internal(handle)
|
||||
if size + next_line_bytes <= rotation.max_bytes {
|
||||
true
|
||||
} else {
|
||||
let rotated = rotate_file_sink_internal(sink, rotation)
|
||||
if !rotated {
|
||||
sink.rotation_failures.val += 1
|
||||
Some(rotation) =>
|
||||
match sink.handle.val {
|
||||
None => false
|
||||
Some(handle) => {
|
||||
let size = file_size_internal(handle)
|
||||
if size + next_line_bytes <= rotation.max_bytes {
|
||||
true
|
||||
} else {
|
||||
let rotated = rotate_file_sink_internal(sink, rotation)
|
||||
if !rotated {
|
||||
sink.rotation_failures.val += 1
|
||||
}
|
||||
rotated
|
||||
}
|
||||
rotated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub impl Sink for FileSink with write(self, rec) {
|
||||
///|
|
||||
pub impl Sink for FileSink with fn write(self, rec) {
|
||||
match self.handle.val {
|
||||
None => {
|
||||
self.write_failures.val += 1
|
||||
}
|
||||
None => self.write_failures.val += 1
|
||||
Some(_) => {
|
||||
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 {
|
||||
match self.handle.val {
|
||||
None => {
|
||||
self.write_failures.val += 1
|
||||
}
|
||||
None => self.write_failures.val += 1
|
||||
Some(active) => {
|
||||
let wrote = write_file_handle_internal(active, line)
|
||||
if wrote {
|
||||
|
||||
+195
-95
@@ -1,7 +1,9 @@
|
||||
///|
|
||||
pub(all) suberror ConfigError {
|
||||
InvalidConfig(String)
|
||||
}
|
||||
|
||||
///|
|
||||
pub(all) enum SinkKind {
|
||||
Console
|
||||
JsonConsole
|
||||
@@ -9,6 +11,7 @@ pub(all) enum SinkKind {
|
||||
File
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct TextFormatterConfig {
|
||||
show_timestamp : Bool
|
||||
show_level : Bool
|
||||
@@ -25,20 +28,21 @@ pub struct TextFormatterConfig {
|
||||
style_tags : Map[String, TextStyle]
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn TextFormatterConfig::new(
|
||||
show_timestamp~ : Bool = true,
|
||||
show_level~ : Bool = true,
|
||||
show_target~ : Bool = true,
|
||||
show_fields~ : Bool = true,
|
||||
separator~ : String = " ",
|
||||
field_separator~ : String = " ",
|
||||
template~ : String = "",
|
||||
color_mode~ : ColorMode = ColorMode::Never,
|
||||
color_support~ : ColorSupport = ColorSupport::TrueColor,
|
||||
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
||||
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
style_tags~ : Map[String, TextStyle] = {},
|
||||
show_timestamp? : Bool = true,
|
||||
show_level? : Bool = true,
|
||||
show_target? : Bool = true,
|
||||
show_fields? : Bool = true,
|
||||
separator? : String = " ",
|
||||
field_separator? : String = " ",
|
||||
template? : String = "",
|
||||
color_mode? : ColorMode = ColorMode::Never,
|
||||
color_support? : ColorSupport = ColorSupport::TrueColor,
|
||||
style_markup? : StyleMarkupMode = StyleMarkupMode::Full,
|
||||
target_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
fields_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
style_tags? : Map[String, TextStyle] = {},
|
||||
) -> TextFormatterConfig {
|
||||
{
|
||||
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()
|
||||
for name, style in style_tags {
|
||||
ignore(registry.set_tag(name, style=style))
|
||||
ignore(registry.set_tag(name, style~))
|
||||
}
|
||||
registry
|
||||
}
|
||||
|
||||
pub fn TextFormatterConfig::to_formatter(self : TextFormatterConfig) -> TextFormatter {
|
||||
///|
|
||||
pub fn TextFormatterConfig::to_formatter(
|
||||
self : TextFormatterConfig,
|
||||
) -> TextFormatter {
|
||||
text_formatter(
|
||||
show_timestamp=self.show_timestamp,
|
||||
show_level=self.show_level,
|
||||
@@ -87,18 +97,21 @@ pub fn TextFormatterConfig::to_formatter(self : TextFormatterConfig) -> TextForm
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct QueueConfig {
|
||||
max_pending : Int
|
||||
overflow : QueueOverflowPolicy
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn QueueConfig::new(
|
||||
max_pending : Int,
|
||||
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
overflow? : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
||||
) -> QueueConfig {
|
||||
{ max_pending, overflow }
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct SinkConfig {
|
||||
kind : SinkKind
|
||||
path : String
|
||||
@@ -108,24 +121,19 @@ pub struct SinkConfig {
|
||||
text_formatter : TextFormatterConfig
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn SinkConfig::new(
|
||||
kind~ : SinkKind = SinkKind::Console,
|
||||
path~ : String = "",
|
||||
append~ : Bool = true,
|
||||
auto_flush~ : Bool = true,
|
||||
rotation~ : FileRotation? = None,
|
||||
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
|
||||
kind? : SinkKind = SinkKind::Console,
|
||||
path? : String = "",
|
||||
append? : Bool = true,
|
||||
auto_flush? : Bool = true,
|
||||
rotation? : FileRotation? = None,
|
||||
text_formatter? : TextFormatterConfig = default_text_formatter_config(),
|
||||
) -> SinkConfig {
|
||||
{
|
||||
kind,
|
||||
path,
|
||||
append,
|
||||
auto_flush,
|
||||
rotation,
|
||||
text_formatter,
|
||||
}
|
||||
{ kind, path, append, auto_flush, rotation, text_formatter }
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct LoggerConfig {
|
||||
min_level : @core.Level
|
||||
target : String
|
||||
@@ -134,34 +142,33 @@ pub struct LoggerConfig {
|
||||
queue : QueueConfig?
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn LoggerConfig::new(
|
||||
min_level~ : @core.Level = @core.Level::Info,
|
||||
target~ : String = "",
|
||||
timestamp~ : Bool = false,
|
||||
sink~ : SinkConfig = default_sink_config(),
|
||||
queue~ : QueueConfig? = None,
|
||||
min_level? : @core.Level = @core.Level::Info,
|
||||
target? : String = "",
|
||||
timestamp? : Bool = false,
|
||||
sink? : SinkConfig = default_sink_config(),
|
||||
queue? : QueueConfig? = None,
|
||||
) -> LoggerConfig {
|
||||
{
|
||||
min_level,
|
||||
target,
|
||||
timestamp,
|
||||
sink,
|
||||
queue,
|
||||
}
|
||||
{ min_level, target, timestamp, sink, queue }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_text_formatter_config() -> TextFormatterConfig {
|
||||
TextFormatterConfig::new()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_sink_config() -> SinkConfig {
|
||||
SinkConfig::new()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_logger_config() -> LoggerConfig {
|
||||
LoggerConfig::new()
|
||||
}
|
||||
|
||||
///|
|
||||
fn expect_object(
|
||||
value : @json_parser.JsonValue,
|
||||
context : String,
|
||||
@@ -172,33 +179,40 @@ fn expect_object(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_string(
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
default~ : String = "",
|
||||
default? : String = "",
|
||||
) -> String raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => default
|
||||
Some(value) => match value.as_string() {
|
||||
Some(text) => text
|
||||
None => raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => text
|
||||
None =>
|
||||
raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_optional_string(
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
) -> String? raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => None
|
||||
Some(value) => match value.as_string() {
|
||||
Some(text) => Some(text)
|
||||
None => raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_string() {
|
||||
Some(text) => Some(text)
|
||||
None =>
|
||||
raise ConfigError::InvalidConfig("Expected string at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_bool(
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
@@ -206,13 +220,15 @@ fn get_bool(
|
||||
) -> Bool raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => default
|
||||
Some(value) => match value.as_bool() {
|
||||
Some(flag) => flag
|
||||
None => raise ConfigError::InvalidConfig("Expected bool at key " + key)
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_bool() {
|
||||
Some(flag) => flag
|
||||
None => raise ConfigError::InvalidConfig("Expected bool at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn get_int(
|
||||
obj : Map[String, @json_parser.JsonValue],
|
||||
key : String,
|
||||
@@ -220,13 +236,16 @@ fn get_int(
|
||||
) -> Int raise ConfigError {
|
||||
match obj.get(key) {
|
||||
None => default
|
||||
Some(value) => match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None => raise ConfigError::InvalidConfig("Expected number at key " + key)
|
||||
}
|
||||
Some(value) =>
|
||||
match value.as_number() {
|
||||
Some(number) => number.to_int()
|
||||
None =>
|
||||
raise ConfigError::InvalidConfig("Expected number at key " + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn parse_level(name : String) -> @core.Level raise ConfigError {
|
||||
match name.to_upper() {
|
||||
"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 {
|
||||
match name.to_upper() {
|
||||
"DROPNEWEST" => QueueOverflowPolicy::DropNewest
|
||||
"DROPPOLDEST" => 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 {
|
||||
match name.to_upper() {
|
||||
"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 {
|
||||
match name.to_upper() {
|
||||
"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 {
|
||||
match name.to_upper() {
|
||||
"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 {
|
||||
match name.to_upper() {
|
||||
"DISABLED" => StyleMarkupMode::Disabled
|
||||
"BUILTIN" => StyleMarkupMode::Builtin
|
||||
"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 {
|
||||
match kind {
|
||||
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")
|
||||
TextFormatterConfig::new(
|
||||
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=" "),
|
||||
template=get_string(obj, "template", default=""),
|
||||
color_mode=parse_color_mode(get_string(obj, "color_mode", default="never")),
|
||||
color_support=parse_color_support(get_string(obj, "color_support", default="truecolor")),
|
||||
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")),
|
||||
color_support=parse_color_support(
|
||||
get_string(obj, "color_support", default="truecolor"),
|
||||
),
|
||||
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") {
|
||||
None => {}
|
||||
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(
|
||||
value : @json_parser.JsonValue,
|
||||
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 style_tags : Map[String, TextStyle] = {}
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
QueueConfig::new(
|
||||
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")
|
||||
file_rotation(
|
||||
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 kind = parse_sink_kind(get_string(obj, "kind", default="console"))
|
||||
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="")
|
||||
match kind {
|
||||
SinkKind::File => if path == "" {
|
||||
raise ConfigError::InvalidConfig("File sink requires non-empty path")
|
||||
}
|
||||
SinkKind::File =>
|
||||
if path == "" {
|
||||
raise ConfigError::InvalidConfig("File sink requires non-empty path")
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
SinkConfig::new(
|
||||
kind=kind,
|
||||
path=path,
|
||||
kind~,
|
||||
path~,
|
||||
append=get_bool(obj, "append", default=true),
|
||||
auto_flush=get_bool(obj, "auto_flush", default=true),
|
||||
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 {
|
||||
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 {
|
||||
@json_parser.JsonValue::Object({
|
||||
"max_pending": @json_parser.JsonValue::Number(queue.max_pending.to_double()),
|
||||
"overflow": @json_parser.JsonValue::String(match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
QueueOverflowPolicy::DropOldest => "DropOldest"
|
||||
}),
|
||||
"overflow": @json_parser.JsonValue::String(
|
||||
match queue.overflow {
|
||||
QueueOverflowPolicy::DropNewest => "DropNewest"
|
||||
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)
|
||||
if pretty {
|
||||
@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] = {
|
||||
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp),
|
||||
"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),
|
||||
"field_separator": @json_parser.JsonValue::String(config.field_separator),
|
||||
"template": @json_parser.JsonValue::String(config.template),
|
||||
"color_mode": @json_parser.JsonValue::String(color_mode_label(config.color_mode)),
|
||||
"color_support": @json_parser.JsonValue::String(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)),
|
||||
"color_mode": @json_parser.JsonValue::String(
|
||||
color_mode_label(config.color_mode),
|
||||
),
|
||||
"color_support": @json_parser.JsonValue::String(
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
fn text_style_config_to_json(style : TextStyle) -> @json_parser.JsonValue {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {
|
||||
"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)
|
||||
}
|
||||
|
||||
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] = {}
|
||||
for name, style in style_tags {
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn stringify_text_formatter_config(
|
||||
config : TextFormatterConfig,
|
||||
pretty~ : Bool = false,
|
||||
pretty? : Bool = false,
|
||||
) -> String {
|
||||
let value = text_formatter_config_to_json(config)
|
||||
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({
|
||||
"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 {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {
|
||||
"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)
|
||||
}
|
||||
|
||||
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)
|
||||
if pretty {
|
||||
@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 {
|
||||
let obj : Map[String, @json_parser.JsonValue] = {
|
||||
"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)
|
||||
}
|
||||
|
||||
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)
|
||||
if pretty {
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
fn string_to_c_bytes(str : String) -> Bytes {
|
||||
let res : Array[Byte] = []
|
||||
let len = str.length()
|
||||
@@ -31,14 +32,18 @@ fn string_to_c_bytes(str : String) -> Bytes {
|
||||
Bytes::from_array(res)
|
||||
}
|
||||
|
||||
///|
|
||||
#external
|
||||
type NativeFileHandle
|
||||
|
||||
///|
|
||||
#borrow(path, mode)
|
||||
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"
|
||||
|
||||
///|
|
||||
#borrow(buffer)
|
||||
extern "C" fn file_write_ffi(
|
||||
buffer : Bytes,
|
||||
@@ -47,32 +52,37 @@ extern "C" fn file_write_ffi(
|
||||
handle : NativeFileHandle,
|
||||
) -> Int = "bitlogger_file_write"
|
||||
|
||||
///|
|
||||
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_seek_ffi(
|
||||
handle : NativeFileHandle,
|
||||
offset : Int,
|
||||
origin : Int,
|
||||
) -> Int = "bitlogger_file_seek"
|
||||
|
||||
///|
|
||||
extern "C" fn file_tell_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_tell"
|
||||
|
||||
///|
|
||||
#borrow(from_path, to_path)
|
||||
extern "C" fn file_rename_ffi(
|
||||
from_path : Bytes,
|
||||
to_path : Bytes,
|
||||
) -> Int = "bitlogger_file_rename"
|
||||
extern "C" fn file_rename_ffi(from_path : Bytes, to_path : Bytes) -> Int = "bitlogger_file_rename"
|
||||
|
||||
///|
|
||||
#borrow(path)
|
||||
extern "C" fn file_remove_ffi(path : Bytes) -> Int = "bitlogger_file_remove"
|
||||
|
||||
///|
|
||||
pub struct FileHandle {
|
||||
path : String
|
||||
raw : NativeFileHandle
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
||||
let mode = if append { "ab" } else { "wb" }
|
||||
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 written = file_write_ffi(bytes, 1, bytes.length() - 1, handle.raw)
|
||||
written == bytes.length() - 1
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn flush_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
file_flush_ffi(handle.raw) == 0
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn close_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
file_close_ffi(handle.raw) == 0
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn file_size_internal(handle : FileHandle) -> Int {
|
||||
ignore(file_seek_ffi(handle.raw, 0, 2))
|
||||
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 {
|
||||
file_rename_ffi(string_to_c_bytes(from_path), string_to_c_bytes(to_path)) == 0
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn remove_file_internal(path : String) -> Bool {
|
||||
file_remove_ffi(string_to_c_bytes(path)) == 0
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn string_byte_length_internal(content : String) -> Int {
|
||||
string_to_c_bytes(content).length() - 1
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn native_files_supported_internal() -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
///|
|
||||
pub struct FileHandle {
|
||||
path : String
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
||||
ignore(append)
|
||||
ignore(path)
|
||||
@@ -10,42 +12,53 @@ pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
||||
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(content)
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn flush_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
ignore(handle)
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn close_file_handle_internal(handle : FileHandle) -> Bool {
|
||||
ignore(handle)
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn file_size_internal(handle : FileHandle) -> Int {
|
||||
ignore(handle)
|
||||
0
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn rename_file_internal(from_path : String, to_path : String) -> Bool {
|
||||
ignore(from_path)
|
||||
ignore(to_path)
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn remove_file_internal(path : String) -> Bool {
|
||||
ignore(path)
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn string_byte_length_internal(content : String) -> Int {
|
||||
content.length()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn native_files_supported_internal() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
+16
-16
@@ -1,29 +1,27 @@
|
||||
///|
|
||||
pub type RecordPredicate = (@core.Record) -> Bool
|
||||
|
||||
///|
|
||||
pub fn level_at_least(min_level : @core.Level) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
rec.level.priority() >= min_level.priority()
|
||||
}
|
||||
fn(rec) { rec.level.priority() >= min_level.priority() }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn target_is(target : String) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
rec.target == target
|
||||
}
|
||||
fn(rec) { rec.target == target }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn target_has_prefix(prefix : String) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
rec.target.has_prefix(prefix)
|
||||
}
|
||||
fn(rec) { rec.target.has_prefix(prefix) }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn message_contains(fragment : String) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
rec.message.contains(fragment)
|
||||
}
|
||||
fn(rec) { rec.message.contains(fragment) }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn has_field(key : String) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
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 {
|
||||
fn(rec) {
|
||||
for field in rec.fields {
|
||||
@@ -46,16 +45,16 @@ pub fn field_equals(key : String, value : String) -> RecordPredicate {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn not_(predicate : RecordPredicate) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
!(predicate(rec))
|
||||
}
|
||||
fn(rec) { !predicate(rec) }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
for predicate in predicates {
|
||||
if !(predicate(rec)) {
|
||||
if !predicate(rec) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -63,6 +62,7 @@ pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate {
|
||||
fn(rec) {
|
||||
for predicate in predicates {
|
||||
|
||||
+322
-150
@@ -1,22 +1,27 @@
|
||||
///|
|
||||
pub type RecordFormatter = (@core.Record) -> String
|
||||
|
||||
///|
|
||||
pub(all) enum ColorMode {
|
||||
Never
|
||||
Auto
|
||||
Always
|
||||
}
|
||||
|
||||
///|
|
||||
pub(all) enum ColorSupport {
|
||||
Basic
|
||||
TrueColor
|
||||
}
|
||||
|
||||
///|
|
||||
pub(all) enum StyleMarkupMode {
|
||||
Disabled
|
||||
Builtin
|
||||
Full
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct TextStyle {
|
||||
fg : String?
|
||||
bg : String?
|
||||
@@ -26,29 +31,34 @@ pub struct TextStyle {
|
||||
underline : Bool
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn text_style(
|
||||
fg~ : String? = None,
|
||||
bg~ : String? = None,
|
||||
bold~ : Bool = false,
|
||||
dim~ : Bool = false,
|
||||
italic~ : Bool = false,
|
||||
underline~ : Bool = false,
|
||||
fg? : String? = None,
|
||||
bg? : String? = None,
|
||||
bold? : Bool = false,
|
||||
dim? : Bool = false,
|
||||
italic? : Bool = false,
|
||||
underline? : Bool = false,
|
||||
) -> TextStyle {
|
||||
{ fg, bg, bold, dim, italic, underline }
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct StyleTagRegistry {
|
||||
entries : Map[String, TextStyle]
|
||||
}
|
||||
|
||||
///|
|
||||
fn normalize_style_tag_name(name : String) -> String {
|
||||
name.trim().to_lower().to_owned()
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn style_tag_registry() -> StyleTagRegistry {
|
||||
{ entries: {} }
|
||||
}
|
||||
|
||||
///|
|
||||
fn merge_text_style(base : TextStyle, overlay : TextStyle) -> TextStyle {
|
||||
{
|
||||
fg: match overlay.fg {
|
||||
@@ -66,91 +76,113 @@ fn merge_text_style(base : TextStyle, overlay : TextStyle) -> TextStyle {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn StyleTagRegistry::set_tag(
|
||||
self : StyleTagRegistry,
|
||||
name : String,
|
||||
style~ : TextStyle = text_style(),
|
||||
fg~ : String? = None,
|
||||
bg~ : String? = None,
|
||||
bold~ : Bool = false,
|
||||
dim~ : Bool = false,
|
||||
italic~ : Bool = false,
|
||||
underline~ : Bool = false,
|
||||
style? : TextStyle = text_style(),
|
||||
fg? : String? = None,
|
||||
bg? : String? = None,
|
||||
bold? : Bool = false,
|
||||
dim? : Bool = false,
|
||||
italic? : Bool = false,
|
||||
underline? : Bool = false,
|
||||
) -> StyleTagRegistry {
|
||||
let overlay = text_style(fg=fg, bg=bg, bold=bold, dim=dim, italic=italic, underline=underline)
|
||||
self.entries[normalize_style_tag_name(name)] = merge_text_style(style, overlay)
|
||||
let overlay = text_style(fg~, bg~, bold~, dim~, italic~, underline~)
|
||||
self.entries[normalize_style_tag_name(name)] = merge_text_style(
|
||||
style, overlay,
|
||||
)
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn StyleTagRegistry::define_alias(
|
||||
self : StyleTagRegistry,
|
||||
name : String,
|
||||
target : String,
|
||||
) -> StyleTagRegistry {
|
||||
match self.get(target) {
|
||||
Some(style) => self.set_tag(name, style=style)
|
||||
None => match global_style_tag_registry().get(target) {
|
||||
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=style)
|
||||
None => self
|
||||
Some(style) => self.set_tag(name, style~)
|
||||
None =>
|
||||
match global_style_tag_registry().get(target) {
|
||||
Some(style) => self.set_tag(name, style~)
|
||||
None =>
|
||||
match builtin_text_style_for_tag(normalize_style_tag_name(target)) {
|
||||
Some(style) => self.set_tag(name, style~)
|
||||
None => self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn default_style_tag_registry() -> StyleTagRegistry {
|
||||
style_tag_registry()
|
||||
.set_tag("black", fg=Some("black"))
|
||||
.set_tag("red", fg=Some("red"))
|
||||
.set_tag("green", fg=Some("green"))
|
||||
.set_tag("yellow", fg=Some("yellow"))
|
||||
.set_tag("blue", fg=Some("blue"))
|
||||
.set_tag("magenta", fg=Some("magenta"))
|
||||
.set_tag("cyan", fg=Some("cyan"))
|
||||
.set_tag("white", fg=Some("white"))
|
||||
.set_tag("bright_black", fg=Some("bright_black"))
|
||||
.set_tag("bright_red", fg=Some("bright_red"))
|
||||
.set_tag("bright_green", fg=Some("bright_green"))
|
||||
.set_tag("bright_yellow", fg=Some("bright_yellow"))
|
||||
.set_tag("bright_blue", fg=Some("bright_blue"))
|
||||
.set_tag("bright_magenta", fg=Some("bright_magenta"))
|
||||
.set_tag("bright_cyan", fg=Some("bright_cyan"))
|
||||
.set_tag("bright_white", fg=Some("bright_white"))
|
||||
.set_tag("accent", fg=Some("bright_cyan"), bold=true)
|
||||
.set_tag("info", fg=Some("cyan"))
|
||||
.set_tag("success", fg=Some("green"), bold=true)
|
||||
.set_tag("warning", fg=Some("yellow"), bold=true)
|
||||
.set_tag("danger", fg=Some("red"), bold=true)
|
||||
.set_tag("muted", fg=Some("bright_black"), dim=true)
|
||||
.set_tag("b", bold=true)
|
||||
.set_tag("dim", dim=true)
|
||||
.set_tag("i", italic=true)
|
||||
.set_tag("u", underline=true)
|
||||
.set_tag("black", fg=Some("black"))
|
||||
.set_tag("red", fg=Some("red"))
|
||||
.set_tag("green", fg=Some("green"))
|
||||
.set_tag("yellow", fg=Some("yellow"))
|
||||
.set_tag("blue", fg=Some("blue"))
|
||||
.set_tag("magenta", fg=Some("magenta"))
|
||||
.set_tag("cyan", fg=Some("cyan"))
|
||||
.set_tag("white", fg=Some("white"))
|
||||
.set_tag("bright_black", fg=Some("bright_black"))
|
||||
.set_tag("bright_red", fg=Some("bright_red"))
|
||||
.set_tag("bright_green", fg=Some("bright_green"))
|
||||
.set_tag("bright_yellow", fg=Some("bright_yellow"))
|
||||
.set_tag("bright_blue", fg=Some("bright_blue"))
|
||||
.set_tag("bright_magenta", fg=Some("bright_magenta"))
|
||||
.set_tag("bright_cyan", fg=Some("bright_cyan"))
|
||||
.set_tag("bright_white", fg=Some("bright_white"))
|
||||
.set_tag("accent", fg=Some("bright_cyan"), bold=true)
|
||||
.set_tag("info", fg=Some("cyan"))
|
||||
.set_tag("success", fg=Some("green"), bold=true)
|
||||
.set_tag("warning", fg=Some("yellow"), bold=true)
|
||||
.set_tag("danger", fg=Some("red"), bold=true)
|
||||
.set_tag("muted", fg=Some("bright_black"), dim=true)
|
||||
.set_tag("b", bold=true)
|
||||
.set_tag("dim", dim=true)
|
||||
.set_tag("i", italic=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 {
|
||||
global_style_tag_registry_ref.val
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn set_global_style_tag_registry(registry : StyleTagRegistry) -> Unit {
|
||||
global_style_tag_registry_ref.val = registry
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn reset_global_style_tag_registry() -> Unit {
|
||||
global_style_tag_registry_ref.val = style_tag_registry()
|
||||
}
|
||||
|
||||
///|
|
||||
priv struct InlineStyle {
|
||||
fg_code : String?
|
||||
bg_code : String?
|
||||
@@ -162,24 +194,29 @@ priv struct InlineStyle {
|
||||
underline : Bool
|
||||
}
|
||||
|
||||
///|
|
||||
priv struct StyledSegment {
|
||||
text : String
|
||||
style : InlineStyle
|
||||
}
|
||||
|
||||
///|
|
||||
priv struct StyleFrame {
|
||||
tag : String
|
||||
style : InlineStyle
|
||||
}
|
||||
|
||||
///|
|
||||
fn code_unit(ch : Char) -> Int {
|
||||
ch.to_int()
|
||||
}
|
||||
|
||||
///|
|
||||
fn code_unit16(ch : UInt16) -> Int {
|
||||
ch.to_int()
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct TextFormatter {
|
||||
show_timestamp : Bool
|
||||
show_level : Bool
|
||||
@@ -196,20 +233,21 @@ pub struct TextFormatter {
|
||||
style_tags : StyleTagRegistry?
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn text_formatter(
|
||||
show_timestamp~ : Bool = true,
|
||||
show_level~ : Bool = true,
|
||||
show_target~ : Bool = true,
|
||||
show_fields~ : Bool = true,
|
||||
separator~ : String = " ",
|
||||
field_separator~ : String = " ",
|
||||
template~ : String = "",
|
||||
color_mode~ : ColorMode = ColorMode::Never,
|
||||
color_support~ : ColorSupport = ColorSupport::TrueColor,
|
||||
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
||||
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
style_tags~ : StyleTagRegistry? = None,
|
||||
show_timestamp? : Bool = true,
|
||||
show_level? : Bool = true,
|
||||
show_target? : Bool = true,
|
||||
show_fields? : Bool = true,
|
||||
separator? : String = " ",
|
||||
field_separator? : String = " ",
|
||||
template? : String = "",
|
||||
color_mode? : ColorMode = ColorMode::Never,
|
||||
color_support? : ColorSupport = ColorSupport::TrueColor,
|
||||
style_markup? : StyleMarkupMode = StyleMarkupMode::Full,
|
||||
target_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
fields_style_markup? : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||
style_tags? : StyleTagRegistry? = None,
|
||||
) -> TextFormatter {
|
||||
{
|
||||
show_timestamp,
|
||||
@@ -228,6 +266,7 @@ pub fn text_formatter(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn color_support_label(support : ColorSupport) -> String {
|
||||
match support {
|
||||
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(
|
||||
show_timestamp=self.show_timestamp,
|
||||
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,
|
||||
template=self.template,
|
||||
color_mode=self.color_mode,
|
||||
color_support=color_support,
|
||||
color_support~,
|
||||
style_markup=self.style_markup,
|
||||
target_style_markup=self.target_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 {
|
||||
match mode {
|
||||
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(
|
||||
show_timestamp=self.show_timestamp,
|
||||
show_level=self.show_level,
|
||||
@@ -272,17 +320,21 @@ pub fn TextFormatter::with_style_markup(self : TextFormatter, style_markup : Sty
|
||||
template=self.template,
|
||||
color_mode=self.color_mode,
|
||||
color_support=self.color_support,
|
||||
style_markup=style_markup,
|
||||
style_markup~,
|
||||
target_style_markup=self.target_style_markup,
|
||||
fields_style_markup=self.fields_style_markup,
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn TextFormatter::with_target_style_markup(
|
||||
self : TextFormatter,
|
||||
style_markup : StyleMarkupMode,
|
||||
@@ -304,6 +356,7 @@ pub fn TextFormatter::with_target_style_markup(
|
||||
)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn TextFormatter::with_fields_style_markup(
|
||||
self : TextFormatter,
|
||||
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(
|
||||
show_timestamp=self.show_timestamp,
|
||||
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 {
|
||||
match mode {
|
||||
ColorMode::Never => "never"
|
||||
@@ -351,17 +409,20 @@ pub fn color_mode_label(mode : ColorMode) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn use_ansi_color(mode : ColorMode) -> Bool {
|
||||
match mode {
|
||||
ColorMode::Never => false
|
||||
ColorMode::Always => true
|
||||
ColorMode::Auto => match @env.get_env_var("NO_COLOR") {
|
||||
Some(_) => false
|
||||
None => true
|
||||
}
|
||||
ColorMode::Auto =>
|
||||
match @env.get_env_var("NO_COLOR") {
|
||||
Some(_) => false
|
||||
None => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn ansi_wrap(text : String, code : String, enabled : Bool) -> String {
|
||||
if !enabled || 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 == "" {
|
||||
text
|
||||
} else {
|
||||
@@ -404,6 +470,7 @@ fn ansi_wrap_with_style(text : String, style : InlineStyle, enabled : Bool) -> S
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn default_inline_style() -> InlineStyle {
|
||||
{
|
||||
fg_code: None,
|
||||
@@ -417,6 +484,7 @@ fn default_inline_style() -> InlineStyle {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn named_color_code(tag : String) -> String? {
|
||||
match tag {
|
||||
"black" => Some("30")
|
||||
@@ -439,6 +507,7 @@ fn named_color_code(tag : String) -> String? {
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn named_bg_color_code(tag : String) -> String? {
|
||||
match tag {
|
||||
"black" => Some("40")
|
||||
@@ -461,6 +530,7 @@ fn named_bg_color_code(tag : 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 g = hex_pair_to_int(value.unsafe_get(3), value.unsafe_get(4))
|
||||
@@ -495,18 +565,17 @@ fn basic_name_from_hex(value : String) -> String {
|
||||
} else {
|
||||
"green"
|
||||
}
|
||||
} else if r > 96 && g < 96 {
|
||||
"magenta"
|
||||
} else if g > 96 && r < 96 {
|
||||
"cyan"
|
||||
} else {
|
||||
if r > 96 && g < 96 {
|
||||
"magenta"
|
||||
} else if g > 96 && r < 96 {
|
||||
"cyan"
|
||||
} else {
|
||||
"blue"
|
||||
}
|
||||
"blue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn resolve_inline_color_code(
|
||||
formatter : TextFormatter,
|
||||
basic_name : String?,
|
||||
@@ -514,21 +583,25 @@ fn resolve_inline_color_code(
|
||||
) -> String {
|
||||
match formatter.color_support {
|
||||
ColorSupport::TrueColor => truecolor_code
|
||||
ColorSupport::Basic => match basic_name {
|
||||
Some(name) => named_code_from_basic_name(name).unwrap_or(truecolor_code)
|
||||
None => truecolor_code
|
||||
}
|
||||
ColorSupport::Basic =>
|
||||
match basic_name {
|
||||
Some(name) => named_code_from_basic_name(name).unwrap_or(truecolor_code)
|
||||
None => truecolor_code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn named_code_from_basic_name(name : String) -> String? {
|
||||
named_color_code(name)
|
||||
}
|
||||
|
||||
///|
|
||||
fn named_bg_code_from_basic_name(name : String) -> String? {
|
||||
named_bg_color_code(name)
|
||||
}
|
||||
|
||||
///|
|
||||
fn resolve_inline_bg_code(
|
||||
formatter : TextFormatter,
|
||||
basic_name : String?,
|
||||
@@ -536,13 +609,16 @@ fn resolve_inline_bg_code(
|
||||
) -> String {
|
||||
match formatter.color_support {
|
||||
ColorSupport::TrueColor => truecolor_code
|
||||
ColorSupport::Basic => match basic_name {
|
||||
Some(name) => named_bg_code_from_basic_name(name).unwrap_or(truecolor_code)
|
||||
None => truecolor_code
|
||||
}
|
||||
ColorSupport::Basic =>
|
||||
match basic_name {
|
||||
Some(name) =>
|
||||
named_bg_code_from_basic_name(name).unwrap_or(truecolor_code)
|
||||
None => truecolor_code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn is_hex_color(value : String) -> Bool {
|
||||
if value.length() != 7 {
|
||||
return false
|
||||
@@ -562,6 +638,7 @@ fn is_hex_color(value : String) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
///|
|
||||
fn hex_to_int(ch : UInt16) -> Int {
|
||||
let code = code_unit16(ch)
|
||||
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 {
|
||||
hex_to_int(high) * 16 + hex_to_int(low)
|
||||
}
|
||||
|
||||
///|
|
||||
fn rgb_fg_code(value : String) -> String {
|
||||
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))
|
||||
@@ -584,6 +663,7 @@ fn rgb_fg_code(value : String) -> String {
|
||||
"38;2;\{r};\{g};\{b}"
|
||||
}
|
||||
|
||||
///|
|
||||
fn rgb_bg_code(value : String) -> String {
|
||||
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))
|
||||
@@ -591,6 +671,7 @@ fn rgb_bg_code(value : String) -> String {
|
||||
"48;2;\{r};\{g};\{b}"
|
||||
}
|
||||
|
||||
///|
|
||||
fn rgb_bg_code_from_hex(value : String) -> String {
|
||||
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))
|
||||
@@ -598,6 +679,7 @@ fn rgb_bg_code_from_hex(value : String) -> String {
|
||||
"48;2;\{r};\{g};\{b}"
|
||||
}
|
||||
|
||||
///|
|
||||
fn inline_style_from_text_style(
|
||||
base : InlineStyle,
|
||||
style : TextStyle,
|
||||
@@ -609,15 +691,24 @@ fn inline_style_from_text_style(
|
||||
let normalized = normalize_style_tag_name(value)
|
||||
match named_color_code(normalized) {
|
||||
Some(code) => Some((Some(code), Some(normalized)))
|
||||
None => if is_hex_color(value) {
|
||||
let basic_name = basic_name_from_hex(value)
|
||||
Some((
|
||||
Some(resolve_inline_color_code(formatter, Some(basic_name), rgb_fg_code(value))),
|
||||
Some(basic_name),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None =>
|
||||
if is_hex_color(value) {
|
||||
let basic_name = basic_name_from_hex(value)
|
||||
Some(
|
||||
(
|
||||
Some(
|
||||
resolve_inline_color_code(
|
||||
formatter,
|
||||
Some(basic_name),
|
||||
rgb_fg_code(value),
|
||||
),
|
||||
),
|
||||
Some(basic_name),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,36 +725,48 @@ fn inline_style_from_text_style(
|
||||
}
|
||||
Some((Some(code), Some(bg_name)))
|
||||
}
|
||||
None => if is_hex_color(value) {
|
||||
let basic_name = basic_name_from_hex(value)
|
||||
Some((
|
||||
Some(resolve_inline_bg_code(formatter, Some(basic_name), rgb_bg_code_from_hex(value))),
|
||||
Some(basic_name),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None =>
|
||||
if is_hex_color(value) {
|
||||
let basic_name = basic_name_from_hex(value)
|
||||
Some(
|
||||
(
|
||||
Some(
|
||||
resolve_inline_bg_code(
|
||||
formatter,
|
||||
Some(basic_name),
|
||||
rgb_bg_code_from_hex(value),
|
||||
),
|
||||
),
|
||||
Some(basic_name),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match fg {
|
||||
Some((next_fg_code, next_fg_name)) => match bg {
|
||||
Some((next_bg_code, next_bg_name)) => Some({
|
||||
fg_code: next_fg_code,
|
||||
bg_code: next_bg_code,
|
||||
fg_basic_name: next_fg_name,
|
||||
bg_basic_name: next_bg_name,
|
||||
bold: base.bold || style.bold,
|
||||
dim: base.dim || style.dim,
|
||||
italic: base.italic || style.italic,
|
||||
underline: base.underline || style.underline,
|
||||
})
|
||||
None => None
|
||||
}
|
||||
Some((next_fg_code, next_fg_name)) =>
|
||||
match bg {
|
||||
Some((next_bg_code, next_bg_name)) =>
|
||||
Some({
|
||||
fg_code: next_fg_code,
|
||||
bg_code: next_bg_code,
|
||||
fg_basic_name: next_fg_name,
|
||||
bg_basic_name: next_bg_name,
|
||||
bold: base.bold || style.bold,
|
||||
dim: base.dim || style.dim,
|
||||
italic: base.italic || style.italic,
|
||||
underline: base.underline || style.underline,
|
||||
})
|
||||
None => None
|
||||
}
|
||||
None => None
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn builtin_text_style_for_tag(tag : String) -> TextStyle? {
|
||||
match normalize_style_tag_name(tag) {
|
||||
"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)
|
||||
match formatter.style_markup {
|
||||
StyleMarkupMode::Builtin => return builtin_text_style_for_tag(normalized)
|
||||
_ => ()
|
||||
}
|
||||
match formatter.style_tags {
|
||||
Some(registry) => match registry.get(normalized) {
|
||||
Some(style) => Some(style)
|
||||
None => match global_style_tag_registry().get(normalized) {
|
||||
Some(registry) =>
|
||||
match registry.get(normalized) {
|
||||
Some(style) => Some(style)
|
||||
None =>
|
||||
match global_style_tag_registry().get(normalized) {
|
||||
Some(style) => Some(style)
|
||||
None => builtin_text_style_for_tag(normalized)
|
||||
}
|
||||
}
|
||||
None =>
|
||||
match global_style_tag_registry().get(normalized) {
|
||||
Some(style) => Some(style)
|
||||
None => builtin_text_style_for_tag(normalized)
|
||||
}
|
||||
}
|
||||
None => match global_style_tag_registry().get(normalized) {
|
||||
Some(style) => Some(style)
|
||||
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)
|
||||
if is_hex_color(tag) {
|
||||
let basic_name = basic_name_from_hex(tag)
|
||||
Some({
|
||||
..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),
|
||||
})
|
||||
} 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())
|
||||
Some({
|
||||
..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),
|
||||
})
|
||||
} else {
|
||||
@@ -741,6 +866,7 @@ fn apply_inline_tag(style : InlineStyle, tag : String, formatter : TextFormatter
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn push_plain_segment(
|
||||
segments : Array[StyledSegment],
|
||||
buffer : StringBuilder,
|
||||
@@ -753,6 +879,7 @@ fn push_plain_segment(
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn pop_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
|
||||
let normalized = normalize_style_tag_name(tag)
|
||||
if stack.length() <= 1 {
|
||||
@@ -769,6 +896,7 @@ fn pop_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
///|
|
||||
fn has_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
|
||||
let normalized = normalize_style_tag_name(tag)
|
||||
if stack.length() <= 1 {
|
||||
@@ -782,7 +910,11 @@ fn has_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
|
||||
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 buffer = StringBuilder::new()
|
||||
let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }]
|
||||
@@ -845,7 +977,12 @@ fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[Style
|
||||
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 {
|
||||
StyleMarkupMode::Disabled => return text
|
||||
_ => ()
|
||||
@@ -860,10 +997,12 @@ fn render_styled_text(text : String, formatter : TextFormatter, mode : StyleMark
|
||||
out.to_string()
|
||||
}
|
||||
|
||||
///|
|
||||
fn render_inline_markup(message : String, formatter : TextFormatter) -> String {
|
||||
render_styled_text(message, formatter, formatter.style_markup)
|
||||
}
|
||||
|
||||
///|
|
||||
fn level_ansi_code(level : @core.Level) -> String {
|
||||
match level {
|
||||
@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 {
|
||||
let obj : Map[String, Json] = {}
|
||||
for item in fields {
|
||||
@@ -882,58 +1022,89 @@ fn fields_to_json(fields : Array[@core.Field]) -> Json {
|
||||
Json::object(obj)
|
||||
}
|
||||
|
||||
///|
|
||||
fn timestamp_text(rec : @core.Record, formatter : TextFormatter) -> String {
|
||||
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 {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn level_text(rec : @core.Record, formatter : TextFormatter) -> String {
|
||||
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 {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn target_text(rec : @core.Record, formatter : TextFormatter) -> String {
|
||||
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))
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
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}"
|
||||
}
|
||||
|
||||
///|
|
||||
fn fields_text(rec : @core.Record, formatter : TextFormatter) -> String {
|
||||
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))
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
fn render_template(rec : @core.Record, formatter : TextFormatter) -> String {
|
||||
formatter.template
|
||||
.replace_all(old="{timestamp}", 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="{target}", new=target_text(rec, formatter))
|
||||
.replace_all(old="{message}", new=render_inline_markup(rec.message, formatter))
|
||||
.replace_all(old="{fields}", new=fields_text(rec, formatter))
|
||||
.trim()
|
||||
.to_owned()
|
||||
.replace_all(old="{timestamp}", 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="{target}", new=target_text(rec, formatter))
|
||||
.replace_all(
|
||||
old="{message}",
|
||||
new=render_inline_markup(rec.message, formatter),
|
||||
)
|
||||
.replace_all(old="{fields}", new=fields_text(rec, formatter))
|
||||
.trim()
|
||||
.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 != "" {
|
||||
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 {
|
||||
let obj : Map[String, Json] = {
|
||||
"level": Json::string(rec.level.label()),
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
"Nanaloveyuki/BitLogger/src/core" @core,
|
||||
"maria/json_parser" @json_parser,
|
||||
"moonbitlang/core/env" @env,
|
||||
"Nanaloveyuki/BitLogger/src/core",
|
||||
"maria/json_parser",
|
||||
"moonbitlang/core/env",
|
||||
"moonbitlang/core/json",
|
||||
"moonbitlang/core/ref",
|
||||
}
|
||||
|
||||
+33
-22
@@ -1,21 +1,22 @@
|
||||
///|
|
||||
pub type RecordPatch = (@core.Record) -> @core.Record
|
||||
|
||||
///|
|
||||
pub fn identity_patch() -> RecordPatch {
|
||||
fn(rec) { rec }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn set_target(target : String) -> RecordPatch {
|
||||
fn(rec) {
|
||||
rec.with_target(target)
|
||||
}
|
||||
fn(rec) { rec.with_target(target) }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn prefix_message(prefix : String) -> RecordPatch {
|
||||
fn(rec) {
|
||||
rec.with_message("\{prefix}\{rec.message}")
|
||||
}
|
||||
fn(rec) { rec.with_message("\{prefix}\{rec.message}") }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn append_fields(extra_fields : Array[@core.Field]) -> RecordPatch {
|
||||
fn(rec) {
|
||||
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) {
|
||||
rec.with_fields(rec.fields.map(fn(field) {
|
||||
if field.key == key {
|
||||
field.with_value(placeholder)
|
||||
} else {
|
||||
field
|
||||
}
|
||||
}))
|
||||
rec.with_fields(
|
||||
rec.fields.map(fn(field) {
|
||||
if field.key == key {
|
||||
field.with_value(placeholder)
|
||||
} else {
|
||||
field
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redact_fields(keys : Array[String], placeholder~ : String = "***") -> RecordPatch {
|
||||
///|
|
||||
pub fn redact_fields(
|
||||
keys : Array[String],
|
||||
placeholder? : String = "***",
|
||||
) -> RecordPatch {
|
||||
fn(rec) {
|
||||
rec.with_fields(rec.fields.map(fn(field) {
|
||||
if keys.contains(field.key) {
|
||||
field.with_value(placeholder)
|
||||
} else {
|
||||
field
|
||||
}
|
||||
}))
|
||||
rec.with_fields(
|
||||
rec.fields.map(fn(field) {
|
||||
if keys.contains(field.key) {
|
||||
field.with_value(placeholder)
|
||||
} else {
|
||||
field
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn compose_patches(patches : Array[RecordPatch]) -> RecordPatch {
|
||||
fn(rec) {
|
||||
let mut current = rec
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
///|
|
||||
pub struct RuntimeFileState {
|
||||
file : FileSinkState
|
||||
queued : Bool
|
||||
@@ -5,16 +6,20 @@ pub struct RuntimeFileState {
|
||||
dropped_count : Int
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn RuntimeFileState::new(
|
||||
file : FileSinkState,
|
||||
queued~ : Bool = false,
|
||||
pending_count~ : Int = 0,
|
||||
dropped_count~ : Int = 0,
|
||||
queued? : Bool = false,
|
||||
pending_count? : Int = 0,
|
||||
dropped_count? : Int = 0,
|
||||
) -> RuntimeFileState {
|
||||
{ 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] = {
|
||||
"append": @json_parser.JsonValue::Bool(policy.append),
|
||||
"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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if pretty {
|
||||
@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] = {
|
||||
"path": @json_parser.JsonValue::String(state.path),
|
||||
"available": @json_parser.JsonValue::Bool(state.available),
|
||||
"append": @json_parser.JsonValue::Bool(state.append),
|
||||
"auto_flush": @json_parser.JsonValue::Bool(state.auto_flush),
|
||||
"open_failures": @json_parser.JsonValue::Number(state.open_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()),
|
||||
"open_failures": @json_parser.JsonValue::Number(
|
||||
state.open_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 {
|
||||
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)
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue {
|
||||
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)
|
||||
if pretty {
|
||||
@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({
|
||||
"file": file_sink_state_to_json_value(state.file),
|
||||
"queued": @json_parser.JsonValue::Bool(state.queued),
|
||||
"pending_count": @json_parser.JsonValue::Number(state.pending_count.to_double()),
|
||||
"dropped_count": @json_parser.JsonValue::Number(state.dropped_count.to_double()),
|
||||
"pending_count": @json_parser.JsonValue::Number(
|
||||
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)
|
||||
if pretty {
|
||||
@json_parser.stringify_pretty(value, 2)
|
||||
|
||||
+29
-14
@@ -1,8 +1,10 @@
|
||||
///|
|
||||
pub struct FileRotation {
|
||||
max_bytes : Int
|
||||
max_backups : Int
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct FileSinkState {
|
||||
path : String
|
||||
available : Bool
|
||||
@@ -15,30 +17,33 @@ pub struct FileSinkState {
|
||||
rotation_failures : Int
|
||||
}
|
||||
|
||||
///|
|
||||
pub struct FileSinkPolicy {
|
||||
append : Bool
|
||||
auto_flush : Bool
|
||||
rotation : FileRotation?
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSinkPolicy::new(
|
||||
append~ : Bool = true,
|
||||
auto_flush~ : Bool = true,
|
||||
rotation~ : FileRotation? = None,
|
||||
append? : Bool = true,
|
||||
auto_flush? : Bool = true,
|
||||
rotation? : FileRotation? = None,
|
||||
) -> FileSinkPolicy {
|
||||
{ append, auto_flush, rotation }
|
||||
}
|
||||
|
||||
///|
|
||||
pub fn FileSinkState::new(
|
||||
path : String,
|
||||
available~ : Bool = false,
|
||||
append~ : Bool = true,
|
||||
auto_flush~ : Bool = true,
|
||||
rotation~ : FileRotation? = None,
|
||||
open_failures~ : Int = 0,
|
||||
write_failures~ : Int = 0,
|
||||
flush_failures~ : Int = 0,
|
||||
rotation_failures~ : Int = 0,
|
||||
available? : Bool = false,
|
||||
append? : Bool = true,
|
||||
auto_flush? : Bool = true,
|
||||
rotation? : FileRotation? = None,
|
||||
open_failures? : Int = 0,
|
||||
write_failures? : Int = 0,
|
||||
flush_failures? : Int = 0,
|
||||
rotation_failures? : Int = 0,
|
||||
) -> FileSinkState {
|
||||
{
|
||||
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_backups: if max_backups <= 0 { 1 } else { max_backups },
|
||||
max_bytes: if max_bytes <= 0 {
|
||||
1
|
||||
} else {
|
||||
max_bytes
|
||||
},
|
||||
max_backups: if max_backups <= 0 {
|
||||
1
|
||||
} else {
|
||||
max_backups
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
///|
|
||||
pub(all) enum QueueOverflowPolicy {
|
||||
DropNewest
|
||||
DropOldest
|
||||
|
||||
Reference in New Issue
Block a user