mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
537 lines
16 KiB
MoonBit
537 lines
16 KiB
MoonBit
pub(all) suberror ConfigError {
|
|
InvalidConfig(String)
|
|
}
|
|
|
|
pub(all) enum SinkKind {
|
|
Console
|
|
JsonConsole
|
|
TextConsole
|
|
File
|
|
}
|
|
|
|
pub struct TextFormatterConfig {
|
|
show_timestamp : Bool
|
|
show_level : Bool
|
|
show_target : Bool
|
|
show_fields : Bool
|
|
separator : String
|
|
field_separator : String
|
|
template : String
|
|
color_mode : ColorMode
|
|
color_support : ColorSupport
|
|
style_markup : StyleMarkupMode
|
|
target_style_markup : StyleMarkupMode
|
|
fields_style_markup : StyleMarkupMode
|
|
style_tags : Map[String, TextStyle]
|
|
}
|
|
|
|
pub fn TextFormatterConfig::new(
|
|
show_timestamp~ : Bool = true,
|
|
show_level~ : Bool = true,
|
|
show_target~ : Bool = true,
|
|
show_fields~ : Bool = true,
|
|
separator~ : String = " ",
|
|
field_separator~ : String = " ",
|
|
template~ : String = "",
|
|
color_mode~ : ColorMode = ColorMode::Never,
|
|
color_support~ : ColorSupport = ColorSupport::TrueColor,
|
|
style_markup~ : StyleMarkupMode = StyleMarkupMode::Full,
|
|
target_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
|
fields_style_markup~ : StyleMarkupMode = StyleMarkupMode::Disabled,
|
|
style_tags~ : Map[String, TextStyle] = {},
|
|
) -> TextFormatterConfig {
|
|
{
|
|
show_timestamp,
|
|
show_level,
|
|
show_target,
|
|
show_fields,
|
|
separator,
|
|
field_separator,
|
|
template,
|
|
color_mode,
|
|
color_support,
|
|
style_markup,
|
|
target_style_markup,
|
|
fields_style_markup,
|
|
style_tags,
|
|
}
|
|
}
|
|
|
|
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 {
|
|
text_formatter(
|
|
show_timestamp=self.show_timestamp,
|
|
show_level=self.show_level,
|
|
show_target=self.show_target,
|
|
show_fields=self.show_fields,
|
|
separator=self.separator,
|
|
field_separator=self.field_separator,
|
|
template=self.template,
|
|
color_mode=self.color_mode,
|
|
color_support=self.color_support,
|
|
style_markup=self.style_markup,
|
|
target_style_markup=self.target_style_markup,
|
|
fields_style_markup=self.fields_style_markup,
|
|
style_tags=if self.style_tags.length() == 0 {
|
|
None
|
|
} else {
|
|
Some(style_tag_registry_from_config(self.style_tags))
|
|
},
|
|
)
|
|
}
|
|
|
|
pub struct QueueConfig {
|
|
max_pending : Int
|
|
overflow : QueueOverflowPolicy
|
|
}
|
|
|
|
pub fn QueueConfig::new(
|
|
max_pending : Int,
|
|
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
|
|
) -> QueueConfig {
|
|
{ max_pending, overflow }
|
|
}
|
|
|
|
pub struct SinkConfig {
|
|
kind : SinkKind
|
|
path : String
|
|
append : Bool
|
|
auto_flush : Bool
|
|
rotation : FileRotation?
|
|
text_formatter : TextFormatterConfig
|
|
}
|
|
|
|
pub fn SinkConfig::new(
|
|
kind~ : SinkKind = SinkKind::Console,
|
|
path~ : String = "",
|
|
append~ : Bool = true,
|
|
auto_flush~ : Bool = true,
|
|
rotation~ : FileRotation? = None,
|
|
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
|
|
) -> SinkConfig {
|
|
{
|
|
kind,
|
|
path,
|
|
append,
|
|
auto_flush,
|
|
rotation,
|
|
text_formatter,
|
|
}
|
|
}
|
|
|
|
pub struct LoggerConfig {
|
|
min_level : @core.Level
|
|
target : String
|
|
timestamp : Bool
|
|
sink : SinkConfig
|
|
queue : QueueConfig?
|
|
}
|
|
|
|
pub fn LoggerConfig::new(
|
|
min_level~ : @core.Level = @core.Level::Info,
|
|
target~ : String = "",
|
|
timestamp~ : Bool = false,
|
|
sink~ : SinkConfig = default_sink_config(),
|
|
queue~ : QueueConfig? = None,
|
|
) -> LoggerConfig {
|
|
{
|
|
min_level,
|
|
target,
|
|
timestamp,
|
|
sink,
|
|
queue,
|
|
}
|
|
}
|
|
|
|
pub fn default_text_formatter_config() -> TextFormatterConfig {
|
|
TextFormatterConfig::new()
|
|
}
|
|
|
|
pub fn default_sink_config() -> SinkConfig {
|
|
SinkConfig::new()
|
|
}
|
|
|
|
pub fn default_logger_config() -> LoggerConfig {
|
|
LoggerConfig::new()
|
|
}
|
|
|
|
fn expect_object(
|
|
value : @json_parser.JsonValue,
|
|
context : String,
|
|
) -> Map[String, @json_parser.JsonValue] raise ConfigError {
|
|
match value.as_object() {
|
|
Some(obj) => obj
|
|
None => raise ConfigError::InvalidConfig("Expected object at " + context)
|
|
}
|
|
}
|
|
|
|
fn get_string(
|
|
obj : Map[String, @json_parser.JsonValue],
|
|
key : String,
|
|
default~ : String = "",
|
|
) -> String raise ConfigError {
|
|
match obj.get(key) {
|
|
None => default
|
|
Some(value) => match value.as_string() {
|
|
Some(text) => text
|
|
None => raise ConfigError::InvalidConfig("Expected string at key " + key)
|
|
}
|
|
}
|
|
}
|
|
|
|
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(
|
|
obj : Map[String, @json_parser.JsonValue],
|
|
key : String,
|
|
default~ : Bool,
|
|
) -> Bool raise ConfigError {
|
|
match obj.get(key) {
|
|
None => default
|
|
Some(value) => match value.as_bool() {
|
|
Some(flag) => flag
|
|
None => raise ConfigError::InvalidConfig("Expected bool at key " + key)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_int(
|
|
obj : Map[String, @json_parser.JsonValue],
|
|
key : String,
|
|
default~ : Int,
|
|
) -> Int raise ConfigError {
|
|
match obj.get(key) {
|
|
None => default
|
|
Some(value) => match value.as_number() {
|
|
Some(number) => number.to_int()
|
|
None => raise ConfigError::InvalidConfig("Expected number at key " + key)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_level(name : String) -> @core.Level raise ConfigError {
|
|
match name.to_upper() {
|
|
"TRACE" => @core.Level::Trace
|
|
"DEBUG" => @core.Level::Debug
|
|
"INFO" => @core.Level::Info
|
|
"WARN" => @core.Level::Warn
|
|
"ERROR" => @core.Level::Error
|
|
_ => raise ConfigError::InvalidConfig("Unsupported level: " + name)
|
|
}
|
|
}
|
|
|
|
fn parse_overflow(name : String) -> QueueOverflowPolicy raise ConfigError {
|
|
match name.to_upper() {
|
|
"DROPNEWEST" => QueueOverflowPolicy::DropNewest
|
|
"DROPPOLDEST" => QueueOverflowPolicy::DropOldest
|
|
"DROPOLDEST" => QueueOverflowPolicy::DropOldest
|
|
_ => raise ConfigError::InvalidConfig("Unsupported queue overflow policy: " + name)
|
|
}
|
|
}
|
|
|
|
fn parse_sink_kind(name : String) -> SinkKind raise ConfigError {
|
|
match name.to_upper() {
|
|
"CONSOLE" => SinkKind::Console
|
|
"JSON_CONSOLE" => SinkKind::JsonConsole
|
|
"JSONCONSOLE" => SinkKind::JsonConsole
|
|
"TEXT_CONSOLE" => SinkKind::TextConsole
|
|
"TEXTCONSOLE" => SinkKind::TextConsole
|
|
"FILE" => SinkKind::File
|
|
_ => raise ConfigError::InvalidConfig("Unsupported sink kind: " + name)
|
|
}
|
|
}
|
|
|
|
fn parse_color_mode(name : String) -> ColorMode raise ConfigError {
|
|
match name.to_upper() {
|
|
"NEVER" => ColorMode::Never
|
|
"AUTO" => ColorMode::Auto
|
|
"ALWAYS" => ColorMode::Always
|
|
_ => raise ConfigError::InvalidConfig("Unsupported color mode: " + name)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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 {
|
|
match kind {
|
|
SinkKind::Console => "console"
|
|
SinkKind::JsonConsole => "json_console"
|
|
SinkKind::TextConsole => "text_console"
|
|
SinkKind::File => "file"
|
|
}
|
|
}
|
|
|
|
fn parse_text_formatter_config(value : @json_parser.JsonValue) -> TextFormatterConfig raise ConfigError {
|
|
let obj = expect_object(value, "text_formatter")
|
|
TextFormatterConfig::new(
|
|
show_timestamp=get_bool(obj, "show_timestamp", default=true),
|
|
show_level=get_bool(obj, "show_level", default=true),
|
|
show_target=get_bool(obj, "show_target", default=true),
|
|
show_fields=get_bool(obj, "show_fields", default=true),
|
|
separator=get_string(obj, "separator", default=" "),
|
|
field_separator=get_string(obj, "field_separator", default=" "),
|
|
template=get_string(obj, "template", default=""),
|
|
color_mode=parse_color_mode(get_string(obj, "color_mode", default="never")),
|
|
color_support=parse_color_support(get_string(obj, "color_support", default="truecolor")),
|
|
style_markup=parse_style_markup_mode(get_string(obj, "style_markup", default="full")),
|
|
target_style_markup=parse_style_markup_mode(get_string(obj, "target_style_markup", default="disabled")),
|
|
fields_style_markup=parse_style_markup_mode(get_string(obj, "fields_style_markup", default="disabled")),
|
|
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 {
|
|
let obj = expect_object(value, "queue")
|
|
QueueConfig::new(
|
|
get_int(obj, "max_pending", default=0),
|
|
overflow=parse_overflow(get_string(obj, "overflow", default="DropNewest")),
|
|
)
|
|
}
|
|
|
|
fn parse_file_rotation_config(value : @json_parser.JsonValue) -> FileRotation raise ConfigError {
|
|
let obj = expect_object(value, "sink.rotation")
|
|
file_rotation(
|
|
get_int(obj, "max_bytes", default=1),
|
|
max_backups=get_int(obj, "max_backups", default=1),
|
|
)
|
|
}
|
|
|
|
fn parse_sink_config(value : @json_parser.JsonValue) -> SinkConfig raise ConfigError {
|
|
let obj = expect_object(value, "sink")
|
|
let kind = parse_sink_kind(get_string(obj, "kind", default="console"))
|
|
let formatter = match obj.get("text_formatter") {
|
|
None => default_text_formatter_config()
|
|
Some(inner) => parse_text_formatter_config(inner)
|
|
}
|
|
let path = get_string(obj, "path", default="")
|
|
match kind {
|
|
SinkKind::File => if path == "" {
|
|
raise ConfigError::InvalidConfig("File sink requires non-empty path")
|
|
}
|
|
_ => ()
|
|
}
|
|
SinkConfig::new(
|
|
kind=kind,
|
|
path=path,
|
|
append=get_bool(obj, "append", default=true),
|
|
auto_flush=get_bool(obj, "auto_flush", default=true),
|
|
rotation=match obj.get("rotation") {
|
|
None => None
|
|
Some(inner) => Some(parse_file_rotation_config(inner))
|
|
},
|
|
text_formatter=formatter,
|
|
)
|
|
}
|
|
|
|
pub fn parse_logger_config_text(input : String) -> LoggerConfig raise ConfigError {
|
|
let root = @json_parser.parse(input) catch {
|
|
e => raise ConfigError::InvalidConfig("Invalid JSON: " + e.to_string())
|
|
}
|
|
let obj = expect_object(root, "root")
|
|
LoggerConfig::new(
|
|
min_level=parse_level(get_string(obj, "min_level", default="INFO")),
|
|
target=get_string(obj, "target", default=""),
|
|
timestamp=get_bool(obj, "timestamp", default=false),
|
|
sink=match obj.get("sink") {
|
|
None => default_sink_config()
|
|
Some(value) => parse_sink_config(value)
|
|
},
|
|
queue=match obj.get("queue") {
|
|
None => None
|
|
Some(value) => Some(parse_queue_config(value))
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
|
|
@json_parser.JsonValue::Object({
|
|
"max_pending": @json_parser.JsonValue::Number(queue.max_pending.to_double()),
|
|
"overflow": @json_parser.JsonValue::String(match queue.overflow {
|
|
QueueOverflowPolicy::DropNewest => "DropNewest"
|
|
QueueOverflowPolicy::DropOldest => "DropOldest"
|
|
}),
|
|
})
|
|
}
|
|
|
|
pub fn stringify_queue_config(queue : QueueConfig, pretty~ : Bool = false) -> String {
|
|
let value = queue_config_to_json(queue)
|
|
if pretty {
|
|
@json_parser.stringify_pretty(value, 2)
|
|
} else {
|
|
@json_parser.stringify(value)
|
|
}
|
|
}
|
|
|
|
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_parser.JsonValue {
|
|
let obj : Map[String, @json_parser.JsonValue] = {
|
|
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp),
|
|
"show_level": @json_parser.JsonValue::Bool(config.show_level),
|
|
"show_target": @json_parser.JsonValue::Bool(config.show_target),
|
|
"show_fields": @json_parser.JsonValue::Bool(config.show_fields),
|
|
"separator": @json_parser.JsonValue::String(config.separator),
|
|
"field_separator": @json_parser.JsonValue::String(config.field_separator),
|
|
"template": @json_parser.JsonValue::String(config.template),
|
|
"color_mode": @json_parser.JsonValue::String(color_mode_label(config.color_mode)),
|
|
"color_support": @json_parser.JsonValue::String(color_support_label(config.color_support)),
|
|
"style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.style_markup)),
|
|
"target_style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.target_style_markup)),
|
|
"fields_style_markup": @json_parser.JsonValue::String(style_markup_mode_label(config.fields_style_markup)),
|
|
}
|
|
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(
|
|
config : TextFormatterConfig,
|
|
pretty~ : Bool = false,
|
|
) -> String {
|
|
let value = text_formatter_config_to_json(config)
|
|
if pretty {
|
|
@json_parser.stringify_pretty(value, 2)
|
|
} else {
|
|
@json_parser.stringify(value)
|
|
}
|
|
}
|
|
|
|
pub fn file_rotation_config_to_json(config : FileRotation) -> @json_parser.JsonValue {
|
|
@json_parser.JsonValue::Object({
|
|
"max_bytes": @json_parser.JsonValue::Number(config.max_bytes.to_double()),
|
|
"max_backups": @json_parser.JsonValue::Number(config.max_backups.to_double()),
|
|
})
|
|
}
|
|
|
|
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
|
|
let obj : Map[String, @json_parser.JsonValue] = {
|
|
"kind": @json_parser.JsonValue::String(sink_kind_label(config.kind)),
|
|
"path": @json_parser.JsonValue::String(config.path),
|
|
"append": @json_parser.JsonValue::Bool(config.append),
|
|
"auto_flush": @json_parser.JsonValue::Bool(config.auto_flush),
|
|
"text_formatter": text_formatter_config_to_json(config.text_formatter),
|
|
}
|
|
match config.rotation {
|
|
None => ()
|
|
Some(rotation) => obj["rotation"] = file_rotation_config_to_json(rotation)
|
|
}
|
|
@json_parser.JsonValue::Object(obj)
|
|
}
|
|
|
|
pub fn stringify_sink_config(config : SinkConfig, pretty~ : Bool = false) -> String {
|
|
let value = sink_config_to_json(config)
|
|
if pretty {
|
|
@json_parser.stringify_pretty(value, 2)
|
|
} else {
|
|
@json_parser.stringify(value)
|
|
}
|
|
}
|
|
|
|
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
|
|
let obj : Map[String, @json_parser.JsonValue] = {
|
|
"min_level": @json_parser.JsonValue::String(config.min_level.label()),
|
|
"target": @json_parser.JsonValue::String(config.target),
|
|
"timestamp": @json_parser.JsonValue::Bool(config.timestamp),
|
|
"sink": sink_config_to_json(config.sink),
|
|
}
|
|
match config.queue {
|
|
None => ()
|
|
Some(queue) => obj["queue"] = queue_config_to_json(queue)
|
|
}
|
|
@json_parser.JsonValue::Object(obj)
|
|
}
|
|
|
|
pub fn stringify_logger_config(config : LoggerConfig, pretty~ : Bool = false) -> String {
|
|
let value = logger_config_to_json(config)
|
|
if pretty {
|
|
@json_parser.stringify_pretty(value, 2)
|
|
} else {
|
|
@json_parser.stringify(value)
|
|
}
|
|
}
|