z.karl
z.karl

Reputation: 305

How can I create a custom filter type using log4rs?

The only available filter is a level threshold, but say I wanted to filter by a specific target: $expr instead. How would I programmatically (or in the yml file) let log4rs know that I need a different kind of filter? Would I need to add a custom feature to accommodate the addition of my new filter type that implements the filter trait?

I have tried implementing my own filter type with all of the associated implementations as per the source code in the documentation of the threshold filter type. I even have the custom filter name specified in the - kind: section of a filter the the .yml config file. What more could log4rs need to implement something like this?

In addition, I am receiving the following error when I run let handle = log4rs::load_config_file("../log4rs.yml", Default::default()).unwrap();

log4rs: error deserializing filter attached to appender custom_filter_appender: no filter deserializer for kind custom_filter registered

Even though I have explicitly defined a deserializer for said custom filter.

Upvotes: 2

Views: 1479

Answers (1)

z.karl
z.karl

Reputation: 305

There are a few point to respond to with this question:

  1. First, with reference to filtering by target: $expr, you can get away with merely creating a new logger that shares the same name as the target(s) you'd like to filter by. This is because log4rs only sends macros from the log crate to loggers with the same target string as the logger name. As such, there is a kind of built in filtering going on here.
  2. Second, with regard to needing a custom feature, you don't need one. Everything I will show below did not require a custom feature to run.
  3. Thirdly, and most interestingly, is the syntax and form of creating a custom filter. I will detail the necessary modifications below:

First the configuration log4rs.yml file:

 appenders:
    custom_filter_appender:
        kind: file
        path: "log/custom_filter.log"
        filters:
        - kind: custom_filter
        interesting_field: interesting_value
        encoder:
          pattern: "{d} {m}{n}"
 root:
   level: error
 loggers:
     filter_logger:
       level: <level you want to filter by>
       appenders:
         - custom_filter_appender
       additive: false

What I did above illustrates what would be necessary to configure a custom filter with log4rs using a configuration file. Observe that filters are attached to appenders and appenders to loggers. This is the only way to attach things. Next I will detail the trait implementations needed in custom_filter.rs for a custom filter and how they will affect the fields representing the custom_filter.

// First remember to include the appropriate log4rs or whatever other libraries you want in the custom_filter.rs file
#[derive(Deserialize)]
pub struct CustomFilterConfig {
    interesting_field: InterestingFieldType,
}
#[derive(Debug)]
pub struct CustomFilter {
    interesting_field: InterestingFieldType,
}

impl CustomFilter {
    /// Creates a new `CustomFilter` with the specified interesting field.
    pub fn new(interesting_field: InterestingFieldType,) -> CustomFilter {
        CustomFilter { interesting_field }
    }
}

impl Filter for CustomFilter {
    fn filter(&self, record: &Record) -> Response {
        if <Some comparison about self.interesting_field> {
            Response::Accept
        } else {
            Response::Reject
        }
    }
}

pub struct CustomFilterDeserializer;

impl Deserialize for CustomFilterDeserializer {
    type Trait = dyn Filter;

    type Config = CustomFilterConfig;

    fn deserialize(
        &self,
        config: CustomFilterConfig,
        _: &Deserializers,
    ) -> Result<Box<dyn Filter>, Box<dyn Error + Sync + Send>> {
        Ok(Box::new(CustomFilter::new(config.interesting_field)))
    }
}

All of these must be implemented if log4rs is to have a prayer of recognizing and running your filter. And remember that only log crate macros that have the same target as the name filter_logger (in this case) will go to this filter for filtering. Now to finish this off we need to know how to set up the log4rs configuration from a file in main.rs with the correct filter added so it can be used.

let mut custom_filter_deserializer = log4rs::file::Deserializers::new();
custom_filter_deserializer.insert(
    "custom_filter",
    crate::custom_filter::CustomFilterDeserializer,
);
log4rs::init_file("log4rs.yml", custom_filter_deserializer).unwrap();

And that should be everything you need to configure a custom filter yourself using log4rs.

Upvotes: 2

Related Questions