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