I am running unit tests and sometimes getting this error message below. How can I poll Logger to prevent bad random tests?
Someone mentioned comment in this answer link, https://stackoverflow.com/a/51812144/15435022
Comment: Plus note that LoggerFactory.getLogger() might return an org.slf4j.helpers.SubstituteLogger instance despite the usage of Logback while the logging is still initializing. You might need to poll for ch.qos.logback.classic.Logger up to a couple of milliseconds if you don't want flaky tests :)
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
@Test
public void testData()
Logger logger = (Logger) LoggerFactory.getLogger(ProfileEventHandler.class);
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.start();
logger.addAppender(listAppender);
Error Returned:
java.lang.ClassCastException: class org.slf4j.helpers.SubstituteLogger cannot be cast to class ch.qos.logback.classic.Logger (org.slf4j.helpers.SubstituteLogger and ch.qos.logback.classic.Logger are in unnamed module of loader 'app')
Note: This answer did not resolve issue, since its not using list appender org.slf4j.helpers.SubstituteLogger cannot be cast to ch.qos.logback.classic.Logger
If I understand correctly what that commentor meant and how SubstituteLogger
works during and after initialization, then the most straightforward approach would be to loop a set number of times, with a delay between each iteration, and check the type of the logger. If the logger is the correct type, return it. Otherwise, if you exhaust the loop, throw an exception to fail the test.
Here's an example. Not sure which unit test framework you're using, but the example assumes JUnit 5.
import static org.junit.jupiter.api.Assertions.fail;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
class FooTests {
private static final int LOGBACK_POLL_ATTEMPTS = 5;
private static final Duration LOGBACK_POLL_DELAY = Duration.ofMillis(10);
private static ch.qos.logback.classic.Logger getLogbackLogger(Class<?> cl) {
try {
org.slf4j.Logger slf4jLogger = null;
for (int i = 0; i < LOGBACK_POLL_ATTEMPTS; i++) {
slf4jLogger = LoggerFactory.getLogger(cl);
if (slf4jLogger instanceof ch.qos.logback.classic.Logger logbackLogger) {
return logbackLogger;
}
// 'sleep(Duration)' added in Java 19; use 'sleep(long)' if needed
Thread.sleep(LOGBACK_POLL_DELAY);
}
fail("SLF4J never returned a Logback logger. Last returned = " + slf4jLogger);
} catch (InterruptedException ex) {
fail("Thread interrupted while polling for Logback logger", ex);
}
throw new Error("unreachable code"); // satisfy compiler
}
@Test
void testData() {
var logger = getLogbackLogger(ProfileEventHandler.class);
// rest of test
}
}
Whether or not Assertions::fail
is the correct way to fail the test is up to you. Perhaps it would be better to use Assumptions::abort
or to directly throw a different exception. Or maybe a combination thereof.