mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
✨ Add ANSI color support fallback modes
This commit is contained in:
@@ -305,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` 提供 `color_support = basic | truecolor`, 可控制 hex / RGB 样式是否降级到基础 ANSI 色
|
||||||
- `TextFormatter` / `TextFormatterConfig` 提供 `style_markup = disabled | builtin | full`, 可决定是否解析 style markup 以及是否启用 custom style tag
|
- `TextFormatter` / `TextFormatterConfig` 提供 `style_markup = disabled | builtin | full`, 可决定是否解析 style markup 以及是否启用 custom style tag
|
||||||
- `target_style_markup` 与 `fields_style_markup` 可独立控制 `target` 和 `fields` 是否解析 style markup
|
- `target_style_markup` 与 `fields_style_markup` 可独立控制 `target` 和 `fields` 是否解析 style markup
|
||||||
- `message` 支持轻量 inline style tag: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
- `message` 支持轻量 inline style tag: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
||||||
@@ -338,6 +339,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.color_support` 支持 `basic`, `truecolor`
|
||||||
- `sink.text_formatter.style_markup` 支持 `disabled`, `builtin`, `full`
|
- `sink.text_formatter.style_markup` 支持 `disabled`, `builtin`, `full`
|
||||||
- `sink.text_formatter.target_style_markup` 与 `sink.text_formatter.fields_style_markup` 支持 `disabled`, `builtin`, `full`
|
- `sink.text_formatter.target_style_markup` 与 `sink.text_formatter.fields_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`
|
||||||
|
|||||||
@@ -74,9 +74,10 @@ 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_markup\":\"builtin\",\"target_style_markup\":\"builtin\",\"fields_style_markup\":\"disabled\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true},\"panel\":{\"bg\":\"#202020\",\"underline\":true}}}}}",
|
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"color_support\":\"basic\",\"style_markup\":\"builtin\",\"target_style_markup\":\"builtin\",\"fields_style_markup\":\"disabled\",\"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(style_markup_mode_label(config.sink.text_formatter.style_markup), content="builtin")
|
||||||
|
inspect(color_support_label(config.sink.text_formatter.color_support), content="basic")
|
||||||
inspect(style_markup_mode_label(config.sink.text_formatter.target_style_markup), content="builtin")
|
inspect(style_markup_mode_label(config.sink.text_formatter.target_style_markup), content="builtin")
|
||||||
inspect(style_markup_mode_label(config.sink.text_formatter.fields_style_markup), content="disabled")
|
inspect(style_markup_mode_label(config.sink.text_formatter.fields_style_markup), content="disabled")
|
||||||
inspect(config.sink.text_formatter.style_tags.length(), content="2")
|
inspect(config.sink.text_formatter.style_tags.length(), content="2")
|
||||||
@@ -151,6 +152,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,
|
||||||
|
color_support=ColorSupport::Basic,
|
||||||
style_markup=StyleMarkupMode::Builtin,
|
style_markup=StyleMarkupMode::Builtin,
|
||||||
target_style_markup=StyleMarkupMode::Builtin,
|
target_style_markup=StyleMarkupMode::Builtin,
|
||||||
fields_style_markup=StyleMarkupMode::Disabled,
|
fields_style_markup=StyleMarkupMode::Disabled,
|
||||||
@@ -164,6 +166,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(color_support_label(config.sink.text_formatter.color_support), content="basic")
|
||||||
inspect(style_markup_mode_label(config.sink.text_formatter.style_markup), content="builtin")
|
inspect(style_markup_mode_label(config.sink.text_formatter.style_markup), content="builtin")
|
||||||
inspect(style_markup_mode_label(config.sink.text_formatter.target_style_markup), content="builtin")
|
inspect(style_markup_mode_label(config.sink.text_formatter.target_style_markup), content="builtin")
|
||||||
inspect(style_markup_mode_label(config.sink.text_formatter.fields_style_markup), content="disabled")
|
inspect(style_markup_mode_label(config.sink.text_formatter.fields_style_markup), content="disabled")
|
||||||
@@ -213,6 +216,7 @@ 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,
|
||||||
|
color_support=ColorSupport::Basic,
|
||||||
style_markup=StyleMarkupMode::Builtin,
|
style_markup=StyleMarkupMode::Builtin,
|
||||||
target_style_markup=StyleMarkupMode::Builtin,
|
target_style_markup=StyleMarkupMode::Builtin,
|
||||||
fields_style_markup=StyleMarkupMode::Disabled,
|
fields_style_markup=StyleMarkupMode::Disabled,
|
||||||
@@ -221,7 +225,7 @@ test "config subtype json helpers stringify stable shapes" {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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\",\"target_style_markup\":\"builtin\",\"fields_style_markup\":\"disabled\",\"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\",\"color_support\":\"basic\",\"style_markup\":\"builtin\",\"target_style_markup\":\"builtin\",\"fields_style_markup\":\"disabled\",\"style_tags\":{\"accent\":{\"bold\":true,\"dim\":false,\"italic\":false,\"underline\":false,\"fg\":\"#4cc9f0\"}}}",
|
||||||
)
|
)
|
||||||
inspect(
|
inspect(
|
||||||
stringify_sink_config(
|
stringify_sink_config(
|
||||||
@@ -234,10 +238,24 @@ 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\",\"style_markup\":\"full\",\"target_style_markup\":\"disabled\",\"fields_style_markup\":\"disabled\"},\"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\",\"color_support\":\"truecolor\",\"style_markup\":\"full\",\"target_style_markup\":\"disabled\",\"fields_style_markup\":\"disabled\"},\"rotation\":{\"max_bytes\":128,\"max_backups\":2}}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "config basic color support downgrades hex colors" {
|
||||||
|
let formatter = TextFormatterConfig::new(
|
||||||
|
show_level=false,
|
||||||
|
show_target=false,
|
||||||
|
color_mode=ColorMode::Always,
|
||||||
|
color_support=ColorSupport::Basic,
|
||||||
|
)
|
||||||
|
let rendered = format_text(
|
||||||
|
Record::new(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>"),
|
||||||
|
formatter=formatter.to_formatter(),
|
||||||
|
)
|
||||||
|
inspect(rendered, content="\u{001b}[31mhot\u{001b}[0m \u{001b}[100mbg\u{001b}[0m")
|
||||||
|
}
|
||||||
|
|
||||||
test "config formatter style tags render in built logger" {
|
test "config formatter style tags render in built logger" {
|
||||||
let formatter = TextFormatterConfig::new(
|
let formatter = TextFormatterConfig::new(
|
||||||
show_timestamp=false,
|
show_timestamp=false,
|
||||||
|
|||||||
@@ -139,6 +139,22 @@ test "text formatter supports hex inline colors" {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "text formatter can downgrade hex colors to basic ansi" {
|
||||||
|
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,
|
||||||
|
color_support=ColorSupport::Basic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content="\u{001b}[31mhot\u{001b}[0m \u{001b}[100mbg\u{001b}[0m",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
test "text formatter keeps unknown inline tags as plain text" {
|
test "text formatter keeps unknown inline tags as plain text" {
|
||||||
let rec = record(Level::Info, "<unknown>boom</>")
|
let rec = record(Level::Info, "<unknown>boom</>")
|
||||||
inspect(
|
inspect(
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ test {
|
|||||||
|
|
||||||
- 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`
|
||||||
|
- `color_support` / `color_support`: `basic`, `truecolor`
|
||||||
- `style_markup` / `style_markup`: `disabled`, `builtin`, `full`
|
- `style_markup` / `style_markup`: `disabled`, `builtin`, `full`
|
||||||
- `target_style_markup` / `target_style_markup`, `fields_style_markup` / `fields_style_markup`: `disabled`, `builtin`, `full`
|
- `target_style_markup` / `target_style_markup`, `fields_style_markup` / `fields_style_markup`: `disabled`, `builtin`, `full`
|
||||||
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub struct TextFormatterConfig {
|
|||||||
field_separator : String
|
field_separator : String
|
||||||
template : String
|
template : String
|
||||||
color_mode : ColorMode
|
color_mode : ColorMode
|
||||||
|
color_support : ColorSupport
|
||||||
style_markup : StyleMarkupMode
|
style_markup : StyleMarkupMode
|
||||||
target_style_markup : StyleMarkupMode
|
target_style_markup : StyleMarkupMode
|
||||||
fields_style_markup : StyleMarkupMode
|
fields_style_markup : StyleMarkupMode
|
||||||
@@ -33,6 +34,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,
|
||||||
|
color_support~ : ColorSupport = ColorSupport::TrueColor,
|
||||||
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
||||||
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||||
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||||
@@ -47,6 +49,7 @@ pub fn TextFormatterConfig::new(
|
|||||||
field_separator,
|
field_separator,
|
||||||
template,
|
template,
|
||||||
color_mode,
|
color_mode,
|
||||||
|
color_support,
|
||||||
style_markup,
|
style_markup,
|
||||||
target_style_markup,
|
target_style_markup,
|
||||||
fields_style_markup,
|
fields_style_markup,
|
||||||
@@ -72,6 +75,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,
|
||||||
|
color_support=self.color_support,
|
||||||
style_markup=self.style_markup,
|
style_markup=self.style_markup,
|
||||||
target_style_markup=self.target_style_markup,
|
target_style_markup=self.target_style_markup,
|
||||||
fields_style_markup=self.fields_style_markup,
|
fields_style_markup=self.fields_style_markup,
|
||||||
@@ -869,6 +873,14 @@ 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
|
||||||
|
"TRUECOLOR" => ColorSupport::TrueColor
|
||||||
|
_ => raise ConfigError::InvalidConfig("Unsupported color support: " + name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_style_markup_mode(name : String) -> StyleMarkupMode raise ConfigError {
|
fn parse_style_markup_mode(name : String) -> StyleMarkupMode raise ConfigError {
|
||||||
match name.to_upper() {
|
match name.to_upper() {
|
||||||
"DISABLED" => StyleMarkupMode::Disabled
|
"DISABLED" => StyleMarkupMode::Disabled
|
||||||
@@ -898,6 +910,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")),
|
||||||
|
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")),
|
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")),
|
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")),
|
fields_style_markup=parse_style_markup_mode(get_string(obj, "fields_style_markup", default="disabled")),
|
||||||
@@ -1024,6 +1037,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)),
|
||||||
|
"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)),
|
"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)),
|
"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)),
|
"fields_style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.fields_style_markup)),
|
||||||
|
|||||||
+175
-26
@@ -6,6 +6,11 @@ pub(all) enum ColorMode {
|
|||||||
Always
|
Always
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(all) enum ColorSupport {
|
||||||
|
Basic
|
||||||
|
TrueColor
|
||||||
|
}
|
||||||
|
|
||||||
pub(all) enum StyleMarkupMode {
|
pub(all) enum StyleMarkupMode {
|
||||||
Disabled
|
Disabled
|
||||||
Builtin
|
Builtin
|
||||||
@@ -149,6 +154,8 @@ pub fn reset_global_style_tag_registry() -> Unit {
|
|||||||
priv struct InlineStyle {
|
priv struct InlineStyle {
|
||||||
fg_code : String?
|
fg_code : String?
|
||||||
bg_code : String?
|
bg_code : String?
|
||||||
|
fg_basic_name : String?
|
||||||
|
bg_basic_name : String?
|
||||||
bold : Bool
|
bold : Bool
|
||||||
dim : Bool
|
dim : Bool
|
||||||
italic : Bool
|
italic : Bool
|
||||||
@@ -182,6 +189,7 @@ pub struct TextFormatter {
|
|||||||
field_separator : String
|
field_separator : String
|
||||||
template : String
|
template : String
|
||||||
color_mode : ColorMode
|
color_mode : ColorMode
|
||||||
|
color_support : ColorSupport
|
||||||
style_markup : StyleMarkupMode
|
style_markup : StyleMarkupMode
|
||||||
target_style_markup : StyleMarkupMode
|
target_style_markup : StyleMarkupMode
|
||||||
fields_style_markup : StyleMarkupMode
|
fields_style_markup : StyleMarkupMode
|
||||||
@@ -197,6 +205,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,
|
||||||
|
color_support~ : ColorSupport = ColorSupport::TrueColor,
|
||||||
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
||||||
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||||
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
||||||
@@ -211,6 +220,7 @@ pub fn text_formatter(
|
|||||||
field_separator,
|
field_separator,
|
||||||
template,
|
template,
|
||||||
color_mode,
|
color_mode,
|
||||||
|
color_support,
|
||||||
style_markup,
|
style_markup,
|
||||||
target_style_markup,
|
target_style_markup,
|
||||||
fields_style_markup,
|
fields_style_markup,
|
||||||
@@ -218,6 +228,17 @@ pub fn text_formatter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn color_support_label(support : ColorSupport) -> String {
|
||||||
|
match support {
|
||||||
|
ColorSupport::Basic => "basic"
|
||||||
|
ColorSupport::TrueColor => "truecolor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TextFormatter::with_color_support(self : TextFormatter, color_support : ColorSupport) -> TextFormatter {
|
||||||
|
{ ..self, color_support }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String {
|
pub fn style_markup_mode_label(mode : StyleMarkupMode) -> String {
|
||||||
match mode {
|
match mode {
|
||||||
StyleMarkupMode::Disabled => "disabled"
|
StyleMarkupMode::Disabled => "disabled"
|
||||||
@@ -314,7 +335,16 @@ fn ansi_wrap_with_style(text : String, style : InlineStyle, enabled : Bool) -> S
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_inline_style() -> InlineStyle {
|
fn default_inline_style() -> InlineStyle {
|
||||||
{ fg_code: None, bg_code: None, bold: false, dim: false, italic: false, underline: false }
|
{
|
||||||
|
fg_code: None,
|
||||||
|
bg_code: None,
|
||||||
|
fg_basic_name: None,
|
||||||
|
bg_basic_name: None,
|
||||||
|
bold: false,
|
||||||
|
dim: false,
|
||||||
|
italic: false,
|
||||||
|
underline: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn named_color_code(tag : String) -> String? {
|
fn named_color_code(tag : String) -> String? {
|
||||||
@@ -361,6 +391,88 @@ 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))
|
||||||
|
let b = hex_pair_to_int(value.unsafe_get(5), value.unsafe_get(6))
|
||||||
|
let max_value = if r >= g && r >= b { r } else if g >= b { g } else { b }
|
||||||
|
if max_value < 48 {
|
||||||
|
"bright_black"
|
||||||
|
} else {
|
||||||
|
let min_value = if r <= g && r <= b { r } else if g <= b { g } else { b }
|
||||||
|
let spread = max_value - min_value
|
||||||
|
if spread < 24 {
|
||||||
|
if max_value > 170 {
|
||||||
|
"white"
|
||||||
|
} else if max_value > 96 {
|
||||||
|
"bright_black"
|
||||||
|
} else {
|
||||||
|
"black"
|
||||||
|
}
|
||||||
|
} else if r >= g && r >= b {
|
||||||
|
if g > 96 && b < 96 {
|
||||||
|
"yellow"
|
||||||
|
} else if b > 96 && g < 96 {
|
||||||
|
"magenta"
|
||||||
|
} else {
|
||||||
|
"red"
|
||||||
|
}
|
||||||
|
} else if g >= r && g >= b {
|
||||||
|
if r > 96 && b < 96 {
|
||||||
|
"yellow"
|
||||||
|
} else if b > 96 && r < 96 {
|
||||||
|
"cyan"
|
||||||
|
} else {
|
||||||
|
"green"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if r > 96 && g < 96 {
|
||||||
|
"magenta"
|
||||||
|
} else if g > 96 && r < 96 {
|
||||||
|
"cyan"
|
||||||
|
} else {
|
||||||
|
"blue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_inline_color_code(
|
||||||
|
formatter : TextFormatter,
|
||||||
|
basic_name : String?,
|
||||||
|
truecolor_code : String,
|
||||||
|
) -> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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?,
|
||||||
|
truecolor_code : String,
|
||||||
|
) -> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_hex_color(value : String) -> Bool {
|
fn is_hex_color(value : String) -> Bool {
|
||||||
if value.length() != 7 {
|
if value.length() != 7 {
|
||||||
return false
|
return false
|
||||||
@@ -416,34 +528,61 @@ fn rgb_bg_code_from_hex(value : String) -> String {
|
|||||||
"48;2;\{r};\{g};\{b}"
|
"48;2;\{r};\{g};\{b}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inline_style_from_text_style(base : InlineStyle, style : TextStyle) -> InlineStyle? {
|
fn inline_style_from_text_style(
|
||||||
let fg_code = match style.fg {
|
base : InlineStyle,
|
||||||
None => Some(base.fg_code)
|
style : TextStyle,
|
||||||
Some(value) => match named_color_code(normalize_style_tag_name(value)) {
|
formatter : TextFormatter,
|
||||||
Some(code) => Some(Some(code))
|
) -> InlineStyle? {
|
||||||
None => if is_hex_color(value) {
|
let fg = match style.fg {
|
||||||
Some(Some(rgb_fg_code(value)))
|
None => Some((base.fg_code, base.fg_basic_name))
|
||||||
} else {
|
Some(value) => {
|
||||||
None
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let bg_code = match style.bg {
|
let bg = match style.bg {
|
||||||
None => Some(base.bg_code)
|
None => Some((base.bg_code, base.bg_basic_name))
|
||||||
Some(value) => match named_bg_color_code(normalize_style_tag_name(value)) {
|
Some(value) => {
|
||||||
Some(code) => Some(Some(code))
|
let normalized = normalize_style_tag_name(value)
|
||||||
None => if is_hex_color(value) {
|
match named_bg_color_code(normalized) {
|
||||||
Some(Some(rgb_bg_code_from_hex(value)))
|
Some(code) => {
|
||||||
} else {
|
let bg_name = if normalized.has_prefix("bright_") {
|
||||||
None
|
normalized
|
||||||
|
} else {
|
||||||
|
normalized
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match fg_code {
|
match fg {
|
||||||
Some(next_fg) => match bg_code {
|
Some((next_fg_code, next_fg_name)) => match bg {
|
||||||
Some(next_bg) => Some({
|
Some((next_bg_code, next_bg_name)) => Some({
|
||||||
fg_code: next_fg,
|
fg_code: next_fg_code,
|
||||||
bg_code: next_bg,
|
bg_code: next_bg_code,
|
||||||
|
fg_basic_name: next_fg_name,
|
||||||
|
bg_basic_name: next_bg_name,
|
||||||
bold: base.bold || style.bold,
|
bold: base.bold || style.bold,
|
||||||
dim: base.dim || style.dim,
|
dim: base.dim || style.dim,
|
||||||
italic: base.italic || style.italic,
|
italic: base.italic || style.italic,
|
||||||
@@ -511,12 +650,22 @@ fn resolve_registered_text_style(tag : String, formatter : TextFormatter) -> Tex
|
|||||||
fn apply_inline_tag(style : InlineStyle, tag : String, formatter : TextFormatter) -> InlineStyle? {
|
fn apply_inline_tag(style : InlineStyle, tag : String, formatter : TextFormatter) -> InlineStyle? {
|
||||||
let normalized = normalize_style_tag_name(tag)
|
let normalized = normalize_style_tag_name(tag)
|
||||||
if is_hex_color(tag) {
|
if is_hex_color(tag) {
|
||||||
Some({ ..style, fg_code: Some(rgb_fg_code(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_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()) {
|
||||||
Some({ ..style, bg_code: Some(rgb_bg_code(normalized)) })
|
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_basic_name: Some(basic_name),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
match resolve_registered_text_style(normalized, formatter) {
|
match resolve_registered_text_style(normalized, formatter) {
|
||||||
Some(spec) => inline_style_from_text_style(style, spec)
|
Some(spec) => inline_style_from_text_style(style, spec, formatter)
|
||||||
None => None
|
None => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,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 `color_support = basic | truecolor` so hex / RGB styling can be forced to downgrade to basic ANSI colors.
|
||||||
- `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.
|
- `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.
|
||||||
- `target_style_markup` and `fields_style_markup` independently control whether `target` and `fields` are parsed for style markup.
|
- `target_style_markup` and `fields_style_markup` independently control whether `target` and `fields` are parsed for style markup.
|
||||||
- `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>...</>`.
|
||||||
@@ -314,6 +315,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.color_support` currently supports `basic` and `truecolor`.
|
||||||
- `sink.text_formatter.style_markup` currently supports `disabled`, `builtin`, and `full`.
|
- `sink.text_formatter.style_markup` currently supports `disabled`, `builtin`, and `full`.
|
||||||
- `sink.text_formatter.target_style_markup` and `sink.text_formatter.fields_style_markup` currently support `disabled`, `builtin`, and `full`.
|
- `sink.text_formatter.target_style_markup` and `sink.text_formatter.fields_style_markup` currently support `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`.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ version 0.4.0
|
|||||||
- feat: add builtin semantic style tags such as `accent`, `info`, `success`, `warning`, `danger`, and `muted`
|
- feat: add builtin semantic style tags such as `accent`, `info`, `success`, `warning`, `danger`, and `muted`
|
||||||
- feat: add independent `target_style_markup` and `fields_style_markup` controls for `TextFormatter` and `TextFormatterConfig`
|
- feat: add independent `target_style_markup` and `fields_style_markup` controls for `TextFormatter` and `TextFormatterConfig`
|
||||||
- feat: support named closing tags like `</red>` alongside the existing short close `</>`
|
- feat: support named closing tags like `</red>` alongside the existing short close `</>`
|
||||||
|
- feat: add `ColorSupport = Basic | TrueColor` and support `sink.text_formatter.color_support` so hex / RGB styles can downgrade to basic ANSI colors
|
||||||
|
|
||||||
### Test
|
### Test
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ version 0.4.0
|
|||||||
- test: cover builtin semantic tag rendering and confirm user overrides still take precedence
|
- test: cover builtin semantic tag rendering and confirm user overrides still take precedence
|
||||||
- test: cover target and field markup rendering, plus config roundtrip for the new formatter markup scopes
|
- test: cover target and field markup rendering, plus config roundtrip for the new formatter markup scopes
|
||||||
- test: cover named closing tags, mixed short/named closing, and unmatched named-close fallback behavior
|
- test: cover named closing tags, mixed short/named closing, and unmatched named-close fallback behavior
|
||||||
|
- test: cover basic color-support downgrade for hex foreground/background rendering in runtime and config paths
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@@ -49,3 +51,4 @@ version 0.4.0
|
|||||||
- 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`
|
- users can now decide whether custom style parsing is enabled through runtime formatter APIs or `sink.text_formatter.style_markup`
|
||||||
- `fields_style_markup` currently styles field values only and intentionally leaves field keys raw
|
- `fields_style_markup` currently styles field values only and intentionally leaves field keys raw
|
||||||
|
- `basic` color support currently keeps ANSI styling but downgrades hex / RGB colors to the nearest basic ANSI color family
|
||||||
|
|||||||
Reference in New Issue
Block a user