Hi All:

As a user, I want to programmatically configure Log4j to throw an exception 
when a specific component logs a WARN event.

Background: I am working with a large and complex stack that include Hibernate 
in the mix. I just fixed a bug that, that as a side-effect, caused Hibernate to 
log WARN events. As a check on possible regressions, I want to make sure that a 
test class fails when Hibernate logs a WARN event. This whole Maven module 
shares a log4j configuration file and, for now, I only want this check on this 
one test class.

My current implementation uses a custom filter called ThrowingThresholdFilter 
[see end of message], a copy of our ThresholdFilter that throws a subclass of 
LoggingException called FilterLoggingException when the configured Level is 
matched.

I also have happen to have other checks with other custom filters: 
ThrowingLevelFilter and ThrowingStringMatchFilter. 

The only change in the configuration file is the use of the “ignoreExceptions” 
attribute to a Console Appender.

The test contains:
private static final Filter THROWING_FILTER = 
ThrowingThresholdFilter.createFilter(Level.WARN, Result.NEUTRAL);
    @SuppressWarnings("resource")
    @BeforeAll
    static void beforeAddFilter() {
        
LoggerContext.getContext(false).getLogger("org.hibernate").addFilter(THROWING_FILTER);
    }

My proposal is to allow a user to _not_ define any custom filters by reusing a 
new Result enum value called “Throw”. When a filter returns “Throw”, then Log4j 
throws a new LoggingException subclass called FilterLoggingException.

Then my test can replace:

private static final Filter THROWING_FILTER = 
ThrowingThresholdFilter.createFilter(Level.WARN, Result.NEUTRAL);

and drop all custom filters.

With:

private static final Filter THROWING_FILTER = 
ThresholdFilter.createFilter(Level.WARN, Result.NEUTRAL);

WDYT?

Gary

package my.company;

import java.util.Objects;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LoggingException;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.filter.AbstractFilter;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.PerformanceSensitive;

/**
 * This filter returns the onMatch result if the level in the {@link LogEvent} 
is the same or more specific
 * than the configured level and the {@code onMismatch} value otherwise. For 
example, if the ThresholdFilter
 * is configured with Level {@code ERROR} and the LogEvent contains Level 
{@code DEBUG} then the {@code onMismatch} value will
 * be returned since {@code ERROR} events are more specific than {@code DEBUG}.
 * <p>
 * The default Level is {@code ERROR}.
 * </p>
 *
 * @see Level#isMoreSpecificThan(Level)
 */
@Plugin(name = "ThrowingThresholdFilter", category = Node.CATEGORY, elementType 
= Filter.ELEMENT_TYPE, printObject = true)
@PerformanceSensitive("allocation")
public final class ThrowingThresholdFilter extends AbstractFilter {

    public static class FilterLoggingException extends LoggingException {

        private static final long serialVersionUID = 1L;

        public FilterLoggingException(String message) {
            super(message);
        }
        
    }

    private final Level level;

    private ThrowingThresholdFilter(final Level level, final Result onMismatch) 
{
        super(Result.NEUTRAL, onMismatch);
        this.level = level;
    }

    @Override
    public Result filter(final Logger logger, final Level testLevel, final 
Marker marker, final String msg,
                         final Object... params) {
        return filter(testLevel);
    }

    @Override
    public Result filter(final Logger logger, final Level testLevel, final 
Marker marker, final Object msg,
                         final Throwable t) {
        return filter(testLevel);
    }

    @Override
    public Result filter(final Logger logger, final Level testLevel, final 
Marker marker, final Message msg,
                         final Throwable t) {
        return filter(testLevel);
    }

    @Override
    public Result filter(final LogEvent event) {
        return filter(event.getLevel());
    }

    private Result filter(final Level testLevel) {
        if (testLevel.isMoreSpecificThan(this.level)) {
            throw new FilterLoggingException(Objects.toString(testLevel));
        }
        return onMismatch;
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2, final Object p3) 
{
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2, final Object p3,
            final Object p4) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2, final Object p3,
            final Object p4, final Object p5) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2, final Object p3,
            final Object p4, final Object p5, final Object p6) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2, final Object p3,
            final Object p4, final Object p5, final Object p6,
            final Object p7) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2, final Object p3,
            final Object p4, final Object p5, final Object p6,
            final Object p7, final Object p8) {
        return filter(level);
    }

    @Override
    public Result filter(final Logger logger, final Level level, final Marker 
marker, final String msg,
            final Object p0, final Object p1, final Object p2, final Object p3,
            final Object p4, final Object p5, final Object p6,
            final Object p7, final Object p8, final Object p9) {
        return filter(level);
    }

    public Level getLevel() {
        return level;
    }

    @Override
    public String toString() {
        return level.toString();
    }

    /**
     * Creates a ThrowingThresholdFilter.
     * @param level The log Level.
     * @param mismatch The action to take on a mismatch.
     * @return The created ThrowingThresholdFilter.
     */
    // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
    @PluginFactory
    public static ThrowingThresholdFilter createFilter(
            @PluginAttribute("level") final Level level,
            @PluginAttribute("onMismatch") final Result mismatch) {
        final Level actualLevel = level == null ? Level.ERROR : level;
        final Result onMismatch = mismatch == null ? Result.DENY : mismatch;
        return new ThrowingThresholdFilter(actualLevel, onMismatch);
    }

}

Reply via email to