Adapt to logging configuration and functional/behavior changes introduced in SLF4J 2.0.x and Logback 1.4.x.

This commit is contained in:
John Blum
2022-09-30 18:34:00 -07:00
parent 6f301e4627
commit 461bf5c3e0
7 changed files with 324 additions and 99 deletions

View File

@@ -44,6 +44,8 @@ import ch.qos.logback.core.Appender;
@SuppressWarnings("unused")
public abstract class LogbackSupport {
private static final Class<ch.qos.logback.classic.Logger> LOGBACK_LOGGER_TYPE = ch.qos.logback.classic.Logger.class;
private static final String STATIC_LOGGER_BINDER_CLASSNAME = "org.slf4j.impl.StaticLoggerBinder";
private static final boolean STATIC_LOGGER_BINDER_CLASS_PRESENT =
@@ -185,8 +187,8 @@ public abstract class LogbackSupport {
public static ch.qos.logback.classic.Logger requireLogbackRootLogger() {
return resolveRootLogger()
.filter(ch.qos.logback.classic.Logger.class::isInstance)
.map(ch.qos.logback.classic.Logger.class::cast)
.filter(LOGBACK_LOGGER_TYPE::isInstance)
.map(LOGBACK_LOGGER_TYPE::cast)
.orElseThrow(() -> new IllegalStateException(String.format(ILLEGAL_LOGGER_TYPE_EXCEPTION_MESSAGE,
ROOT_LOGGER_NAME, nullSafeTypeName(resolveRootLogger().orElse(null)))));
}

View File

@@ -18,15 +18,27 @@ package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.stream.StreamSupport;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.geode.logging.slf4j.logback.support.LogbackSupport;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.slf4j.ILoggerFactory;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,118 +46,287 @@ import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.status.StatusListener;
/**
* Abstract base class for testing the spring-geode-starter-logging and spring-gemfire-starter modules.
* Abstract base class for testing the spring-geode-starter-logging and spring-geode-starter modules.
*
* @author John Blum
* @see org.slf4j.Logger
* @see org.slf4j.LoggerFactory
* @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport
* @see org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender
* @see ch.qos.logback.classic.Level
* @see ch.qos.logback.classic.Logger
* @see ch.qos.logback.classic.LoggerContext
* @see ch.qos.logback.classic.util.ContextInitializer
* @see ch.qos.logback.core.Appender
* @since 1.3.0
*/
public abstract class AbstractLoggingIntegrationTests extends IntegrationTestsSupport {
@SuppressWarnings("unused")
public abstract class AbstractLoggingIntegrationTests {
private static final boolean STATUS_DEBUG_ENABLED = false;
private static final Class<ch.qos.logback.classic.Logger> LOGBACK_LOGGER_TYPE =
ch.qos.logback.classic.Logger.class;
private static final Function<? super ch.qos.logback.classic.Logger, Level> LOGBACK_LOGGER_LEVEL =
ch.qos.logback.classic.Logger::getLevel;
protected static final String APACHE_GEODE_LOGGER_NAME = "org.apache.geode";
protected static final String CONSOLE_APPENDER_NAME = "CONSOLE";
protected static final String DELEGATE_APPENDER_NAME = "delegate";
protected static final String SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY = "spring.boot.data.gemfire.log.level";
private Logger apacheGeodeLogger = LoggerFactory.getLogger(APACHE_GEODE_LOGGER_NAME);
private static TestAppender testAppender;
private TestAppender testAppender;
private static ch.qos.logback.classic.Logger assertLogbackLogger(org.slf4j.Logger slf4jLogger,
String loggerName, Level logLevel) {
protected TestAppender getTestAppender() {
assertThat(slf4jLogger).isInstanceOf(LOGBACK_LOGGER_TYPE);
assertThat(slf4jLogger.getName()).isEqualTo(loggerName);
assertThat(slf4jLogger)
.asInstanceOf(InstanceOfAssertFactories.type(LOGBACK_LOGGER_TYPE))
.extracting(LOGBACK_LOGGER_LEVEL)
.isEqualTo(logLevel);
assertThat(this.testAppender).describedAs("TestAppender could not be resolved").isNotNull();
return this.testAppender;
return LOGBACK_LOGGER_TYPE.cast(slf4jLogger);
}
protected Level getTestLogLevel() {
return Level.INFO;
private static LoggerContext assertLogbackLoggerConfiguration(LoggerContext loggerContext,
String... loggerNames) {
List<String> configuredLoggerNames = CollectionUtils.nullSafeList(loggerContext.getLoggerList()).stream()
.filter(Objects::nonNull)
.map(Logger::getName)
.filter(StringUtils::hasText)
.toList();
assertThat(configuredLoggerNames).contains(loggerNames);
return loggerContext;
}
public void assertApacheGeodeLoggerLogLevel(Level logLevel) {
private static ch.qos.logback.classic.Logger assertLogbackLoggerAppenderConfiguration(
ch.qos.logback.classic.Logger logbackLogger, String... appenderNames) {
Optional.ofNullable(this.apacheGeodeLogger)
.filter(ch.qos.logback.classic.Logger.class::isInstance)
.map(ch.qos.logback.classic.Logger.class::cast)
.map(logger -> {
assertThat(logger.getLevel()).isEqualTo(logLevel);
return logger;
})
.orElseThrow(() -> newIllegalStateException("'org.apache.geode' Logger not found"));
Spliterator<Appender<?>> configuredTestLoggerAppenders =
Spliterators.spliteratorUnknownSize(logbackLogger.iteratorForAppenders(), Spliterator.NONNULL);
List<String> configuredTestLoggerAppenderNames =
StreamSupport.stream(configuredTestLoggerAppenders, false)
.filter(Objects::nonNull)
.map(Appender::getName)
.filter(StringUtils::hasText)
.toList();
assertThat(configuredTestLoggerAppenderNames).contains(appenderNames);
return logbackLogger;
}
@Before
public void setup() {
private static void log(String message, Object... args) {
System.err.printf(message, args);
System.err.flush();
}
configureLogging();
private static ch.qos.logback.classic.Logger logConfiguredAppenders(ch.qos.logback.classic.Logger logger) {
log("Configured Appenders: ");
Spliterator<Appender<?>> appenders =
Spliterators.spliteratorUnknownSize(logger.iteratorForAppenders(), Spliterator.NONNULL);
log("%s%n", Arrays.toString(StreamSupport.stream(appenders, false)
.filter(Objects::nonNull)
.map(Appender::getName)
.filter(StringUtils::hasText)
.toArray(String[]::new)));
return logger;
}
private static LoggerContext logConfiguredLoggers(LoggerContext loggerContext) {
log("Configured Loggers: ");
log("%s%n", Arrays.toString(loggerContext.getLoggerList().stream()
.filter(Objects::nonNull)
.map(Logger::getName)
.filter(StringUtils::hasText)
.toArray(String[]::new)));
return loggerContext;
}
protected static void setupLogback(Level testLogLevel) {
configureLogging(testLogLevel);
configureRootLoggerDelegatingAppender();
logMessages();
}
private void configureLogging() {
System.setProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY, getTestLogLevel().toString());
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
assertThat(loggerFactory).isInstanceOf(LoggerContext.class);
LoggerContext loggerContext = (LoggerContext) loggerFactory;
@SuppressWarnings("all")
private static void configureLogging(Level testLogLevel) {
try {
new ContextInitializer(loggerContext).autoConfig();
System.setProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY, testLogLevel.toString());
LogbackSupport.resetLogback();
LoggerContext loggerContext = LogbackSupport.requireLoggerContext();
initializeLogback(loggerContext)
.getStatusManager().add(LoggingStatusListener.create().debug(STATUS_DEBUG_ENABLED));
logConfiguredLoggers(loggerContext);
assertLogbackLoggerConfiguration(loggerContext,
Logger.ROOT_LOGGER_NAME, APACHE_GEODE_LOGGER_NAME, "com.gemstone.gemfire", "org.jgroups");
ch.qos.logback.classic.Logger logbackOrgApacheGeodeLogger = assertLogbackLogger(loggerContext
.getLogger(APACHE_GEODE_LOGGER_NAME), APACHE_GEODE_LOGGER_NAME, testLogLevel);
logConfiguredAppenders(logbackOrgApacheGeodeLogger);
assertLogbackLoggerAppenderConfiguration(logbackOrgApacheGeodeLogger,
CONSOLE_APPENDER_NAME, DELEGATE_APPENDER_NAME);
}
catch (Exception cause) {
throw newIllegalStateException("Failed to configure and initialize SLF4J/Logback logging context", cause);
throw newIllegalStateException("Failed to configure and initialize SLF4J/Logback", cause);
}
}
private static LoggerContext initializeLogback(LoggerContext loggerContext) throws JoranException {
new ContextInitializer(loggerContext)
.autoConfig(ClassUtils.getDefaultClassLoader());
return loggerContext;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void configureRootLoggerDelegatingAppender() {
private static void configureRootLoggerDelegatingAppender() {
Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
ch.qos.logback.classic.Logger logbackRootLogger =
assertLogbackLogger(LogbackSupport.requireLogbackRootLogger(), Logger.ROOT_LOGGER_NAME, Level.ERROR);
assertThat(rootLogger).isInstanceOf(ch.qos.logback.classic.Logger.class);
assertThat(logbackRootLogger.getAppender(CONSOLE_APPENDER_NAME)).isNull();
ch.qos.logback.classic.Logger logbackRootLogger = (ch.qos.logback.classic.Logger) rootLogger;
Appender<?> delegateAppender = logbackRootLogger.getAppender("delegate");
Appender<?> delegateAppender = logbackRootLogger.getAppender(DELEGATE_APPENDER_NAME);
assertThat(delegateAppender).isNotNull();
assertThat(delegateAppender.getName()).isEqualTo("delegate");
assertThat(delegateAppender.getName()).isEqualTo(DELEGATE_APPENDER_NAME);
this.testAppender = new TestAppender();
this.testAppender.start();
TestAppender testAppender = initializeTestAppender();
((DelegatingAppender) delegateAppender).setAppender(this.testAppender);
((DelegatingAppender) delegateAppender).setAppender(testAppender);
assertThat(((DelegatingAppender) delegateAppender).getAppender()).isSameAs(this.testAppender);
assertThat(delegateAppender)
.asInstanceOf(InstanceOfAssertFactories.type(DelegatingAppender.class))
.extracting(DelegatingAppender::getAppender)
.isSameAs(testAppender);
}
public void logMessages() {
private static TestAppender initializeTestAppender() {
testAppender = new TestAppender();
testAppender.start();
return testAppender;
}
@AfterClass
public static void cleanupTestContext() {
System.clearProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY);
Optional.ofNullable(testAppender).ifPresent(TestAppender::clear);
}
private Logger apacheGeodeLogger;
@Before
public void initializeApacheGeodeLoggerReference() {
this.apacheGeodeLogger = LoggerFactory.getLogger(APACHE_GEODE_LOGGER_NAME);
}
protected TestAppender getTestAppender() {
assertThat(testAppender)
.describedAs("TestAppender could not be resolved")
.isNotNull();
return testAppender;
}
protected void assertApacheGeodeLoggerLogLevel(Level logLevel) {
Optional.ofNullable(this.apacheGeodeLogger)
.filter(LOGBACK_LOGGER_TYPE::isInstance)
.map(LOGBACK_LOGGER_TYPE::cast)
.map(logger -> {
assertThat(logger.getName()).isEqualTo(APACHE_GEODE_LOGGER_NAME);
assertThat(logger.getLevel()).isEqualTo(logLevel);
return logger;
})
.orElseThrow(() -> newIllegalStateException("'%s' Logger not found", APACHE_GEODE_LOGGER_NAME));
}
protected void assertLogMessages(String... logMessages) {
logMessages();
Arrays.stream(ArrayUtils.nullSafeArray(logMessages, String.class)).forEach(logMessage ->
assertThat(getTestAppender().lastLogMessage()).isEqualTo(logMessage));
assertThat(getTestAppender().lastLogMessage()).isNull();
}
protected void logMessages() {
assertThat(this.apacheGeodeLogger).isNotNull();
this.apacheGeodeLogger.trace("TRACE TEST");
this.apacheGeodeLogger.debug("DEBUG TEST");
this.apacheGeodeLogger.info("INFO TEST");
this.apacheGeodeLogger.warn("WARN TEST");
this.apacheGeodeLogger.error("ERROR TEST");
}
@After
public void tearDown() {
protected static class LoggingStatusListener implements StatusListener {
Optional.ofNullable(this.testAppender).ifPresent(it -> {
it.clear();
it.stop();
});
protected static LoggingStatusListener create() {
return new LoggingStatusListener();
}
System.clearProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY);
private volatile boolean debug = false;
protected boolean isDebugging() {
return this.debug;
}
@Override
public void addStatusEvent(Status status) {
if (isDebugging()) {
logStatus(status);
}
}
protected LoggingStatusListener debug(boolean debug) {
this.debug = debug;
return this;
}
private void logStatus(Status status) {
log("[STATUS] %s: %s%n", status.getOrigin(), status.getMessage());
if (status.hasChildren()) {
for (Status child : CollectionUtils.iterable(status.iterator())) {
logStatus(child);
}
}
}
}
}

View File

@@ -15,12 +15,9 @@
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import ch.qos.logback.classic.Level;
/**
@@ -40,24 +37,20 @@ import ch.qos.logback.classic.Level;
*/
public class DebugLoggingIntegrationTests extends AbstractLoggingIntegrationTests {
@Override
protected Level getTestLogLevel() {
return Level.DEBUG;
private static final Level TEST_LOG_LEVEL = Level.DEBUG;
@BeforeClass
public static void setupLogback() {
setupLogback(TEST_LOG_LEVEL);
}
@Test
public void logLevelIsSetToDebug() {
assertApacheGeodeLoggerLogLevel(Level.DEBUG);
assertApacheGeodeLoggerLogLevel(TEST_LOG_LEVEL);
}
@Test
public void logMessagesAtDebug() {
TestAppender testAppender = getTestAppender();
assertThat(testAppender.lastLogMessage()).isEqualTo("ERROR TEST");
assertThat(testAppender.lastLogMessage()).isEqualTo("INFO TEST");
assertThat(testAppender.lastLogMessage()).isEqualTo("DEBUG TEST");
assertThat(testAppender.lastLogMessage()).isNull();
assertLogMessages("ERROR TEST", "WARN TEST", "INFO TEST", "DEBUG TEST");
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.springframework.geode.logging.slf4j.logback;
import org.junit.BeforeClass;
import org.junit.Test;
import ch.qos.logback.classic.Level;
/**
* Integration Tests testing the {@literal org.apache.geode} {@link org.slf4j.Logger}
* with the log level {@link Level#ERROR}.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender
* @see org.springframework.geode.logging.slf4j.logback.ErrorLoggingIntegrationTests
* @see ch.qos.logback.classic.Level#ERROR
* @since 2.0.0
*/
public class ErrorLoggingIntegrationTests extends AbstractLoggingIntegrationTests {
private static final Level TEST_LOG_LEVEL = Level.ERROR;
@BeforeClass
public static void setupLogback() {
setupLogback(TEST_LOG_LEVEL);
}
@Test
public void logLevelIsSetToDebug() {
assertApacheGeodeLoggerLogLevel(TEST_LOG_LEVEL);
}
@Test
public void logMessagesAtDebug() {
assertLogMessages("ERROR TEST");
}
}

View File

@@ -15,12 +15,9 @@
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import ch.qos.logback.classic.Level;
/**
@@ -36,18 +33,20 @@ import ch.qos.logback.classic.Level;
*/
public class InfoLoggingIntegrationTests extends AbstractLoggingIntegrationTests {
private static final Level TEST_LOG_LEVEL = Level.INFO;
@BeforeClass
public static void setupLogback() {
setupLogback(TEST_LOG_LEVEL);
}
@Test
public void logLevelIsSetToInfo() {
assertApacheGeodeLoggerLogLevel(Level.INFO);
assertApacheGeodeLoggerLogLevel(TEST_LOG_LEVEL);
}
@Test
public void logsMessagesAtInfo() {
TestAppender testAppender = getTestAppender();
assertThat(testAppender.lastLogMessage()).isEqualTo("ERROR TEST");
assertThat(testAppender.lastLogMessage()).isEqualTo("INFO TEST");
assertThat(testAppender.lastLogMessage()).isNull();
assertLogMessages("ERROR TEST", "WARN TEST", "INFO TEST");
}
}

View File

@@ -15,12 +15,9 @@
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import ch.qos.logback.classic.Level;
/**
@@ -40,21 +37,20 @@ import ch.qos.logback.classic.Level;
*/
public class NoLoggingIntegrationTests extends AbstractLoggingIntegrationTests {
@Override
protected Level getTestLogLevel() {
return Level.OFF;
private static final Level TEST_LOG_LEVEL = Level.OFF;
@BeforeClass
public static void setupLogback() {
setupLogback(TEST_LOG_LEVEL);
}
@Test
public void logLevelIsSetToOff() {
assertApacheGeodeLoggerLogLevel(Level.OFF);
assertApacheGeodeLoggerLogLevel(TEST_LOG_LEVEL);
}
@Test
public void logsNoMessages() {
TestAppender testAppender = getTestAppender();
assertThat(testAppender.lastLogMessage()).isNull();
assertLogMessages();
}
}

View File

@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<variable name="bootPresent" value="false"/>
<include resource="org/springframework/geode/logging/slf4j/logback/properties-include.xml"/>
<include resource="org/springframework/geode/logging/slf4j/logback/appender-include.xml"/>
<include resource="org/springframework/geode/logging/slf4j/logback/logger-include.xml"/>