mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
✨ Add configurable style markup modes
This commit is contained in:
@@ -197,6 +197,20 @@ logger.info("<accent>styled</> output and <danger>alert</>")
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details><summary>关闭 style markup 解析示例</summary>
|
||||||
|
|
||||||
|
```moonbit
|
||||||
|
let formatter = text_formatter(
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
).without_style_markup()
|
||||||
|
|
||||||
|
let logger = Logger::new(text_console_sink(formatter), target="raw")
|
||||||
|
|
||||||
|
logger.info("<red>kept as raw text</>")
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details><summary>JSON 配置加载示例</summary>
|
<details><summary>JSON 配置加载示例</summary>
|
||||||
|
|
||||||
```moonbit
|
```moonbit
|
||||||
@@ -226,6 +240,20 @@ logger.info("<accent>styled from json</>")
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details><summary>JSON style_markup 模式示例</summary>
|
||||||
|
|
||||||
|
```moonbit
|
||||||
|
let config = parse_logger_config_text(
|
||||||
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"disabled\"}}}",
|
||||||
|
)
|
||||||
|
|
||||||
|
let logger = build_logger(config)
|
||||||
|
|
||||||
|
logger.info("<red>still raw</>")
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details><summary>native 文件 sink 示例</summary>
|
<details><summary>native 文件 sink 示例</summary>
|
||||||
|
|
||||||
```moonbit
|
```moonbit
|
||||||
@@ -277,6 +305,7 @@ match logger.file_runtime_state() {
|
|||||||
- `QueueConfig`, `TextFormatterConfig`, `SinkConfig` 可分别通过 `queue_config_to_json(...)` / `stringify_queue_config(...)`, `text_formatter_config_to_json(...)` / `stringify_text_formatter_config(...)`, `sink_config_to_json(...)` / `stringify_sink_config(...)` 单独导出 JSON
|
- `QueueConfig`, `TextFormatterConfig`, `SinkConfig` 可分别通过 `queue_config_to_json(...)` / `stringify_queue_config(...)`, `text_formatter_config_to_json(...)` / `stringify_text_formatter_config(...)`, `sink_config_to_json(...)` / `stringify_sink_config(...)` 单独导出 JSON
|
||||||
- 支持字段: `min_level`, `target`, `timestamp`, `sink.kind`, `sink.path`, `sink.append`, `sink.auto_flush`, `sink.rotation`, `sink.text_formatter`, `queue`
|
- 支持字段: `min_level`, `target`, `timestamp`, `sink.kind`, `sink.path`, `sink.append`, `sink.auto_flush`, `sink.rotation`, `sink.text_formatter`, `queue`
|
||||||
- `TextFormatter` / `TextFormatterConfig` 提供 `color_mode = Never | Auto | Always`, 可控制 ANSI 文本着色
|
- `TextFormatter` / `TextFormatterConfig` 提供 `color_mode = Never | Auto | Always`, 可控制 ANSI 文本着色
|
||||||
|
- `TextFormatter` / `TextFormatterConfig` 提供 `style_markup = disabled | builtin | full`, 可决定是否解析 style markup 以及是否启用 custom style tag
|
||||||
- `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 > 内置标签
|
||||||
@@ -306,6 +335,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_markup` 支持 `disabled`, `builtin`, `full`
|
||||||
- `sink.text_formatter.style_tags.<name>` 支持 `fg`, `bg`, `bold`, `dim`, `italic`, `underline`
|
- `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
|
||||||
|
|||||||
@@ -74,8 +74,9 @@ test "logger config parser reads formatter and queue options" {
|
|||||||
|
|
||||||
test "logger config parser reads formatter style tags" {
|
test "logger config parser reads formatter style tags" {
|
||||||
let config = parse_logger_config_text(
|
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}}}}}",
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"builtin\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true},\"panel\":{\"bg\":\"#202020\",\"underline\":true}}}}}",
|
||||||
)
|
)
|
||||||
|
inspect(style_markup_mode_label(config.sink.text_formatter.style_markup), content="builtin")
|
||||||
inspect(config.sink.text_formatter.style_tags.length(), content="2")
|
inspect(config.sink.text_formatter.style_tags.length(), content="2")
|
||||||
match config.sink.text_formatter.style_tags.get("accent") {
|
match config.sink.text_formatter.style_tags.get("accent") {
|
||||||
Some(style) => {
|
Some(style) => {
|
||||||
@@ -148,6 +149,7 @@ test "logger config stringify roundtrips formatter style tags" {
|
|||||||
kind=SinkKind::TextConsole,
|
kind=SinkKind::TextConsole,
|
||||||
text_formatter=TextFormatterConfig::new(
|
text_formatter=TextFormatterConfig::new(
|
||||||
color_mode=ColorMode::Always,
|
color_mode=ColorMode::Always,
|
||||||
|
style_markup=StyleMarkupMode::Builtin,
|
||||||
style_tags={
|
style_tags={
|
||||||
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
||||||
"panel": text_style(bg=Some("#202020"), dim=true),
|
"panel": text_style(bg=Some("#202020"), dim=true),
|
||||||
@@ -158,6 +160,7 @@ test "logger config stringify roundtrips formatter style tags" {
|
|||||||
)
|
)
|
||||||
let config = parse_logger_config_text(text)
|
let config = parse_logger_config_text(text)
|
||||||
inspect(color_mode_label(config.sink.text_formatter.color_mode), content="always")
|
inspect(color_mode_label(config.sink.text_formatter.color_mode), content="always")
|
||||||
|
inspect(style_markup_mode_label(config.sink.text_formatter.style_markup), content="builtin")
|
||||||
inspect(config.sink.text_formatter.style_tags.length(), content="2")
|
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().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("accent").unwrap().bold, content="true")
|
||||||
@@ -204,12 +207,13 @@ 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_markup=StyleMarkupMode::Builtin,
|
||||||
style_tags={
|
style_tags={
|
||||||
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
"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\",\"style_tags\":{\"accent\":{\"bold\":true,\"dim\":false,\"italic\":false,\"underline\":false,\"fg\":\"#4cc9f0\"}}}",
|
content="{\"show_timestamp\":false,\"show_level\":true,\"show_target\":false,\"show_fields\":true,\"separator\":\" | \",\"field_separator\":\",\",\"template\":\"[{level}] {message} :: {fields}\",\"color_mode\":\"always\",\"style_markup\":\"builtin\",\"style_tags\":{\"accent\":{\"bold\":true,\"dim\":false,\"italic\":false,\"underline\":false,\"fg\":\"#4cc9f0\"}}}",
|
||||||
)
|
)
|
||||||
inspect(
|
inspect(
|
||||||
stringify_sink_config(
|
stringify_sink_config(
|
||||||
@@ -222,7 +226,7 @@ test "config subtype json helpers stringify stable shapes" {
|
|||||||
text_formatter=TextFormatterConfig::new(show_timestamp=false, color_mode=ColorMode::Auto),
|
text_formatter=TextFormatterConfig::new(show_timestamp=false, color_mode=ColorMode::Auto),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content="{\"kind\":\"file\",\"path\":\"demo.log\",\"append\":false,\"auto_flush\":false,\"text_formatter\":{\"show_timestamp\":false,\"show_level\":true,\"show_target\":true,\"show_fields\":true,\"separator\":\" \",\"field_separator\":\" \",\"template\":\"\",\"color_mode\":\"auto\"},\"rotation\":{\"max_bytes\":128,\"max_backups\":2}}",
|
content="{\"kind\":\"file\",\"path\":\"demo.log\",\"append\":false,\"auto_flush\":false,\"text_formatter\":{\"show_timestamp\":false,\"show_level\":true,\"show_target\":true,\"show_fields\":true,\"separator\":\" \",\"field_separator\":\" \",\"template\":\"\",\"color_mode\":\"auto\",\"style_markup\":\"full\"},\"rotation\":{\"max_bytes\":128,\"max_backups\":2}}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +246,40 @@ test "config formatter style tags render in built logger" {
|
|||||||
inspect(rendered, content="[\u{001b}[32mINFO\u{001b}[0m] \u{001b}[38;2;76;201;240;1mtag\u{001b}[0m")
|
inspect(rendered, content="[\u{001b}[32mINFO\u{001b}[0m] \u{001b}[38;2;76;201;240;1mtag\u{001b}[0m")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "config builtin style markup ignores custom tags" {
|
||||||
|
let formatter = TextFormatterConfig::new(
|
||||||
|
show_level=false,
|
||||||
|
show_target=false,
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
style_markup=StyleMarkupMode::Builtin,
|
||||||
|
style_tags={
|
||||||
|
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
let rendered = format_text(
|
||||||
|
Record::new(Level::Info, "<accent>custom</> <red>builtin</>"),
|
||||||
|
formatter=formatter.to_formatter(),
|
||||||
|
)
|
||||||
|
inspect(rendered, content="<accent>custom</> \u{001b}[31mbuiltin\u{001b}[0m")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "config disabled style markup keeps raw tags" {
|
||||||
|
let formatter = TextFormatterConfig::new(
|
||||||
|
show_level=false,
|
||||||
|
show_target=false,
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
style_markup=StyleMarkupMode::Disabled,
|
||||||
|
style_tags={
|
||||||
|
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
let rendered = format_text(
|
||||||
|
Record::new(Level::Info, "<accent>raw</> <red>tag</>"),
|
||||||
|
formatter=formatter.to_formatter(),
|
||||||
|
)
|
||||||
|
inspect(rendered, content="<accent>raw</> <red>tag</>")
|
||||||
|
}
|
||||||
|
|
||||||
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(
|
||||||
|
|||||||
@@ -123,6 +123,39 @@ test "text formatter keeps unknown inline tags as plain text" {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "text formatter can disable style markup parsing" {
|
||||||
|
let rec = record(Level::Info, "<red>boom</> <b>bold</>", target="svc")
|
||||||
|
inspect(
|
||||||
|
format_text(
|
||||||
|
rec,
|
||||||
|
formatter=text_formatter(color_mode=ColorMode::Always).without_style_markup(),
|
||||||
|
),
|
||||||
|
content="[\u{001b}[32mINFO\u{001b}[0m] [\u{001b}[34msvc\u{001b}[0m] <red>boom</> <b>bold</>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "text formatter builtin style markup mode ignores custom tags" {
|
||||||
|
let formatter = text_formatter(
|
||||||
|
show_level=false,
|
||||||
|
show_target=false,
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
style_markup=StyleMarkupMode::Builtin,
|
||||||
|
).with_style_tags(
|
||||||
|
style_tag_registry().set_tag("accent", fg=Some("#4cc9f0"), bold=true),
|
||||||
|
)
|
||||||
|
let rec = record(Level::Info, "<accent>custom</> <red>builtin</>")
|
||||||
|
inspect(
|
||||||
|
format_text(rec, formatter=formatter),
|
||||||
|
content="<accent>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::Builtin), content="builtin")
|
||||||
|
inspect(style_markup_mode_label(StyleMarkupMode::Full), content="full")
|
||||||
|
}
|
||||||
|
|
||||||
test "text formatter supports custom style tags" {
|
test "text formatter supports custom style tags" {
|
||||||
let formatter = text_formatter(
|
let formatter = text_formatter(
|
||||||
show_level=false,
|
show_level=false,
|
||||||
|
|||||||
@@ -206,6 +206,16 @@ test {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```mbt check
|
||||||
|
test {
|
||||||
|
let formatter = text_formatter(
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
).without_style_markup()
|
||||||
|
let logger = Logger::new(text_console_sink(formatter), target="raw")
|
||||||
|
logger.info("<red>kept as raw text</>")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
```mbt check
|
```mbt check
|
||||||
test {
|
test {
|
||||||
let config = parse_logger_config_text(
|
let config = parse_logger_config_text(
|
||||||
@@ -227,10 +237,21 @@ test {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```mbt check
|
||||||
|
test {
|
||||||
|
let config = parse_logger_config_text(
|
||||||
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"disabled\"}}}",
|
||||||
|
)
|
||||||
|
let logger = build_logger(config)
|
||||||
|
logger.info("<red>still raw</>")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Formatter Template / 模板格式
|
## Formatter Template / 模板格式
|
||||||
|
|
||||||
- supported tokens / 支持的 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
|
- supported tokens / 支持的 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
|
||||||
- `color_mode` / `color_mode`: `never`, `auto`, `always`
|
- `color_mode` / `color_mode`: `never`, `auto`, `always`
|
||||||
|
- `style_markup` / `style_markup`: `disabled`, `builtin`, `full`
|
||||||
- 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
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub struct TextFormatterConfig {
|
|||||||
field_separator : String
|
field_separator : String
|
||||||
template : String
|
template : String
|
||||||
color_mode : ColorMode
|
color_mode : ColorMode
|
||||||
|
style_markup : StyleMarkupMode
|
||||||
style_tags : Map[String, TextStyle]
|
style_tags : Map[String, TextStyle]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +31,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_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
||||||
style_tags~ : Map[String, TextStyle] = {},
|
style_tags~ : Map[String, TextStyle] = {},
|
||||||
) -> TextFormatterConfig {
|
) -> TextFormatterConfig {
|
||||||
{
|
{
|
||||||
@@ -41,6 +43,7 @@ pub fn TextFormatterConfig::new(
|
|||||||
field_separator,
|
field_separator,
|
||||||
template,
|
template,
|
||||||
color_mode,
|
color_mode,
|
||||||
|
style_markup,
|
||||||
style_tags,
|
style_tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,6 +66,7 @@ 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_markup=self.style_markup,
|
||||||
style_tags=if self.style_tags.length() == 0 {
|
style_tags=if self.style_tags.length() == 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -857,6 +861,15 @@ fn parse_color_mode(name : String) -> ColorMode 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sink_kind_label(kind : SinkKind) -> String {
|
fn sink_kind_label(kind : SinkKind) -> String {
|
||||||
match kind {
|
match kind {
|
||||||
SinkKind::Console => "console"
|
SinkKind::Console => "console"
|
||||||
@@ -877,6 +890,7 @@ 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_markup=parse_style_markup_mode(get_string(obj, "style_markup", default="full")),
|
||||||
style_tags=match obj.get("style_tags") {
|
style_tags=match obj.get("style_tags") {
|
||||||
None => {}
|
None => {}
|
||||||
Some(inner) => parse_style_tags_config(inner)
|
Some(inner) => parse_style_tags_config(inner)
|
||||||
@@ -1000,6 +1014,7 @@ 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)),
|
||||||
|
"style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.style_markup)),
|
||||||
}
|
}
|
||||||
if config.style_tags.length() != 0 {
|
if config.style_tags.length() != 0 {
|
||||||
obj["style_tags"] = style_tags_config_to_json(config.style_tags)
|
obj["style_tags"] = style_tags_config_to_json(config.style_tags)
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ pub(all) enum ColorMode {
|
|||||||
Always
|
Always
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(all) enum StyleMarkupMode {
|
||||||
|
Disabled
|
||||||
|
Builtin
|
||||||
|
Full
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TextStyle {
|
pub struct TextStyle {
|
||||||
fg : String?
|
fg : String?
|
||||||
bg : String?
|
bg : String?
|
||||||
@@ -165,6 +171,7 @@ pub struct TextFormatter {
|
|||||||
field_separator : String
|
field_separator : String
|
||||||
template : String
|
template : String
|
||||||
color_mode : ColorMode
|
color_mode : ColorMode
|
||||||
|
style_markup : StyleMarkupMode
|
||||||
style_tags : StyleTagRegistry?
|
style_tags : StyleTagRegistry?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +184,7 @@ pub fn text_formatter(
|
|||||||
field_separator~ : String = " ",
|
field_separator~ : String = " ",
|
||||||
template~ : String = "",
|
template~ : String = "",
|
||||||
color_mode~ : ColorMode = ColorMode::Never,
|
color_mode~ : ColorMode = ColorMode::Never,
|
||||||
|
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
||||||
style_tags~ : StyleTagRegistry? = None,
|
style_tags~ : StyleTagRegistry? = None,
|
||||||
) -> TextFormatter {
|
) -> TextFormatter {
|
||||||
{
|
{
|
||||||
@@ -188,10 +196,27 @@ pub fn text_formatter(
|
|||||||
field_separator,
|
field_separator,
|
||||||
template,
|
template,
|
||||||
color_mode,
|
color_mode,
|
||||||
|
style_markup,
|
||||||
style_tags,
|
style_tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String {
|
||||||
|
match mode {
|
||||||
|
StyleMarkupMode::Disabled => "disabled"
|
||||||
|
StyleMarkupMode::Builtin => "builtin"
|
||||||
|
StyleMarkupMode::Full => "full"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TextFormatter::with_style_markup(self : TextFormatter, style_markup : StyleMarkupMode) -> TextFormatter {
|
||||||
|
{ ..self, style_markup }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TextFormatter::without_style_markup(self : TextFormatter) -> TextFormatter {
|
||||||
|
{ ..self, style_markup: StyleMarkupMode::Disabled }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn TextFormatter::with_style_tags(self : TextFormatter, style_tags : StyleTagRegistry) -> TextFormatter {
|
pub fn TextFormatter::with_style_tags(self : TextFormatter, style_tags : StyleTagRegistry) -> TextFormatter {
|
||||||
{ ..self, style_tags: Some(style_tags) }
|
{ ..self, style_tags: Some(style_tags) }
|
||||||
}
|
}
|
||||||
@@ -427,6 +452,10 @@ fn builtin_text_style_for_tag(tag : String) -> TextStyle? {
|
|||||||
|
|
||||||
fn resolve_registered_text_style(tag : String, formatter : TextFormatter) -> TextStyle? {
|
fn resolve_registered_text_style(tag : String, formatter : TextFormatter) -> TextStyle? {
|
||||||
let normalized = normalize_style_tag_name(tag)
|
let normalized = normalize_style_tag_name(tag)
|
||||||
|
match formatter.style_markup {
|
||||||
|
StyleMarkupMode::Builtin => return builtin_text_style_for_tag(normalized)
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
match formatter.style_tags {
|
match formatter.style_tags {
|
||||||
Some(registry) => match registry.get(normalized) {
|
Some(registry) => match registry.get(normalized) {
|
||||||
Some(style) => Some(style)
|
Some(style) => Some(style)
|
||||||
@@ -522,6 +551,10 @@ fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[Style
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_inline_markup(message : String, formatter : TextFormatter) -> String {
|
fn render_inline_markup(message : String, formatter : TextFormatter) -> String {
|
||||||
|
match formatter.style_markup {
|
||||||
|
StyleMarkupMode::Disabled => return message
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
let enabled = use_ansi_color(formatter.color_mode)
|
let enabled = use_ansi_color(formatter.color_mode)
|
||||||
let segments = parse_inline_markup(message, formatter)
|
let segments = parse_inline_markup(message, formatter)
|
||||||
let out = StringBuilder::new()
|
let out = StringBuilder::new()
|
||||||
|
|||||||
@@ -183,6 +183,18 @@ let logger = Logger::new(text_console_sink(formatter), target="styled")
|
|||||||
logger.info("<accent>styled</> output and <danger>alert</>")
|
logger.info("<accent>styled</> output and <danger>alert</>")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Disable style markup parsing:
|
||||||
|
|
||||||
|
```moonbit
|
||||||
|
let formatter = text_formatter(
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
).without_style_markup()
|
||||||
|
|
||||||
|
let logger = Logger::new(text_console_sink(formatter), target="raw")
|
||||||
|
|
||||||
|
logger.info("<red>kept as raw text</>")
|
||||||
|
```
|
||||||
|
|
||||||
JSON config loading:
|
JSON config loading:
|
||||||
|
|
||||||
```moonbit
|
```moonbit
|
||||||
@@ -208,6 +220,18 @@ let logger = build_logger(config)
|
|||||||
logger.info("<accent>styled from json</>")
|
logger.info("<accent>styled from json</>")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
JSON `style_markup` mode:
|
||||||
|
|
||||||
|
```moonbit
|
||||||
|
let config = parse_logger_config_text(
|
||||||
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"disabled\"}}}",
|
||||||
|
)
|
||||||
|
|
||||||
|
let logger = build_logger(config)
|
||||||
|
|
||||||
|
logger.info("<red>still raw</>")
|
||||||
|
```
|
||||||
|
|
||||||
Native file sink:
|
Native file sink:
|
||||||
|
|
||||||
```moonbit
|
```moonbit
|
||||||
@@ -257,6 +281,7 @@ match logger.file_runtime_state() {
|
|||||||
- `QueueConfig`, `TextFormatterConfig`, and `SinkConfig` can also be exported independently through `queue_config_to_json(...)` / `stringify_queue_config(...)`, `text_formatter_config_to_json(...)` / `stringify_text_formatter_config(...)`, and `sink_config_to_json(...)` / `stringify_sink_config(...)`.
|
- `QueueConfig`, `TextFormatterConfig`, and `SinkConfig` can also be exported independently through `queue_config_to_json(...)` / `stringify_queue_config(...)`, `text_formatter_config_to_json(...)` / `stringify_text_formatter_config(...)`, and `sink_config_to_json(...)` / `stringify_sink_config(...)`.
|
||||||
- Supported keys include `min_level`, `target`, `timestamp`, `sink.kind`, `sink.path`, `sink.append`, `sink.auto_flush`, `sink.rotation`, `sink.text_formatter`, and `queue`.
|
- Supported keys include `min_level`, `target`, `timestamp`, `sink.kind`, `sink.path`, `sink.append`, `sink.auto_flush`, `sink.rotation`, `sink.text_formatter`, and `queue`.
|
||||||
- `TextFormatter` and `TextFormatterConfig` now include `color_mode = Never | Auto | Always` for ANSI text coloring control.
|
- `TextFormatter` and `TextFormatterConfig` now include `color_mode = Never | Auto | Always` for ANSI text coloring control.
|
||||||
|
- `TextFormatter` and `TextFormatterConfig` also include `style_markup = disabled | builtin | full` so callers can choose whether style markup is parsed and whether custom tags are active.
|
||||||
- `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.
|
||||||
@@ -286,6 +311,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_markup` currently supports `disabled`, `builtin`, and `full`.
|
||||||
- `sink.text_formatter.style_tags.<name>` currently supports `fg`, `bg`, `bold`, `dim`, `italic`, and `underline`.
|
- `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.
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ version 0.4.0
|
|||||||
- 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
|
- feat: support minimal `sink.text_formatter.style_tags` JSON config parsing and serialization for custom formatter tag styles
|
||||||
|
- feat: add `StyleMarkupMode = Disabled | Builtin | Full` plus formatter helpers so callers can explicitly disable style parsing or allow builtin-only parsing
|
||||||
|
- feat: support `sink.text_formatter.style_markup` in JSON config parsing and serialization
|
||||||
|
|
||||||
### Test
|
### Test
|
||||||
|
|
||||||
@@ -24,11 +26,13 @@ version 0.4.0
|
|||||||
- 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
|
- test: cover formatter `style_tags` JSON parsing, config roundtrip, JSON helper export, and config-driven styled formatter rendering
|
||||||
|
- test: cover disabled markup mode, builtin-only mode, and config-driven `style_markup` behavior
|
||||||
|
|
||||||
### 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
|
- docs: add runtime and JSON `style_tags` examples, and document current JSON schema scope
|
||||||
|
- docs: add runtime and JSON examples for toggling style markup parsing
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
@@ -37,3 +41,4 @@ version 0.4.0
|
|||||||
- 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
|
- JSON config currently supports concrete style objects only; alias-style declarations remain runtime-only
|
||||||
|
- users can now decide whether custom style parsing is enabled through runtime formatter APIs or `sink.text_formatter.style_markup`
|
||||||
|
|||||||
Reference in New Issue
Block a user