GithubHelp home page GithubHelp logo

Comments (15)

Lancern avatar Lancern commented on June 16, 2024 1

So it looks like this now, what do you think?

Maybe change logger to plural and renaming logger to loggers?

[loggers.default]
# ...

[loggers.network]
# ...

[loggers."gui,unnamed,*"]
# ...

I plan to just implement deserialization and drop serialization

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.

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

@Lancern Do you have any experience to share about this?

from spdlog-rs.

Lancern avatar Lancern commented on June 16, 2024

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.

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

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.

from spdlog-rs.

Lancern avatar Lancern commented on June 16, 2024

This use case is perfectly valid. However IMO implementing Serialize and Deserialize directly for formatters, sinks and loggers is not a proper way to achieve this. As I said earlier, these concepts are abstract models of "runtime behaviors", and how could "runtime behaviors" be serialized and deserialized? It just doesn't make sense.

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:

  • Adding functions to get (and set?) the "internal states" of formatters, sinks and loggers;
  • Serialize and deserialize those internal states.

I'll sketch a draft interface design for this later.

from spdlog-rs.

Lancern avatar Lancern commented on June 16, 2024

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 SerializableFormatter that represents a "serializable" formatter (i.e. a formatter with some "serializable internal states"):

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 state associate function gets the internal states of the formatter. The set_state function sets the internal states of the formatter (in a thread-safe way). The new_with_state function creates a new formatter with the given internal states. Type of the internal states can be specified through the State associate type.

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)
}

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

However IMO implementing Serialize and Deserialize directly for formatters, sinks and loggers is not a proper way to achieve this. As I said earlier, these concepts are abstract models of "runtime behaviors", and how could "runtime behaviors" be serialized and deserialized? It just doesn't make sense.

Agree with that. Maybe we could reuse {Logger,Sink,Formatter}Builder for serialization? They have all the parameters to build their target.


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 Registry, which potentially requires changing LoggerBuilder::build() to return Arc<Logger> instead of Logger since we need to store loggers inside the registry for future acquire.

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.

from spdlog-rs.

Lancern avatar Lancern commented on June 16, 2024

How to support custom sinks and formatters? Registry does not know their types when loading from configuration files.

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

How to support custom sinks and formatters? Registry does not know their types when loading from configuration files.

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()))
    });

from spdlog-rs.

Lancern avatar Lancern commented on June 16, 2024

LGTM, but the interface design needs to be discussed further. Maybe we can open a draft PR and discuss there?

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

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 ^^

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

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"

from spdlog-rs.

Lancern avatar Lancern commented on June 16, 2024

LGTM, but a small problem on naming convention: what about lower-letter rather than lower_letter? Cargo.toml uses the former.

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

LGTM, but a small problem on naming convention: what about lower-letter rather than lower_letter? Cargo.toml uses the former.

I'm fine with the suggestion for keys, just a #[serde(rename_all = "kebab-case")] will do.

About values (e.g. = "std_stream_sink"), I now prefer that it should be consistent with the struct name. Because this part may involve manual deserialization, naming it consistently with the struct allows us to avoid the burden of renaming.

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 = "pattern-formatter", 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"

from spdlog-rs.

SpriteOvO avatar SpriteOvO commented on June 16, 2024

Also, I plan to just implement deserialization and drop serialization, based on

  • If we implement serialization we need more effort to maintain a logger registry, and if we drop it we can just deserialize to a Config struct and then users can obtain what they want from it.

  • Rethinking, it doesn't seem to make sense to save a programmatically built/modified logger to a config file at runtime.

from spdlog-rs.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.