Detailed use of logback filters (1)

Logback filters are based on Ternary Logic, allowing filters to be combined or chained together to form more complex filtering strategies. This design is heavily influenced by Linux's iptables.

 

About logback's own filter

logback provides two kinds of filters: regular filters and turbo filters.

 

Regular filter

The regular filter inherits from the abstract class Filter , which essentially only contains a method decide() that takes ILoggingEvent as an input parameter.

 

Based on Ternary Logic, filters are grouped together in a sorted list. The system calls the decide(ILoggingEvent event) method of each filter in this order. The return result of the decide method is the enumeration class FilterReply (one of DENY, NEUTRAL, ACCEPT). When the return value is DENY, the log event directly discards the log and will not be passed to the remaining filters. When the return value is NEUTRAL, it is passed to the filter below. When the return value is ACCEPT, the log event processes the log immediately, skipping calling other filters.

 

In logback, filters can be added to Appenders. By adding one or more filters to the Appender, you can filter log events by any condition, such as: log content, MDC content, time, etc. for any part of the log.

 

Implement your own filter

Creating your own filter is easy, you just need to extend the Filter abstract class and implement the decide method.

Below, an example SampleFilter is provided. The filter only processes logs whose log content contains the "sample" keyword.

package chapters.filters;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

public class SampleFilter extends Filter<ILoggingEvent> {

  @Override
  public FilterReply decide(ILoggingEvent event) {    
    if (event.getMessage().contains("sample")) {
      return FilterReply.ACCEPT;
    } else {
      return FilterReply.NEUTRAL;
    }
  }
}

 

The configuration file below shows adding the SampleFillter to the ConsoleAppender.

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

    <filter class="chapters.filters.SampleFilter" />

    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger - %msg%n
      </pattern>
    </encoder>
  </appender>
        
  <root>
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

 With Joran, the logback configuration framework, it is easy to assign properties or submodules to filters. After adding a property setter method to the filter, you can specify the property value through <property> and nest it in <filter>.

 

The filter we want often contains match and mismatch, and returns the result by whether it matches. For example, according to whether it is "foobar" in the log, one filter may return ACCEPT if it matches, and NEUTRAL if it does not match; if another filter may match, return NEUTRAL if it does not match, return DENY.

Considering the above situation, logback provides the AbstractMatcherFilter class. Through the OnMatch and OnMismatch properties, the appropriate response value can be specified according to whether it matches. Most regular filters in logback inherit from AbstractMatcherFilter.

 

level filter

The level filter ( LevelFilter ) is based on exactly matching log levels. If the log level is equal to the configured level, the filter decides whether to accept or reject the event through the OnMatch and OnMismatch properties in the configuration. The following is an example of a configuration file:

<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>INFO</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      </pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

 Threshold filter

ThresholdFilter ( ThresholdFilter ) filters events below the specified threshold. When the log level in the event is greater than or equal to the specified threshold, the filter's decide method returns NEUTRAL. However, events with log level less than the threshold are rejected. The following is an example of a configuration file:

<configuration>
  <appender name="CONSOLE"
    class="ch.qos.logback.core.ConsoleAppender">
    <!-- deny all events with a level below INFO, that is TRACE and DEBUG -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
      <level>INFO</level>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      </pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

 Conditional filter

A conditional filter ( EvaluatorFilter ) is a pass filter that encapsulates an EventEvaluator. As the name implies, the conditional filter returns the values ​​in the OnMatch and OnMismatch properties based on whether the event meets the specified conditions.

Note that EventEvaluator is an abstract class, by extending it, you can implement your own conditional logic.

 

Groovy event judgment class

Groovy conditional filter ( GEventEvaluator ) is a concrete implementation of EventEvaluator that takes any Groovy language boolean expression as the judgment condition . We call such Groovy Boolean expressions "groovy evaluation expressions". Groovy judgment expressions are the most flexible of the filters available. The Groovy event judgment class requires the Groovy runtime environment. Please refer to the specified documentation to add the Groovy runtime environment to your runtime environment.

