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

Reply via email to