Skip to content

Commit

Permalink
Revise logging in the TestContext framework
Browse files Browse the repository at this point in the history
For a Spring Boot test, Logback logs at DEBUG level by default until
Spring Boot's logging infrastructure has a chance to take control, and
this can result in a considerable amount of INFO and DEBUG output.

It's not the number of lines that causes an issue. Rather, it's the
amount of information in each line that can become overwhelming,
especially when the MergedContextConfiguration is complex.

The main reason for lengthy DEBUG logging in a Spring Boot test is that
Spring Boot's ImportsContextCustomizer implements toString() which
results in logging of the fully qualified class names of all imported
configuration classes. In an example using @WebMvcTest, this resulted
in logging of 27 imported classes twice. However, the lists of
registered TestExecutionListener, ContextCustomizerFactory, and
ContextCustomizer implementations are also logged separately, and that
adds quite a bit of noise at DEBUG level.

This commit addresses these issues and simultaneously completely
revises logging within the Spring TestContext Framework (TCF).

- Numerous log statements are now logged at TRACE level instead of
  DEBUG.

- Numerous log statements now have two modes. When logging at TRACE
  level, fully qualified class names (or the results of invoking
  toString() on related components) are included. When logging at DEBUG
  level, simple names of classes are logged and the results of invoking
  toString() on related components are omitted.

- toString() implementations in TCF components are now based on the
  newly introduced SimpleValueStyler for ToStringCreator.

The combination of the above changes greatly reduces the amount of
DEBUG logging output in the TCF, but users can still access complete
details by switching to TRACE level logging.

Closes gh-29229
  • Loading branch information
sbrannen committed Oct 25, 2022
1 parent f16366e commit 997dd3e
Show file tree
Hide file tree
Showing 32 changed files with 335 additions and 195 deletions.
Expand Up @@ -82,7 +82,7 @@ static BootstrapContext createBootstrapContext(Class<?> testClass) {
ClassUtils.forName(className, BootstrapUtils.class.getClassLoader());
Constructor<? extends BootstrapContext> constructor =
clazz.getConstructor(Class.class, CacheAwareContextLoaderDelegate.class);
logger.debug(LogMessage.format("Instantiating BootstrapContext using constructor [%s]", constructor));
logger.trace(LogMessage.format("Instantiating BootstrapContext using constructor [%s]", constructor));
return BeanUtils.instantiateClass(constructor, testClass, cacheAwareContextLoaderDelegate);
}
catch (Throwable ex) {
Expand All @@ -97,7 +97,7 @@ private static CacheAwareContextLoaderDelegate createCacheAwareContextLoaderDele
try {
clazz = (Class<? extends CacheAwareContextLoaderDelegate>)
ClassUtils.forName(className, BootstrapUtils.class.getClassLoader());
logger.debug(LogMessage.format("Instantiating CacheAwareContextLoaderDelegate from class [%s]", className));
logger.trace(LogMessage.format("Instantiating CacheAwareContextLoaderDelegate from class [%s]", className));
return BeanUtils.instantiateClass(clazz, CacheAwareContextLoaderDelegate.class);
}
catch (Throwable ex) {
Expand Down Expand Up @@ -151,7 +151,7 @@ static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext b
if (clazz == null) {
clazz = resolveDefaultTestContextBootstrapper(testClass);
}
logger.debug(LogMessage.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]",
logger.trace(LogMessage.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]",
testClass.getName(), clazz.getName()));
TestContextBootstrapper testContextBootstrapper =
BeanUtils.instantiateClass(clazz, TestContextBootstrapper.class);
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
Expand All @@ -23,6 +23,8 @@

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -368,15 +370,15 @@ public int hashCode() {
*/
@Override
public String toString() {
return new ToStringCreator(this)
.append("declaringClass", this.declaringClass.getName())
.append("classes", ObjectUtils.nullSafeToString(this.classes))
.append("locations", ObjectUtils.nullSafeToString(this.locations))
return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler()))
.append("declaringClass", this.declaringClass)
.append("classes", this.classes)
.append("locations", this.locations)
.append("inheritLocations", this.inheritLocations)
.append("initializers", ObjectUtils.nullSafeToString(this.initializers))
.append("initializers", this.initializers)
.append("inheritInitializers", this.inheritInitializers)
.append("name", this.name)
.append("contextLoaderClass", this.contextLoaderClass.getName())
.append("contextLoaderClass", this.contextLoaderClass)
.toString();
}