The predicate expression is compiled when the configuration file is parsed. As a user, you don't have to worry about actual memory leaks. However, you need to ensure that your groovy language expressions are valid.

The predicate expression manages the current log event. Logback automatically adds an ILoggingEvent variable 'event' (which can also be the short name 'e') to represent the current log event. TRACE, DEBUG, INFO, WARN and ERROR are also introduced into the current expression category. Therefore, "event.level == DEBUG" and "e.level == DEBUG" are equivalent, both return true when the level of the current log event is DEBUG. For other comparison operations, the level variable needs to be converted to an integer via toInt().

To give you a more comprehensive example:

<configuration>
    
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">      
      <evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator">
        <expression>
           e.level.toInt() >= WARN.toInt() &&  <!-- Stands for && in XML -->
           !(e.mdc?.get("req.userAgent") =~ /Googlebot|msnbot|Yahoo/ )
        </expression>
      </evaluator>
      <OnMismatch>DENY</OnMismatch>
      <OnMatch>NEUTRAL</OnMatch>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger - %msg%n
      </pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

 The above filter will let events with log level higher than WARN continue to pass, or the error is generated by Google, MSN, Yahoo crawlers. This is done to determine whether the "req.userAgent" value in the event's MDC matches the " /Googlebot|msnbot|Yahoo/ " regular expression. Note that since MDC's map can be null, we also use Groovy's unreference-safe operator "?.". This expression logic would be very long if implemented in Java.

If you're wondering how the user agent value is inserted into the MDC with the "req.userAgent" key, logback is implemented via a servlet filter, MDCInsertingServletFilter, which is obviously beyond the scope of this article. It will be covered in later chapters.

 

Java event judgment class

Logback comes with another implementation of EventEvaluator called JaninoEventEvaluator , which takes any Java code block that returns a boolean as a condition. We call this Java Boolean expression a "judgment expression". Judgment expressions have great flexibility in event filtering. JaninoEventEvaluator requires Janino jar. Please refer to other chapters for related dependencies.

Compared with JaninoEventEvaluator, GEventEvaluator is more convenient to use due to the advantages of Groovy, but the same expression, JaninoEventEvaluator generally runs much faster.

The predicate expression is compiled when the configuration file is resolved. As a user, you don't need to worry about memory issues. However, you need to ensure that the Java language expression returns a boolean value, for example, the result is true or false

The judgment expression is to judge the current log event. The judgment expression can obtain various variables automatically imported by Logback. Case-sensitive changes in imported changes are shown below:

Name Type Description
event LoggingEvent The raw log event associated with the log request. All the following variables are available in the event. For example: event.getMessage() returns the same String as the message variable.
message String Raw information in the log request. Like some logs/, when you write l.info("Hello {}", name), the value associated with the name is "Alice", and then the message is "Hello {}".
formattedMessage String Formatted messages in log requests. Like some logs/, when you write l.info("Hello {}", name), the name associated value is "Allice" and the formatted message is "Hello Alice".
logger String log name
loggerContext LoggerContextVO A restricted view object of the log context to which the log event belongs
level int The int value of the log level. Used to simplify creating expressions about levels. The default values ​​of DEBUG, INFO, WARN and ERROR are also available. Therefore, it is correct to use level>INFO
timeStamp long The log event creation time.
marker Marker The Marker object in the log request. Note that this object may be empty.
mdc Map

A map object containing all MDC values ​​at the time the log event was created. The value may be obtained by the following expression: mdc.get("myKey"). Since 0.9.30, the "mdc" variable is never empty

Since Janino does not support generics, java.util.Map does not contain parameter types. The object returned by mdc.get() is Object instead of String. In order to call a String method that returns a value, you need to cast to String. E.g:((String) mdc.get("k")).contains("val")

throwable java.lang.Throwable

The throwable variable is null if no exception is associated with the event.

Unfortunately, "throwable" does not support serialization. Therefore, for remote systems, its value is always null. For local standalone expressions use throwableProxy below

throwableProxy IThrowableProxy The agent that logs event exceptions. If no exception is associated with the event, the "throwableProxy" variable will be empty. Unlike throwable, the throwableProxy value is not null, even on serialized remote systems

