-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking issue for config (deserialization) #25
Comments
@Lancern Do you have any experience to share about this? |
What's the use case? It sounds a little strange to serialize a logger / formatter / sink since these concepts are abstractions for behaviors rather than data. |
Sorry I didn't make that clear enough. Precisely, we are adding a feature that allows users to load logging configs from external inputs (particularly from files) so that they can save the existing configs and/or modify configs dynamically from an external file. I guess this is a common use case for logging libraries. |
This use case is perfectly valid. However IMO implementing The key point here is that although formatters cannot be serialized and deserialize, their "internal states" might could. So I think the proper way to achieve this would be:
I'll sketch a draft interface design for this later. |
Take formatters as an example, I propose the following interface. Note that all names are not well-considered and are subject to change. First, we add a new trait pub trait SerializableFormatter : Formatter {
type State: Deserialize + Serialize;
fn state(&self) -> Self::State;
fn set_state(&self, state: Self::State);
fn new_with_state(state: Self::State) -> Self;
} The With this interface in mind, we can save and load formatter configurations from JSON files with the following functions: (error handling code is omitted for simplicity) pub fn save_formatter_state<F, P>(formatter: &F, config_file: P) -> std::io::Result<()>
where
F: SerializableFormatter,
P: AsRef<Path>,
{
let state = formatter.state();
let state_json = serde_json::to_string().unwrap();
std::fs::write(config_file, state_json)?;
Ok(())
}
pub fn load_formatter_config<F, P>(config_file: P) -> std::io::Result<F>
where
F: SerializableFormatter,
P: AsRef<Path>,
{
let state_json = std::fs::read_to_string(config_file)?;
let state = serde_json::from_str(&state_json).unwrap();
let formatter = F::new_with_state(state);
Ok(formatter)
} |
Agree with that. Maybe we could reuse Basically, I want users to use this feature as easily as possible, I am considering this solution: fn main() -> Result<(), _> {
spdlog::registry().load_from_file("config.json")?;
module_network();
module_gui();
spdlog::registry().save_to_file("config.json")?;
}
fn module_network() -> Result<(), _> {
let network = spdlog::registry()
.logger_or_build(
"network",
// The `builder` has name `network` is set.
|builder| builder.sinks([sink1, sink2]).level_filter(_).build()?
);
info!(logger: network, "downloading...");
}
fn module_gui() -> Result<(), _> {
let gui = spdlog::registry()
.logger_or_build(
"gui",
// The `builder` has name `gui` is set.
|builder| builder.sinks([sink1, sink2]).level_filter(_).build()?
);
info!(logger: gui, "user clicked the button");
} As the code shows, we also have to introduce a new thing The idea referenced from log4rs - Configuration via a YAML file and C++ spdlog - Logger registry, I'm not sure if it's a little overkill, looking forward to better ideas. |
How to support custom sinks and formatters? |
Maybe something like this? spdlog::registry()
.register_custom_sink("CustomSink", |args: String| -> Result<Box<dyn Sink>, _> {
let builder: CustomSinkBuilder = serde::from_str(&args)?;
Ok(Box::new(builder.build()))
}); |
LGTM, but the interface design needs to be discussed further. Maybe we can open a draft PR and discuss there? |
Okay, I'll make a draft PR for the initial later ^^ |
This is the initial design of the config file, IMO, TOML is the best format for this, YAML is probably fine, but the indentation sensitivity is a bit verbose to me. # configure the default logger
[default]
sinks = [
{ name = "std_stream_sink", level_filtter = "all", style_mode = "auto" },
{ name = "rotating_file_sink", base_path = "/var/log/my_app/app.log", rotation_policy = { daily = "12:00" } },
{ name = "win_debug_sink", formatter = { name = "pattern_formatter", pattern = "[{level}] {payload}" } }
]
flush_level_filter = "all"
flush_period = "10s"
# configure named loggers "network"
[network]
sinks = [
{ name = "rotating_file_sink", base_path = "/var/log/my_app/err.log", rotation_policy = { file_size = "10M" }, level_filtter = "error" }
]
flush_level_filter = "warn"
# configure named loggers "gui", unnamed loggers, and the rest of loggers
["gui,unnamed,*"]
sinks = [
{ name = "file_sink", path = "/var/log/my_app/misc.log", level_filtter = "all" },
# user-defined sink/formatter must start with "$" and are registered in Rust code
{ name = "$custom_sink", some_arg = "xxx", level_filtter = "all", formatter = { name = "$custom_formatter", some_arg = "yyy" } }
]
flush_level_filter = "all" |
LGTM, but a small problem on naming convention: what about |
I'm fine with the suggestion for keys, just a About values (e.g. And the logger options will now be under the "logger" key, so that we can reserve the root for future use for configuring global options. So it looks like this now, what do you think? # configure the default logger
[logger.default]
sinks = [
{ name = "StdStreamSink", level-filtter = "all", style-mode = "auto" },
{ name = "RotatingFileSink", base-path = "/var/log/my-app/app.log", rotation-policy = { daily = "12:00" } },
{ name = "WinDebugSink", formatter = { name = "PatternFormatter", pattern = "[{level}] {payload}" } }
]
flush-level-filter = "all"
flush-period = "10s"
# configure named loggers "network"
[logger.network]
sinks = [
{ name = "RotatingFileSink", base-path = "/var/log/my-app/err.log", rotation-policy = { file-size = "10M" }, level-filtter = "error" }
]
flush-level-filter = "warn"
# configure named loggers "gui", unnamed loggers, and the rest of loggers
[logger."gui,unnamed,*"]
sinks = [
{ name = "FileSink", path = "/var/log/my-app/misc.log", level-filtter = "all" },
# user-defined sink/formatter must start with "$" and are registered in Rust code
{ name = "$CustomSink", some-arg = "xxx", level-filtter = "all", formatter = { name = "$CustomFormatter", some-arg = "yyy" } }
]
flush-level-filter = "all" |
Also, I plan to just implement deserialization and drop serialization, based on
|
Maybe change [loggers.default]
# ...
[loggers.network]
# ...
[loggers."gui,unnamed,*"]
# ...
Agree. Serializing a logging configuration at runtime to a file just doesn't make much sense, I couldn't imagine a scenario in which this is useful. |
I'm considering adding a new feature providing the ability to serialize and deserialize for loggers, that allows users to load logging configs from external inputs (particularly from files) so that they can save the existing configs and/or modify configs dynamically from an external file.
The overall idea is, we depending
serde
as an optional dependency, and then we derive/implementSerialize
&Deserialize
around all stuff (e.g. All formatters, sinks, and the Logger)There are some unsolved issues:
erased-serde
crate directly? (I have no experience with this crate)So this is a discussion issue, anyone could leave your opinion or concern :)
Steps / History
RuntimePattern
#45runtime_pattern!
macro #55config
feature #47The text was updated successfully, but these errors were encountered: