mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
✨ Add formatter style_tags config support
This commit is contained in:
@@ -212,6 +212,20 @@ ignore(logger.flush())
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details><summary>JSON style_tags 示例</summary>
|
||||||
|
|
||||||
|
```moonbit
|
||||||
|
let config = parse_logger_config_text(
|
||||||
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true}}}}}",
|
||||||
|
)
|
||||||
|
|
||||||
|
let logger = build_logger(config)
|
||||||
|
|
||||||
|
logger.info("<accent>styled from json</>")
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details><summary>native 文件 sink 示例</summary>
|
<details><summary>native 文件 sink 示例</summary>
|
||||||
|
|
||||||
```moonbit
|
```moonbit
|
||||||
@@ -266,7 +280,8 @@ match logger.file_runtime_state() {
|
|||||||
- `message` 支持轻量 inline style tag: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
- `message` 支持轻量 inline style tag: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
||||||
- 运行期样式标签 API: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
- 运行期样式标签 API: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
||||||
- 样式标签优先级: formatter 局部 `style_tags` > 全局 style tag registry > 内置标签
|
- 样式标签优先级: formatter 局部 `style_tags` > 全局 style tag registry > 内置标签
|
||||||
- 自定义 style tag 当前仅支持运行期 API, 还不支持通过 JSON config 声明
|
- `sink.text_formatter.style_tags` 现支持最小对象映射配置, 可声明 `fg`, `bg`, `bold`, `dim`, `italic`, `underline`
|
||||||
|
- `define_alias(...)` 仍为运行期 API, 当前不在 JSON schema 中
|
||||||
- `sink.rotation` 支持 `max_bytes` 与 `max_backups`, 用于基础 size-based rotation 和 backup retention
|
- `sink.rotation` 支持 `max_bytes` 与 `max_backups`, 用于基础 size-based rotation 和 backup retention
|
||||||
- `file_sink(...)` 提供 `reopen()`, `reopen_with_current_policy()`, `reopen_append()`, `reopen_truncate()`, `open_failures()`, `write_failures()`, `flush_failures()`, `rotation_failures()`, 用于基础可观测性
|
- `file_sink(...)` 提供 `reopen()`, `reopen_with_current_policy()`, `reopen_append()`, `reopen_truncate()`, `open_failures()`, `write_failures()`, `flush_failures()`, `rotation_failures()`, 用于基础可观测性
|
||||||
- `file_sink(...)` 提供 `append_mode()`. 显式传入 `reopen(append=...)` 时, 会更新后续 reopen 使用的 append 策略. `reopen_with_current_policy()` 使用当前保存的策略重开文件, `reopen_append()` / `reopen_truncate()` 提供常见的 append 和 truncate 模式
|
- `file_sink(...)` 提供 `append_mode()`. 显式传入 `reopen(append=...)` 时, 会更新后续 reopen 使用的 append 策略. `reopen_with_current_policy()` 使用当前保存的策略重开文件, `reopen_append()` / `reopen_truncate()` 提供常见的 append 和 truncate 模式
|
||||||
@@ -291,6 +306,7 @@ match logger.file_runtime_state() {
|
|||||||
- `file_sink_state_to_json(...)`, `stringify_file_sink_state(...)`, `runtime_file_state_to_json(...)`, `stringify_runtime_file_state(...)` 可直接把 file / queued-file 快照导出为 JSON, 便于排障或上报
|
- `file_sink_state_to_json(...)`, `stringify_file_sink_state(...)`, `runtime_file_state_to_json(...)`, `stringify_runtime_file_state(...)` 可直接把 file / queued-file 快照导出为 JSON, 便于排障或上报
|
||||||
- `sink.text_formatter.template` 支持固定 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
|
- `sink.text_formatter.template` 支持固定 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
|
||||||
- `sink.text_formatter.color_mode` 支持 `never`, `auto`, `always`
|
- `sink.text_formatter.color_mode` 支持 `never`, `auto`, `always`
|
||||||
|
- `sink.text_formatter.style_tags.<name>` 支持 `fg`, `bg`, `bold`, `dim`, `italic`, `underline`
|
||||||
- 可由配置直接组装的 sink 类型: `console`, `json_console`, `text_console`, `file`
|
- 可由配置直接组装的 sink 类型: `console`, `json_console`, `text_console`, `file`
|
||||||
- `queue` 作为显式包装层附着在最终 sink 外侧. 这仍然是同步 drain 模型, 不是 async runtime
|
- `queue` 作为显式包装层附着在最终 sink 外侧. 这仍然是同步 drain 模型, 不是 async runtime
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,28 @@ test "logger config parser reads formatter and queue options" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "logger config parser reads formatter style tags" {
|
||||||
|
let config = parse_logger_config_text(
|
||||||
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true},\"panel\":{\"bg\":\"#202020\",\"underline\":true}}}}}",
|
||||||
|
)
|
||||||
|
inspect(config.sink.text_formatter.style_tags.length(), content="2")
|
||||||
|
match config.sink.text_formatter.style_tags.get("accent") {
|
||||||
|
Some(style) => {
|
||||||
|
inspect(style.fg.unwrap(), content="#4cc9f0")
|
||||||
|
inspect(style.bold, content="true")
|
||||||
|
inspect(style.bg is None, content="true")
|
||||||
|
}
|
||||||
|
None => inspect(false, content="true")
|
||||||
|
}
|
||||||
|
match config.sink.text_formatter.style_tags.get("panel") {
|
||||||
|
Some(style) => {
|
||||||
|
inspect(style.bg.unwrap(), content="#202020")
|
||||||
|
inspect(style.underline, content="true")
|
||||||
|
}
|
||||||
|
None => inspect(false, content="true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "logger config parser reads file rotation options" {
|
test "logger config parser reads file rotation options" {
|
||||||
let config = parse_logger_config_text(
|
let config = parse_logger_config_text(
|
||||||
"{\"sink\":{\"kind\":\"file\",\"path\":\"bitlogger.log\",\"rotation\":{\"max_bytes\":128,\"max_backups\":3}}}",
|
"{\"sink\":{\"kind\":\"file\",\"path\":\"bitlogger.log\",\"rotation\":{\"max_bytes\":128,\"max_backups\":3}}}",
|
||||||
@@ -119,6 +141,30 @@ test "logger config stringify roundtrips stable fields" {
|
|||||||
inspect(config.sink.text_formatter.template, content="[{level}] {target} {message}")
|
inspect(config.sink.text_formatter.template, content="[{level}] {target} {message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "logger config stringify roundtrips formatter style tags" {
|
||||||
|
let text = stringify_logger_config(
|
||||||
|
LoggerConfig::new(
|
||||||
|
sink=SinkConfig::new(
|
||||||
|
kind=SinkKind::TextConsole,
|
||||||
|
text_formatter=TextFormatterConfig::new(
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
style_tags={
|
||||||
|
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
||||||
|
"panel": text_style(bg=Some("#202020"), dim=true),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
let config = parse_logger_config_text(text)
|
||||||
|
inspect(color_mode_label(config.sink.text_formatter.color_mode), content="always")
|
||||||
|
inspect(config.sink.text_formatter.style_tags.length(), content="2")
|
||||||
|
inspect(config.sink.text_formatter.style_tags.get("accent").unwrap().fg.unwrap(), content="#4cc9f0")
|
||||||
|
inspect(config.sink.text_formatter.style_tags.get("accent").unwrap().bold, content="true")
|
||||||
|
inspect(config.sink.text_formatter.style_tags.get("panel").unwrap().bg.unwrap(), content="#202020")
|
||||||
|
inspect(config.sink.text_formatter.style_tags.get("panel").unwrap().dim, content="true")
|
||||||
|
}
|
||||||
|
|
||||||
test "logger config stringify roundtrips file rotation fields" {
|
test "logger config stringify roundtrips file rotation fields" {
|
||||||
let text = stringify_logger_config(
|
let text = stringify_logger_config(
|
||||||
LoggerConfig::new(
|
LoggerConfig::new(
|
||||||
@@ -158,9 +204,12 @@ test "config subtype json helpers stringify stable shapes" {
|
|||||||
field_separator=",",
|
field_separator=",",
|
||||||
template="[{level}] {message} :: {fields}",
|
template="[{level}] {message} :: {fields}",
|
||||||
color_mode=ColorMode::Always,
|
color_mode=ColorMode::Always,
|
||||||
|
style_tags={
|
||||||
|
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content="{\"show_timestamp\":false,\"show_level\":true,\"show_target\":false,\"show_fields\":true,\"separator\":\" | \",\"field_separator\":\",\",\"template\":\"[{level}] {message} :: {fields}\",\"color_mode\":\"always\"}",
|
content="{\"show_timestamp\":false,\"show_level\":true,\"show_target\":false,\"show_fields\":true,\"separator\":\" | \",\"field_separator\":\",\",\"template\":\"[{level}] {message} :: {fields}\",\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"bold\":true,\"dim\":false,\"italic\":false,\"underline\":false,\"fg\":\"#4cc9f0\"}}}",
|
||||||
)
|
)
|
||||||
inspect(
|
inspect(
|
||||||
stringify_sink_config(
|
stringify_sink_config(
|
||||||
@@ -177,6 +226,22 @@ test "config subtype json helpers stringify stable shapes" {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "config formatter style tags render in built logger" {
|
||||||
|
let formatter = TextFormatterConfig::new(
|
||||||
|
show_timestamp=false,
|
||||||
|
show_target=false,
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
style_tags={
|
||||||
|
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
let rendered = format_text(
|
||||||
|
Record::new(Level::Info, "<accent>tag</>"),
|
||||||
|
formatter=formatter.to_formatter(),
|
||||||
|
)
|
||||||
|
inspect(rendered, content="[\u{001b}[32mINFO\u{001b}[0m] \u{001b}[38;2;76;201;240;1mtag\u{001b}[0m")
|
||||||
|
}
|
||||||
|
|
||||||
test "build logger from config supports queued text console" {
|
test "build logger from config supports queued text console" {
|
||||||
let logger = build_logger(
|
let logger = build_logger(
|
||||||
LoggerConfig::new(
|
LoggerConfig::new(
|
||||||
|
|||||||
+12
-1
@@ -217,6 +217,16 @@ test {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```mbt check
|
||||||
|
test {
|
||||||
|
let config = parse_logger_config_text(
|
||||||
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true}}}}}",
|
||||||
|
)
|
||||||
|
let logger = build_logger(config)
|
||||||
|
logger.info("<accent>styled from json</>")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Formatter Template / 模板格式
|
## Formatter Template / 模板格式
|
||||||
|
|
||||||
- supported tokens / 支持的 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
|
- supported tokens / 支持的 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
|
||||||
@@ -224,7 +234,8 @@ test {
|
|||||||
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
||||||
- runtime style tags / 运行期样式标签: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
- runtime style tags / 运行期样式标签: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
||||||
- style tag priority / 标签优先级: formatter local `style_tags` > global style tag registry > builtin tags
|
- style tag priority / 标签优先级: formatter local `style_tags` > global style tag registry > builtin tags
|
||||||
- custom style tags are runtime-only for now / 自定义样式标签当前仅支持运行期 API
|
- `sink.text_formatter.style_tags` / `sink.text_formatter.style_tags` 现支持最小对象映射: `fg`, `bg`, `bold`, `dim`, `italic`, `underline`
|
||||||
|
- `define_alias(...)` is still runtime-only / `define_alias(...)` 目前仍为运行期 API
|
||||||
- disabled or missing parts render as empty text / 被关闭或缺失的部分会渲染为空字符串
|
- disabled or missing parts render as empty text / 被关闭或缺失的部分会渲染为空字符串
|
||||||
- `template` is intentionally a simple token replacement layer, not a full DSL / `template` 使用轻量 token 替换方式, 不是完整 DSL
|
- `template` is intentionally a simple token replacement layer, not a full DSL / `template` 使用轻量 token 替换方式, 不是完整 DSL
|
||||||
|
|
||||||
|
|||||||
+89
-2
@@ -18,6 +18,7 @@ pub struct TextFormatterConfig {
|
|||||||
field_separator : String
|
field_separator : String
|
||||||
template : String
|
template : String
|
||||||
color_mode : ColorMode
|
color_mode : ColorMode
|
||||||
|
style_tags : Map[String, TextStyle]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn TextFormatterConfig::new(
|
pub fn TextFormatterConfig::new(
|
||||||
@@ -29,6 +30,7 @@ pub fn TextFormatterConfig::new(
|
|||||||
field_separator~ : String = " ",
|
field_separator~ : String = " ",
|
||||||
template~ : String = "",
|
template~ : String = "",
|
||||||
color_mode~ : ColorMode = ColorMode::Never,
|
color_mode~ : ColorMode = ColorMode::Never,
|
||||||
|
style_tags~ : Map[String, TextStyle] = {},
|
||||||
) -> TextFormatterConfig {
|
) -> TextFormatterConfig {
|
||||||
{
|
{
|
||||||
show_timestamp,
|
show_timestamp,
|
||||||
@@ -39,9 +41,18 @@ pub fn TextFormatterConfig::new(
|
|||||||
field_separator,
|
field_separator,
|
||||||
template,
|
template,
|
||||||
color_mode,
|
color_mode,
|
||||||
|
style_tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
registry
|
||||||
|
}
|
||||||
|
|
||||||
pub fn TextFormatterConfig::to_formatter(self : TextFormatterConfig) -> TextFormatter {
|
pub fn TextFormatterConfig::to_formatter(self : TextFormatterConfig) -> TextFormatter {
|
||||||
text_formatter(
|
text_formatter(
|
||||||
show_timestamp=self.show_timestamp,
|
show_timestamp=self.show_timestamp,
|
||||||
@@ -52,6 +63,11 @@ pub fn TextFormatterConfig::to_formatter(self : TextFormatterConfig) -> TextForm
|
|||||||
field_separator=self.field_separator,
|
field_separator=self.field_separator,
|
||||||
template=self.template,
|
template=self.template,
|
||||||
color_mode=self.color_mode,
|
color_mode=self.color_mode,
|
||||||
|
style_tags=if self.style_tags.length() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(style_tag_registry_from_config(self.style_tags))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,6 +775,19 @@ fn get_string(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_bool(
|
fn get_bool(
|
||||||
obj : Map[String, @json_parser.JsonValue],
|
obj : Map[String, @json_parser.JsonValue],
|
||||||
key : String,
|
key : String,
|
||||||
@@ -848,9 +877,37 @@ fn parse_text_formatter_config(value : @json_parser.JsonValue) -> TextFormatterC
|
|||||||
field_separator=get_string(obj, "field_separator", default=" "),
|
field_separator=get_string(obj, "field_separator", default=" "),
|
||||||
template=get_string(obj, "template", default=""),
|
template=get_string(obj, "template", default=""),
|
||||||
color_mode=parse_color_mode(get_string(obj, "color_mode", default="never")),
|
color_mode=parse_color_mode(get_string(obj, "color_mode", default="never")),
|
||||||
|
style_tags=match obj.get("style_tags") {
|
||||||
|
None => {}
|
||||||
|
Some(inner) => parse_style_tags_config(inner)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_text_style_config(
|
||||||
|
value : @json_parser.JsonValue,
|
||||||
|
context : String,
|
||||||
|
) -> TextStyle raise ConfigError {
|
||||||
|
let obj = expect_object(value, context)
|
||||||
|
text_style(
|
||||||
|
fg=get_optional_string(obj, "fg"),
|
||||||
|
bg=get_optional_string(obj, "bg"),
|
||||||
|
bold=get_bool(obj, "bold", default=false),
|
||||||
|
dim=get_bool(obj, "dim", default=false),
|
||||||
|
italic=get_bool(obj, "italic", default=false),
|
||||||
|
underline=get_bool(obj, "underline", default=false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_queue_config(value : @json_parser.JsonValue) -> QueueConfig raise ConfigError {
|
fn parse_queue_config(value : @json_parser.JsonValue) -> QueueConfig raise ConfigError {
|
||||||
let obj = expect_object(value, "queue")
|
let obj = expect_object(value, "queue")
|
||||||
QueueConfig::new(
|
QueueConfig::new(
|
||||||
@@ -934,7 +991,7 @@ 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 {
|
||||||
@json_parser.JsonValue::Object({
|
let obj : Map[String, @json_parser.JsonValue] = {
|
||||||
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp),
|
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp),
|
||||||
"show_level": @json_parser.JsonValue::Bool(config.show_level),
|
"show_level": @json_parser.JsonValue::Bool(config.show_level),
|
||||||
"show_target": @json_parser.JsonValue::Bool(config.show_target),
|
"show_target": @json_parser.JsonValue::Bool(config.show_target),
|
||||||
@@ -943,7 +1000,37 @@ pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_pars
|
|||||||
"field_separator": @json_parser.JsonValue::String(config.field_separator),
|
"field_separator": @json_parser.JsonValue::String(config.field_separator),
|
||||||
"template": @json_parser.JsonValue::String(config.template),
|
"template": @json_parser.JsonValue::String(config.template),
|
||||||
"color_mode": @json_parser.JsonValue::String(color_mode_label(config.color_mode)),
|
"color_mode": @json_parser.JsonValue::String(color_mode_label(config.color_mode)),
|
||||||
})
|
}
|
||||||
|
if config.style_tags.length() != 0 {
|
||||||
|
obj["style_tags"] = style_tags_config_to_json(config.style_tags)
|
||||||
|
}
|
||||||
|
@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),
|
||||||
|
"dim": @json_parser.JsonValue::Bool(style.dim),
|
||||||
|
"italic": @json_parser.JsonValue::Bool(style.italic),
|
||||||
|
"underline": @json_parser.JsonValue::Bool(style.underline),
|
||||||
|
}
|
||||||
|
match style.fg {
|
||||||
|
Some(value) => obj["fg"] = @json_parser.JsonValue::String(value)
|
||||||
|
None => ()
|
||||||
|
}
|
||||||
|
match style.bg {
|
||||||
|
Some(value) => obj["bg"] = @json_parser.JsonValue::String(value)
|
||||||
|
None => ()
|
||||||
|
}
|
||||||
|
@json_parser.JsonValue::Object(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
@json_parser.JsonValue::Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stringify_text_formatter_config(
|
pub fn stringify_text_formatter_config(
|
||||||
|
|||||||
+15
-1
@@ -196,6 +196,18 @@ logger.info("configured from json")
|
|||||||
ignore(logger.flush())
|
ignore(logger.flush())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
JSON `style_tags`:
|
||||||
|
|
||||||
|
```moonbit
|
||||||
|
let config = parse_logger_config_text(
|
||||||
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true}}}}}",
|
||||||
|
)
|
||||||
|
|
||||||
|
let logger = build_logger(config)
|
||||||
|
|
||||||
|
logger.info("<accent>styled from json</>")
|
||||||
|
```
|
||||||
|
|
||||||
Native file sink:
|
Native file sink:
|
||||||
|
|
||||||
```moonbit
|
```moonbit
|
||||||
@@ -248,7 +260,8 @@ match logger.file_runtime_state() {
|
|||||||
- `message` also supports lightweight inline style tags such as `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, and `<bg:#202020>...</>`.
|
- `message` also supports lightweight inline style tags such as `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, and `<bg:#202020>...</>`.
|
||||||
- Runtime style-tag APIs now include `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, and `define_alias(...)`.
|
- Runtime style-tag APIs now include `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, and `define_alias(...)`.
|
||||||
- Style-tag lookup priority is formatter-local `style_tags` > global style tag registry > builtin tags.
|
- Style-tag lookup priority is formatter-local `style_tags` > global style tag registry > builtin tags.
|
||||||
- Custom style tags are runtime-only for now and are not yet part of the JSON config schema.
|
- `sink.text_formatter.style_tags` now supports a minimal object mapping with `fg`, `bg`, `bold`, `dim`, `italic`, and `underline`.
|
||||||
|
- `define_alias(...)` remains a runtime-only API and is not yet part of the JSON config schema.
|
||||||
- `sink.rotation` currently supports `max_bytes` and `max_backups` for basic size-based rotation and backup retention.
|
- `sink.rotation` currently supports `max_bytes` and `max_backups` for basic size-based rotation and backup retention.
|
||||||
- `file_sink(...)` also exposes `reopen()`, `reopen_with_current_policy()`, `reopen_append()`, `reopen_truncate()`, `open_failures()`, `write_failures()`, `flush_failures()`, and `rotation_failures()` for basic observability.
|
- `file_sink(...)` also exposes `reopen()`, `reopen_with_current_policy()`, `reopen_append()`, `reopen_truncate()`, `open_failures()`, `write_failures()`, `flush_failures()`, and `rotation_failures()` for basic observability.
|
||||||
- `file_sink(...)` also exposes `append_mode()`. Passing `append=...` to `reopen(...)` updates the current append policy used by later reopen calls, `reopen_with_current_policy()` makes that stored-policy reopen path explicit, and `reopen_append()` / `reopen_truncate()` cover the two common policy switches directly.
|
- `file_sink(...)` also exposes `append_mode()`. Passing `append=...` to `reopen(...)` updates the current append policy used by later reopen calls, `reopen_with_current_policy()` makes that stored-policy reopen path explicit, and `reopen_append()` / `reopen_truncate()` cover the two common policy switches directly.
|
||||||
@@ -273,6 +286,7 @@ match logger.file_runtime_state() {
|
|||||||
- `file_sink_state_to_json(...)`, `stringify_file_sink_state(...)`, `runtime_file_state_to_json(...)`, and `stringify_runtime_file_state(...)` can export file and queued-file snapshots directly as JSON for diagnostics or reporting.
|
- `file_sink_state_to_json(...)`, `stringify_file_sink_state(...)`, `runtime_file_state_to_json(...)`, and `stringify_runtime_file_state(...)` can export file and queued-file snapshots directly as JSON for diagnostics or reporting.
|
||||||
- `sink.text_formatter.template` currently supports fixed tokens: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, and `{fields}`.
|
- `sink.text_formatter.template` currently supports fixed tokens: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, and `{fields}`.
|
||||||
- `sink.text_formatter.color_mode` currently supports `never`, `auto`, and `always`.
|
- `sink.text_formatter.color_mode` currently supports `never`, `auto`, and `always`.
|
||||||
|
- `sink.text_formatter.style_tags.<name>` currently supports `fg`, `bg`, `bold`, `dim`, `italic`, and `underline`.
|
||||||
- Config-driven sink assembly currently supports `console`, `json_console`, `text_console`, and `file`.
|
- Config-driven sink assembly currently supports `console`, `json_console`, `text_console`, and `file`.
|
||||||
- `queue` remains a synchronous bounded wrapper around the final sink, not an async runtime.
|
- `queue` remains a synchronous bounded wrapper around the final sink, not an async runtime.
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ version 0.4.0
|
|||||||
- feat: keep JSON formatter output unchanged and limit inline style parsing to text message rendering only
|
- feat: keep JSON formatter output unchanged and limit inline style parsing to text message rendering only
|
||||||
- feat: add `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, and `default_style_tag_registry()` for reusable inline style tags
|
- feat: add `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, and `default_style_tag_registry()` for reusable inline style tags
|
||||||
- feat: add formatter-local `style_tags`, global style tag registry helpers, builtin-tag override support, and alias reuse via `define_alias(...)`
|
- feat: add formatter-local `style_tags`, global style tag registry helpers, builtin-tag override support, and alias reuse via `define_alias(...)`
|
||||||
|
- feat: support minimal `sink.text_formatter.style_tags` JSON config parsing and serialization for custom formatter tag styles
|
||||||
|
|
||||||
### Test
|
### Test
|
||||||
|
|
||||||
@@ -22,10 +23,12 @@ version 0.4.0
|
|||||||
- test: cover named inline color tags in ANSI mode
|
- test: cover named inline color tags in ANSI mode
|
||||||
- test: cover plain mode tag stripping, nested tags, hex tags, and unknown-tag fallback behavior
|
- test: cover plain mode tag stripping, nested tags, hex tags, and unknown-tag fallback behavior
|
||||||
- test: cover custom tags, builtin-tag override, formatter-vs-global priority, global registry fallback, and alias reuse
|
- test: cover custom tags, builtin-tag override, formatter-vs-global priority, global registry fallback, and alias reuse
|
||||||
|
- test: cover formatter `style_tags` JSON parsing, config roundtrip, JSON helper export, and config-driven styled formatter rendering
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
- docs: add `color_mode` usage examples to formatter documentation
|
- docs: add `color_mode` usage examples to formatter documentation
|
||||||
|
- docs: add runtime and JSON `style_tags` examples, and document current JSON schema scope
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
@@ -33,3 +36,4 @@ version 0.4.0
|
|||||||
- inline style markup currently supports short close `</>` only
|
- inline style markup currently supports short close `</>` only
|
||||||
- unknown or invalid inline tags currently fall back to plain text and do not raise formatter errors
|
- unknown or invalid inline tags currently fall back to plain text and do not raise formatter errors
|
||||||
- formatter-local tag lookup currently takes precedence over global tag lookup, and global lookup takes precedence over builtin tags
|
- formatter-local tag lookup currently takes precedence over global tag lookup, and global lookup takes precedence over builtin tags
|
||||||
|
- JSON config currently supports concrete style objects only; alias-style declarations remain runtime-only
|
||||||
|
|||||||
Reference in New Issue
Block a user