To give you a concrete example:

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">      
      <evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
        <expression>return message.contains("billing");</expression>
      </evaluator>
      <OnMismatch>NEUTRAL</OnMismatch>
      <OnMatch>DENY</OnMatch>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger - %msg%n
      </pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

The EvaluatorFilter is added to the ConsoleAppender in the above configuration. Then add JaninoEventEvaluator to EvaluatorFilter. If the user does not specify <evaluator>, Logback uses the JaninoEventEvaluator by default. This is a rare occurrence in Joran where the component type is not explicitly referred to.

Since the OnMismatch property is set to NEUTRAL and the OnMatch property is set to DENY, this conditional filter will drop all events that contain "billing".

 

The FilterEvents application makes 10 log requests, from 0 to 9. First, let's run FilterEvent without any filters:

java chapters.filters.FilterEvents src/main/java/chapters/filters/basicConfiguration.xml

 All requests will look like this:

 
0 [main] INFO chapters.filters.FilterEvents - logging statement 0
0 [main] INFO chapters.filters.FilterEvents - logging statement 1
0 [main] INFO chapters.filters.FilterEvents - logging statement 2
0 [main] DEBUG chapters.filters.FilterEvents - logging statement 3
0 [main] INFO chapters.filters.FilterEvents - logging statement 4
0 [main] INFO chapters.filters.FilterEvents - logging statement 5
0 [main] ERROR chapters.filters.FilterEvents - billing statement 6
0 [main] INFO chapters.filters.FilterEvents - logging statement 7
0 [main] INFO chapters.filters.FilterEvents - logging statement 8
0 [main] INFO chapters.filters.FilterEvents - logging statement 9

 Suppose we want to handle "billing statement". The basicEventEvaluator.xml filter mentioned above contains "billing", which is the exact result we want.

Run basicEventEvaluator.xml:

java chapters.filters.FilterEvents src/main/java/chapters/filters/basicEventEvaluator.xml

 We will get:

 
0 [main] INFO chapters.filters.FilterEvents - logging statement 0
0 [main] INFO chapters.filters.FilterEvents - logging statement 1
0 [main] INFO chapters.filters.FilterEvents - logging statement 2
0 [main] DEBUG chapters.filters.FilterEvents - logging statement 3
0 [main] INFO chapters.filters.FilterEvents - logging statement 4
0 [main] INFO chapters.filters.FilterEvents - logging statement 5
0 [main] INFO chapters.filters.FilterEvents - logging statement 7
0 [main] INFO chapters.filters.FilterEvents - logging statement 8
0 [main] INFO chapters.filters.FilterEvents - logging statement 9

 The predicate expression can be a java code block. For example, the following is a valid expression:

<evaluator>
  <expression>
    if(logger.startsWith("org.apache.http"))
      return true;

    if(mdc == null || mdc.get("entity") == null)
      return false;

    String payee = (String) mdc.get("entity");

    if(logger.equals("org.apache.http.wire") && <!-- & encoded as & -->
        payee.contains("someSpecialValue") &&
        !message.contains("someSecret")) {
      return true;
    }

    return false;
  </expression>
</evaluator>

 

Matchers

It is possible to call the matches() method of the String class for regular matching, but this will result in a compilation and a type object being generated every time filter is called. To avoid this problem, you can define one or more Matcher objects in advance. After defining a matcher, you can reuse objects by name in predicate expressions.

The following example illustrates this situation:

<configuration debug="true">

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
      <evaluator>        
        <matcher>
          <Name>odd</Name>
          <!-- filter out odd numbered statements -->
          <regex>statement [13579]</regex>
        </matcher>
        
        <expression>odd.matches(formattedMessage)</expression>
      </evaluator>
      <OnMismatch>NEUTRAL</OnMismatch>
      <OnMatch>DENY</OnMatch>
    </filter>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

 If you need to define more matchers, you can refer to adding more <matcher> elements above.

 

Reference document: https://logback.qos.ch/manual/filters.html

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326706013&siteId=291194637