Expand Down
Expand Up @@ -24,6 +24,8 @@

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -495,16 +497,16 @@ public int hashCode() {
*/
@Override
public String toString() {
return new ToStringCreator(this)
return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler()))
.append("testClass", this.testClass)
.append("locations", ObjectUtils.nullSafeToString(this.locations))
.append("classes", ObjectUtils.nullSafeToString(this.classes))
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(this.contextInitializerClasses))
.append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles))
.append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations))
.append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties))
.append("locations", this.locations)
.append("classes", this.classes)
.append("contextInitializerClasses", this.contextInitializerClasses)
.append("activeProfiles", this.activeProfiles)
.append("propertySourceLocations", this.propertySourceLocations)
.append("propertySourceProperties", this.propertySourceProperties)
.append("contextCustomizers", this.contextCustomizers)
.append("contextLoader", nullSafeClassName(this.contextLoader))
.append("contextLoader", (this.contextLoader != null ? this.contextLoader.getClass() : null))
.append("parent", this.parent)
.toString();
}
Expand Down
Expand Up @@ -31,6 +31,8 @@
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration;
Expand Down Expand Up @@ -585,9 +587,9 @@ public Set<T> findAllLocalMergedAnnotations() {
*/
@Override
public String toString() {
return new ToStringCreator(this)
.append("rootDeclaringClass", this.rootDeclaringClass.getName())
.append("declaringClass", this.declaringClass.getName())
return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler()))
.append("rootDeclaringClass", this.rootDeclaringClass)
.append("declaringClass", this.declaringClass)
.append("annotation", this.annotation)
.toString();
}
Expand Down
Expand Up @@ -123,7 +123,7 @@ private CodeBlock generateContextInitializersMappingCode() {
code.addStatement("$T map = new $T<>()", CONTEXT_INITIALIZER_SUPPLIER_MAP, HashMap.class);
this.initializerClassMappings.forEach((className, testClasses) -> {
List<String> testClassNames = testClasses.stream().map(Class::getName).toList();
logger.debug(LogMessage.format(
logger.trace(LogMessage.format(
"Generating mapping from AOT context initializer supplier [%s] to test classes %s",
className.reflectionName(), testClassNames));
testClassNames.forEach(testClassName ->
Expand All @@ -146,7 +146,7 @@ private CodeBlock generateContextInitializerClassesMappingCode() {
code.addStatement("$T map = new $T<>()", CONTEXT_INITIALIZER_CLASS_MAP, HashMap.class);
this.initializerClassMappings.forEach((className, testClasses) -> {
List<String> testClassNames = testClasses.stream().map(Class::getName).toList();
logger.debug(LogMessage.format(
logger.trace(LogMessage.format(
"Generating mapping from AOT context initializer class [%s] to test classes %s",
className.reflectionName(), testClassNames));
testClassNames.forEach(testClassName ->
Expand Down
Expand Up @@ -19,6 +19,8 @@
import java.util.Collections;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
Expand Down Expand Up @@ -82,9 +84,9 @@ public int hashCode() {

@Override
public String toString() {
return new ToStringCreator(this)
.append("testClass", getTestClass().getName())
.append("contextInitializerClass", this.contextInitializerClass.getName())
return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler()))
.append("testClass", getTestClass())
.append("contextInitializerClass", this.contextInitializerClass)
.append("original", this.original)
.toString();
}
Expand Down
Expand Up @@ -23,7 +23,6 @@
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.log.LogMessage;
import org.springframework.lang.Nullable;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.ApplicationContextFailureProcessor;
Expand Down Expand Up @@ -108,8 +107,8 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedContextCo
else {
context = loadContextInternal(mergedContextConfiguration);
}
if (logger.isDebugEnabled()) {
logger.debug("Storing ApplicationContext [%s] in cache under key %s".formatted(
if (logger.isTraceEnabled()) {
logger.trace("Storing ApplicationContext [%s] in cache under key %s".formatted(
System.identityHashCode(context), mergedContextConfiguration));
}
this.contextCache.put(mergedContextConfiguration, context);
Expand All @@ -135,8 +134,8 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedContextCo
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Retrieved ApplicationContext [%s] from cache with key %s".formatted(
if (logger.isTraceEnabled()) {
logger.trace("Retrieved ApplicationContext [%s] from cache with key %s".formatted(
System.identityHashCode(context), mergedContextConfiguration));
}
}
Expand Down Expand Up @@ -198,7 +197,15 @@ protected ApplicationContext loadContextInAotMode(AotMergedContextConfiguration
() -> "Failed to load AOT ApplicationContextInitializer for test class [%s]"
.formatted(testClass.getName()));
ContextLoader contextLoader = getContextLoader(aotMergedConfig);
logger.info(LogMessage.format("Loading ApplicationContext in AOT mode for %s", aotMergedConfig.getOriginal()));

if (logger.isTraceEnabled()) {
logger.trace("Loading ApplicationContext for AOT runtime for " + aotMergedConfig.getOriginal());
}
else if (logger.isDebugEnabled()) {
logger.debug("Loading ApplicationContext for AOT runtime for test class " +
aotMergedConfig.getTestClass().getName());
}

if (!((contextLoader instanceof AotContextLoader aotContextLoader) &&
(aotContextLoader.loadContextForAotRuntime(aotMergedConfig.getOriginal(), contextInitializer)
instanceof GenericApplicationContext gac))) {
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
Expand All @@ -21,6 +21,8 @@

import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -210,7 +212,7 @@ ErrorMode getErrorMode() {
*/
@Override
public String toString() {
return new ToStringCreator(this)
return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler()))
.append("dataSource", this.dataSource)
.append("transactionManager", this.transactionManager)
.append("transactionMode", this.transactionMode)
Expand Down
Expand Up @@ -247,9 +247,13 @@ private void executeSqlScripts(
}

MergedSqlConfig mergedSqlConfig = new MergedSqlConfig(sql.config(), testContext.getTestClass());
if (logger.isDebugEnabled()) {
logger.debug(String.format("Processing %s for execution phase [%s] and test context %s.",
mergedSqlConfig, executionPhase, testContext));
if (logger.isTraceEnabled()) {
logger.trace("Processing %s for execution phase [%s] and test context %s"
.formatted(mergedSqlConfig, executionPhase, testContext));
}
else if (logger.isDebugEnabled()) {
logger.debug("Processing merged @SqlConfig attributes for execution phase [%s] and test class [%s]"
.formatted(executionPhase, testContext.getTestClass().getName()));
}

String[] scripts = getScripts(sql, testContext.getTestClass(), testContext.getTestMethod(), classLevel);
Expand All @@ -265,7 +269,7 @@ private void executeSqlScripts(
ResourceDatabasePopulator populator = createDatabasePopulator(mergedSqlConfig);
populator.setScripts(scriptResources.toArray(new Resource[0]));
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources));
logger.debug("Executing SQL scripts: " + scriptResources);
}

String dsName = mergedSqlConfig.getDataSource();
Expand Down Expand Up @@ -372,9 +376,9 @@ private String detectDefaultScript(Class<?> testClass, Method testMethod, boolea
ClassPathResource classPathResource = new ClassPathResource(resourcePath);

if (classPathResource.exists()) {
if (logger.isInfoEnabled()) {
logger.info(String.format("Detected default SQL script \"%s\" for test %s [%s]",
prefixedResourcePath, elementType, elementName));
if (logger.isDebugEnabled()) {
logger.debug("Detected default SQL script \"%s\" for test %s [%s]"
.formatted(prefixedResourcePath, elementType, elementName));
}
return prefixedResourcePath;
}
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
Expand Down Expand Up @@ -135,8 +135,9 @@ private static void ensureSpringRulesAreNotPresent(Class<?> testClass) {
*/
public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
super(clazz);
if (logger.isDebugEnabled()) {
logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "]");
if (logger.isTraceEnabled()) {
logger.trace("SpringJUnit4ClassRunner constructor called with [%s]"
.formatted((clazz != null ? clazz.getName() : null)));
}
ensureSpringRulesAreNotPresent(clazz);
this.testContextManager = createTestContextManager(clazz);
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2022 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.
Expand Down Expand Up @@ -77,8 +77,8 @@ public SpringRepeat(Statement next, Method testMethod, int repeat) {
@Override
public void evaluate() throws Throwable {
for (int i = 0; i < this.repeat; i++) {
if (this.repeat > 1 && logger.isInfoEnabled()) {
logger.info(String.format("Repetition %d of test %s#%s()", (i + 1),
if (this.repeat > 1 && logger.isTraceEnabled()) {
logger.trace(String.format("Repetition %d of test %s#%s()", (i + 1),
this.testMethod.getDeclaringClass().getSimpleName(), this.testMethod.getName()));
}
this.next.evaluate();
Expand Down
Expand Up @@ -255,20 +255,20 @@ protected String[] generateDefaultLocations(Class<?> clazz) {
ClassPathResource classPathResource = new ClassPathResource(resourcePath);
if (classPathResource.exists()) {
String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH + resourcePath;
if (logger.isInfoEnabled()) {
logger.info(String.format("Detected default resource location \"%s\" for test class [%s]",
if (logger.isDebugEnabled()) {
logger.debug(String.format("Detected default resource location \"%s\" for test class [%s]",
prefixedResourcePath, clazz.getName()));
}
return new String[] {prefixedResourcePath};
}
else if (logger.isDebugEnabled()) {
logger.debug(String.format("Did not detect default resource location for test class [%s]: " +
else if (logger.isTraceEnabled()) {
logger.trace(String.format("Did not detect default resource location for test class [%s]: " +
"%s does not exist", clazz.getName(), classPathResource));
}
}

if (logger.isInfoEnabled()) {
logger.info(String.format("Could not detect default resource locations for test class [%s]: " +
if (logger.isDebugEnabled()) {
logger.debug(String.format("Could not detect default resource locations for test class [%s]: " +
"no resource found for suffixes %s.", clazz.getName(), ObjectUtils.nullSafeToString(suffixes)));
}

Expand Down

0 comments on commit 997dd3e

Please sign in to comment.