Skip to content
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

Implement RuntimePattern #45

Merged
merged 4 commits into from
Mar 27, 2024
Merged

Implement RuntimePattern #45

merged 4 commits into from
Mar 27, 2024

Conversation

SpriteOvO
Copy link
Owner

@SpriteOvO SpriteOvO commented Oct 25, 2023

This PR introduces RuntimePattern to implement the ability of building a patterns at runtime, this also allows us to implement serialization for loggers (#25) containing PatternFormatter in the future.

Basically, this PR did:

  1. Split pattern template parser from crate spdlog-macros to a separate crate spdlog-internal. so that the runtime builder and compile-time builder can shared the same parser code.

    Creating a separate crate seems to be the only way to share code between lib crate and proc-macro crate, or maybe we could publish a generic crate for the pattern feature, which would allow more other crates to use it. @Lancern Are you interested in this? Since you are the author of the pattern feature, to publish such a crate I think you are the best person to do it if you are interested.

  2. Implement Clone for all Pattern types by using dyn-clone crate, as formatters are required to be clonable, Box<dyn Pattern> are also need to be clonable.

  3. Add RuntimePattern and RuntimePatternBuilder (Removed, added a runtime_pattern! macro instead).

  4. Documentation improvements.

@SpriteOvO SpriteOvO requested a review from Lancern October 25, 2023 19:58
@SpriteOvO SpriteOvO added this to the v0.4.0 milestone Oct 27, 2023
@Lancern
Copy link
Collaborator

Lancern commented Nov 7, 2023

Nice work. But I believe we need further discussion on this to avoid any potential security vulnerabilities. There are just too many severe security vulnerabilities in logging libraries.

While it is essential to be able to parse pattern template strings at runtime to support features like configuration files, is it necessary to allow the user to build the "pattern registry" at runtime? In the current design, the registry is basically a Vec<(String, Fn() -> Box<dyn Pattern>)> that is built at runtime by the user. In a worst case scenario, the adversary may be able to control both the pattern name and the pattern vtable, which effectively means arbitrary code execution.

Thus, I believe it is more appropriate to only allow building the pattern registry at compile time.

Copy link
Collaborator

@Lancern Lancern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM except for some small problems listed. And we need further discussion on the security problem I just commented in the PR discussion thread before this PR can actually land.

spdlog/src/formatter/pattern_formatter/mod.rs Outdated Show resolved Hide resolved
@@ -428,7 +432,7 @@ impl PatternContext {
/// There are 2 approaches to create your own pattern:
/// - Define a new type and implements this trait;
/// - Use the [`pattern`] macro to create a pattern from a template string.
pub trait Pattern: Send + Sync {
pub trait Pattern: Send + Sync + DynClone {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does Pattern requires Clone after the change?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatter requires Clone to be implemented since 78e359c, so that we can implement set_formatter for combined sink

/// For [`AsyncPoolSink`], the function performs the same call to all
/// internal sinks.
fn set_formatter(&self, formatter: Box<dyn Formatter>) {
for sink in &self.backend.sinks {
sink.set_formatter(formatter.clone_box())
}
}

Before this PR, all patterns are actual types, present in the generic P of PatternFormatter<P>, also all patterns implemented Clone, so we didn't explicitly bound Clone for trait Pattern.

But with runtime pattern, we now have Box<dyn Pattern> which doesn't implement Clone, if we impl Clone for Box<dyn Pattern>, we have to bound Clone on trait Pattern, and DynClone make it possible with object-safe.

@SpriteOvO
Copy link
Owner Author

I agree with your security concerns, but I'm still hesitant to drop the custom pattern support for RuntimePattern.

So I decided to change the built-in patterns in the registry to a compile-time array (the type would be [(&'static str, Box<dyn Pattern>); N]), and also try to remove pattern-related unsafe code to rely on Rust's safety model to avoid potential vulnerabilities, such as self-ref for zero-copy (https://github.com/SpriteOvO/spdlog-rs/pull/45/files#diff-e601b4baf8558f5a6458c7a768b3b71303144c3c7e8645e5ff01b0de63b8bb1dR34-R40). The parser is completely based on nom, which describes itself as a "safe parser" in its project description. I did a quick check and it only uses a couple of unsafe operations fairly conservatively.

From the information so far, I'm still not sure if I should be for or against dropping custom patterns. However, deserializing from config files is not currently planned to support custom patterns, as the build will be done internally by spdlog-rs and there is no way for the user to pass custom patterns.

@Lancern
Copy link
Collaborator

Lancern commented Nov 10, 2023

Well, custom pattern support itself is not the root cause of the security problem. The whole point is, we can provide custom pattern support, provided that the set of custom patterns provided by the user must be determined at compile time. For security's sake, we just cannot let the user build the set of custom patterns at runtime. The idea of custom patterns itself has nothing wrong.

As for implementation, maybe we can introduce a new macro named pattern_set! and use this macro to build pattern set during compile time. The syntax can be copied directly from the current pattern! macro. Here is a PoC:

let patterns = pattern_set!{
    {$mypat} => MyPattern::default,
    {$myotherpat} => MyOtherPattern::default,
};

The implementation may take some time. If you are in a hurry to land this PR, maybe we can just land first and then implement the custom pattern support in future PRs.

@SpriteOvO
Copy link
Owner Author

SpriteOvO commented Nov 12, 2023

Well, custom pattern support itself is not the root cause of the security problem. The whole point is, we can provide custom pattern support, provided that the set of custom patterns provided by the user must be determined at compile time. For security's sake, we just cannot let the user build the set of custom patterns at runtime. The idea of custom patterns itself has nothing wrong.

Nice point, I see.

As for implementation, maybe we can introduce a new macro named pattern_set! and use this macro to build pattern set during compile time. The syntax can be copied directly from the current pattern! macro. Here is a PoC:

let patterns = pattern_set!{
    {$mypat} => MyPattern::default,
    {$myotherpat} => MyOtherPattern::default,
};

Or maybe it would be better to just implement a new runtime_pattern! macro? The only difference with the pattern! macro for user is that the first parameter only accepts a Expr.

let pattern: RuntimePattern = runtime_pattern!(template_str, {$custom} => Custom::default);

@SpriteOvO
Copy link
Owner Author

SpriteOvO commented Nov 12, 2023

The implementation may take some time. If you are in a hurry to land this PR, maybe we can just land first and then implement the custom pattern support in future PRs.

I plan to release v0.4.0 with the 2 new features RuntimePattern and Config. So RuntimePattern just needs to be done before Config PR is ready to be merged. There's no clear timeline at the moment, but there should be plenty of time, so we are not in a hurry to merge this PR :)

@Lancern
Copy link
Collaborator

Lancern commented Nov 13, 2023

Or maybe it would be better to just implement a new runtime_pattern! macro? The only difference with the pattern! macro for user is that the first parameter only accepts a Expr.

let pattern: RuntimePattern = runtime_pattern!(template_str, {$custom} => Custom::default);

I think this is better. It is consistent with existing pattern! stuff.

@Lancern
Copy link
Collaborator

Lancern commented Dec 24, 2023

I quickly go through the code in this PR and I found it a good starting point for implementing the runtime_pattern! macro. So I'm going to push new commits directly to this PR and land the feature here.

I believe what left for me to do is just to remove RuntimePatternBuilder from public interface and add the runtime_pattern! macro.

@Lancern Lancern linked an issue Dec 24, 2023 that may be closed by this pull request
@Lancern
Copy link
Collaborator

Lancern commented Dec 31, 2023

I have added the runtime_pattern macro. I found that I have to implement it as a procedural macro in the spdlog_macros crate because the normal macro by example cannot match the $ token we use in the custom pattern formatter syntax.

The documentation of the runtime_pattern macro is not finished, though.

@Lancern Lancern self-requested a review December 31, 2023 15:51
@Lancern
Copy link
Collaborator

Lancern commented Jan 1, 2024

Now the feature should be completed. Seems that I cannot request a review from the PR author through GitHub, so have a review @SpriteOvO .

@SpriteOvO
Copy link
Owner Author

@Lancern Thanks! I'm gonna review it in the next few days. And happy new year! 🎉

Copy link
Owner Author

@SpriteOvO SpriteOvO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have left some comments, and feel free to discuss about them. If there is no objection, I will apply these changes and finish the rest of the work of the PR.

spdlog-internal/src/pattern_parser/registry.rs Outdated Show resolved Hide resolved
spdlog-macros/src/pattern/mod.rs Outdated Show resolved Hide resolved
spdlog-macros/src/pattern/mod.rs Outdated Show resolved Hide resolved
Comment on lines +43 to +40
registry.register_custom(#name_literal, Box::new(|| Box::new(#factory())))
.expect("unexpected panic, please report a bug to spdlog-rs");
Copy link
Owner Author

@SpriteOvO SpriteOvO Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, I think we should pass an array of custom patterns into the registry, and storing it in a separated field of PatternRegistry. Otherwise there is not much difference compare to the previous struct RuntimePatternBuilder approach, it's still building a set of custom patterns at runtime.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, I think we should pass an array of custom patterns into the registry, and storing it in a separated field of PatternRegistry. Otherwise there is not much difference compare to the previous struct RuntimePatternBuilder approach, it's still building a set of custom patterns at runtime.

The idea is actually more complicated than I expected, so let's just keep it that way.

@SpriteOvO SpriteOvO force-pushed the runtime-pattern branch 3 times, most recently from a2b7cbe to 71b4fd2 Compare March 9, 2024 22:48
@SpriteOvO SpriteOvO force-pushed the runtime-pattern branch 4 times, most recently from 38cf870 to 62d2abf Compare March 20, 2024 21:04
@SpriteOvO
Copy link
Owner Author

SpriteOvO commented Mar 20, 2024

I removed the dependency self_cell in commit cb21cb0. Instead, I manually created a self-reference through Box::{leak,from_raw}>, which introduces a unsafe operation. Would you mind reviewing it? @Lancern

The reason for using self-reference is that the Template parser consists of complex combinators and the input type are dependent. I tried replacing &str in it with Cow<'a, str> but failed.

Once that's fine, I think this PR might be ready to merge, I'll do some final checks. I apologize for the long delay.

@Lancern
Copy link
Collaborator

Lancern commented Mar 21, 2024

@SpriteOvO I'll review it as soon as I get some free time before this weekend!

Copy link
Collaborator

@Lancern Lancern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly good, just a single NIT in the inline comments and it's ready to go!

&self,
template: Template,
mut style_range_seen: bool,
) -> PatternParserResult<Patterns> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this function can be inlined into synthesis.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build_patterns will recursively call itself with an style_range_seen = true argument, while synthesis calls it initially with style_range_seen = false.

Keeping this way we can hide the initial value of this argument instead of the caller being responsible for specifying it.

@SpriteOvO
Copy link
Owner Author

SpriteOvO commented Mar 27, 2024

Cleared some doc and rebased to branch main-dev, let's merge it! @Lancern Many thanks for your co-work and review!

@SpriteOvO SpriteOvO merged commit 7d7a3ba into main-dev Mar 27, 2024
64 checks passed
@SpriteOvO SpriteOvO deleted the runtime-pattern branch March 27, 2024 21:38
@SpriteOvO SpriteOvO added this to the v0.4.0 milestone Mar 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tracking issue for runtime_pattern! macro
2 participants