Hi Volkan, Thank you for your review.
Later, I would like to have our whole build check all tests for Hibernate warnings. Today, I'm taking a first step. The second step is to have another Maven module's tests perform this check. Recasting the solution as a JUnit 5 extension is a good idea, but not strictly necessary IMO. I am not fond of JUnit 5 having dropped a bomb by not making all JUnit 4 Rules run on 5, so I am reticent to buy into another extension framework they could leave behind in version 6. Gary On Wed, Oct 9, 2024 at 11:08 AM Volkan Yazıcı <vol...@yazi.ci.invalid> wrote: > Can you solve your problem by writing a JUnit extension (or > `@BeforeAll`/`@AfterAll`) that registers your custom filter to the active > logger context and removes it after the test? That is, > > @FailOnLog4jEvent(level = Level.WARN, namePrefix = "org.hibernate.") > class SomeHibernateTest { > // ... `@Test`-annotated methods ... > } > > > If this is only one test, you can even make it simpler: > > class SomeHibernateTest { > > @BeforeAll > static void registerLog4jEventBan() { > // ... > } > > @AfterAll > static void removeLog4jEventBan() { > } > > // ... `@Test`-annotated methods ... > > } > > > Maybe it is me, but I am not able to see the necessity to add a new > `Result` type, in particular, given you stated *"I only want this check on > this one test class"*. > > On Wed, Oct 9, 2024 at 4:34 PM Gary D. Gregory <ggreg...@apache.org> > wrote: > > > 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); > > } > > > > } > > >