From 3c15d8ed134d01b6f35fa57c898ef3d1d7c9e659 Mon Sep 17 00:00:00 2001 From: Nanaloveyuki Date: Wed, 20 May 2026 09:40:21 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20logger=20config=20presets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/presets.mbt | 103 +++++++++++++++++++++++++++++++++++++ src/presets_test.mbt | 118 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 src/presets.mbt create mode 100644 src/presets_test.mbt diff --git a/src/presets.mbt b/src/presets.mbt new file mode 100644 index 0000000..585abf9 --- /dev/null +++ b/src/presets.mbt @@ -0,0 +1,103 @@ +pub fn console( + min_level~ : Level = Level::Info, + target~ : String = "", + timestamp~ : Bool = false, +) -> LoggerConfig { + LoggerConfig::new( + min_level=min_level, + target=target, + timestamp=timestamp, + sink=SinkConfig::new(kind=SinkKind::Console), + ) +} + +pub fn json_console( + min_level~ : Level = Level::Info, + target~ : String = "", + timestamp~ : Bool = false, +) -> LoggerConfig { + LoggerConfig::new( + min_level=min_level, + target=target, + timestamp=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(), +) -> LoggerConfig { + LoggerConfig::new( + min_level=min_level, + target=target, + timestamp=timestamp, + sink=SinkConfig::new(kind=SinkKind::TextConsole, text_formatter=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(), +) -> LoggerConfig { + LoggerConfig::new( + min_level=min_level, + target=target, + timestamp=timestamp, + sink=SinkConfig::new( + kind=SinkKind::File, + path=path, + append=append, + auto_flush=auto_flush, + rotation=rotation, + text_formatter=text_formatter, + ), + ) +} + +pub fn with_queue( + config : LoggerConfig, + 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)), + ) +} + +pub fn with_file_rotation( + config : LoggerConfig, + max_bytes : Int, + max_backups~ : Int = 1, +) -> LoggerConfig { + match config.sink.kind { + SinkKind::File => + LoggerConfig::new( + min_level=config.min_level, + target=config.target, + timestamp=config.timestamp, + sink=SinkConfig::new( + kind=config.sink.kind, + path=config.sink.path, + append=config.sink.append, + auto_flush=config.sink.auto_flush, + rotation=Some(file_rotation(max_bytes, max_backups=max_backups)), + text_formatter=config.sink.text_formatter, + ), + queue=config.queue, + ) + _ => config + } +} diff --git a/src/presets_test.mbt b/src/presets_test.mbt new file mode 100644 index 0000000..a9f5192 --- /dev/null +++ b/src/presets_test.mbt @@ -0,0 +1,118 @@ +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(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(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(text_config.sink.text_formatter.show_timestamp, content="true") + 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(config.sink.path, content="bitlogger.log") + inspect(config.sink.append, content="true") + inspect(config.sink.auto_flush, content="true") + inspect(config.sink.rotation is None, content="true") + inspect(config.queue is None, content="true") +} + +test "file preset preserves empty path as configured" { + let config = file("") + inspect(match config.sink.kind { + SinkKind::File => "File" + _ => "other" + }, content="File") + inspect(config.sink.path, content="") + inspect(config.sink.rotation is None, content="true") +} + +test "preset helpers compose queue and file rotation without losing config" { + let formatter = TextFormatterConfig::new(show_timestamp=false, separator=" | ") + let config = file( + "service.log", + min_level=Level::Warn, + target="svc.worker", + timestamp=true, + append=false, + auto_flush=false, + text_formatter=formatter, + ) + let config = with_file_rotation( + with_queue(config, max_pending=32, overflow=QueueOverflowPolicy::DropOldest), + 128, + max_backups=3, + ) + inspect(config.min_level.label(), content="WARN") + inspect(config.target, content="svc.worker") + inspect(config.timestamp, content="true") + inspect(config.sink.path, content="service.log") + inspect(config.sink.append, content="false") + inspect(config.sink.auto_flush, content="false") + inspect(config.sink.text_formatter.show_timestamp, content="false") + inspect(config.sink.text_formatter.separator, content=" | ") + match config.queue { + Some(queue) => { + inspect(queue.max_pending, content="32") + inspect(match queue.overflow { + QueueOverflowPolicy::DropNewest => "DropNewest" + QueueOverflowPolicy::DropOldest => "DropOldest" + }, content="DropOldest") + } + None => inspect(false, content="true") + } + match config.sink.rotation { + Some(rotation) => { + inspect(rotation.max_bytes, content="128") + inspect(rotation.max_backups, content="3") + } + None => inspect(false, content="true") + } +} + +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") + 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=" :: ")), + 96, + max_backups=4, + ) + 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") +}