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