Skip to content

Commit

Permalink
Fix test errors due to missing Logback appenders (#459)
Browse files Browse the repository at this point in the history
* Update InMemoryAppenderExtension so that it will reset
  the Logback logging system if it doesn't find the expected
  appenders. It only tries once, which will fix the case when
  a previously-executed test corrupts the logging system,
  like DropwizardAppExtension and DropwizardClientExtension
  both do by stopping and detaching ALL appenders.

Fixes #457
  • Loading branch information
sleberknight authored Feb 6, 2024
1 parent c5f45cf commit 9da30f7
Showing 1 changed file with 65 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package org.kiwiproject.test.logback;

import static java.util.Objects.nonNull;
import static org.assertj.core.api.Assertions.assertThat;

import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.joran.spi.JoranException;
import com.google.common.annotations.Beta;
import com.google.common.io.Resources;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand All @@ -28,6 +37,9 @@ public class InMemoryAppenderExtension implements BeforeEachCallback, AfterEachC
private final Class<?> loggerClass;
private final String appenderName;

// Use the default Logback test configuration file as our default value.
private String logbackConfigFilePath = ClassicConstants.TEST_AUTOCONFIG_FILE;

@Getter
@Accessors(fluent = true)
private InMemoryAppender appender;
Expand Down Expand Up @@ -67,7 +79,7 @@ public InMemoryAppenderExtension(Class<?> loggerClass) {
* {@code appenderName}.
* <p>
* This appender <em>must</em> exist, e.g., in your {@code logback-test.xml}
* configuration file.}
* configuration file.
* <p>
* For example, for a test named {@code MyAwesomeTest}, the
* {@code src/test/resources/logback-test.xml} must contain:
Expand All @@ -93,6 +105,26 @@ public InMemoryAppenderExtension(Class<?> loggerClass, String appenderName) {
this.appenderName = appenderName;
}

/**
* The Logback configuration to use if the logging system needs to be reset.
* <p>
* For example:
* <pre>
* {@literal @}RegisterExtension
* private final InMemoryAppenderExtension inMemoryAppenderExtension =
* new InMemoryAppenderExtension(InMemoryAppenderTest.class)
* .withLogbackConfigFilePath("acme-logback-test.xml");
* </pre>
*
* @param logbackConfigFilePath
* @return this extension, so this can be chained after the constructor
* @see https://github.com/kiwiproject/kiwi-test/issues/457
*/
public InMemoryAppenderExtension withLogbackConfigFilePath(String logbackConfigFilePath) {
this.logbackConfigFilePath = logbackConfigFilePath;
return this;
}

/**
* Exposes the {@link InMemoryAppender} associated with {@code loggerClass}.
* It can be obtained via the {@link #appender()} method. Usually, tests
Expand Down Expand Up @@ -121,9 +153,9 @@ public InMemoryAppenderExtension(Class<?> loggerClass, String appenderName) {
* @param context the current extension context; never {@code null}
*/
@Override
public void beforeEach(ExtensionContext context) {
public void beforeEach(ExtensionContext context) throws Exception {
var logbackLogger = (Logger) LoggerFactory.getLogger(loggerClass);
var rawAppender = logbackLogger.getAppender(appenderName);
var rawAppender = getAppender(logbackLogger);

assertThat(rawAppender)
.describedAs("Expected an appender named '%s' for logger '%s' of type %s",
Expand All @@ -132,6 +164,36 @@ public void beforeEach(ExtensionContext context) {
appender = (InMemoryAppender) rawAppender;
}

@Nullable
@SuppressWarnings("java:S106")
private Appender<ILoggingEvent> getAppender(Logger logbackLogger) throws JoranException {
var rawAppender = logbackLogger.getAppender(appenderName);

if (nonNull(rawAppender)) {
return rawAppender;
}

// Write to stdout since the logging system might be hosed...
System.out.printf(
"Appender %s not found on logger %s; attempt fix by resetting logging configuration using config: %s%n",
appenderName, loggerClass, logbackConfigFilePath);
System.out.println("You can customize the logging configuration using #withLogbackConfigFilePath");

// Reset the Logback logging system
var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();

var joranConfigurator = new JoranConfigurator();
joranConfigurator.setContext(loggerContext);
var logbackConfigUrl = Resources.getResource(logbackConfigFilePath);
joranConfigurator.doConfigure(logbackConfigUrl);
loggerContext.start();

// Try again and return whatever we get. It should not be null after resetting, unless
// the reset failed, or the appender was not configured correctly.
return logbackLogger.getAppender(appenderName);
}

/**
* Clears all logging events from the {@link InMemoryAppender} to ensure each
* test starts with an empty appender.
Expand Down

0 comments on commit 9da30f7

Please sign in to comment.