测试时在Spring Boot中设置日志级别

2023/05/12

1. 概述

在本教程中,我们将学习如何在为Spring Boot应用程序运行测试时设置日志级别

尽管在测试通过时我们大多可以忽略日志,但如果我们需要诊断失败的测试,选择正确的日志级别是至关重要的。

2. 日志级别的重要性

正确配置日志级别可以为我们节省大量时间。

例如,如果测试在CI服务器上失败,但在我们的本地机器上通过,除非我们有足够的日志输出,否则我们很难定位到出现问题的点。相反,如果我们输出太多过于细节的日志,可能会更难以让我们找到有用的信息。

为了输出适量的日志信息,我们可以微调应用程序包的日志记录级别。如果我们发现某个Java包对我们的测试更重要,我们可以给它一个更低的级别,比如DEBUG。同样,为了避免输出太多不必要的日志,我们可以为不太重要的包配置更高的级别,比如INFO或ERROR。

因此,接下来我们将介绍设置日志输出级别的各种方法。

3. 在application.properties设置日志级别

如果我们想在测试中修改日志级别,我们可以在src/test/resources/application.properties中设置一个属性:

logging.level.cn.tuyucheng.taketoday.testloglevel=debug

此属性将专门为cn.tuyucheng.taketoday.testloglevel包设置日志级别

同样,我们可以通过设置根日志级别来更改所有包的日志级别:

logging.level.root=info

在这里,我们使用原始的xml配置,首先我们在测试目录的resources下新建一个application-logback-test.properties文件用于特定的测试profile:

#application-logback-test.properties
logging.config=classpath:logback-test.xml

下面是引用的logback-test.xml文件,该文件同样位于测试目录的resources下:

<configuration>
    <include resource="/org/springframework/boot/logging/logback/base.xml"/>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
    <root level="error">
        <appender-ref ref="STDOUT"/>
    </root>
    <logger name="cn.tuyucheng.taketoday.testloglevel" level="debug"/>
</configuration>

如我们所见,我们为root配置了info级别,而我们自己的包cn.tuyucheng.taketoday.testloglevel配置的是debug级别。

现在让我们通过添加一个输出一些日志的Rest API来测试我们的日志设置:

package cn.tuyucheng.taketoday.testloglevel;

@RestController
public class TestLogLevelController {
    private static final Logger LOG = LoggerFactory.getLogger(TestLogLevelController.class);

    @Autowired
    private OtherComponent otherComponent;

    @GetMapping("/testLogLevel")
    public String testLogLevel() {
        LOG.trace("This is a TRACE log");
        LOG.debug("This is a DEBUG log");
        LOG.info("This is an INFO log");
        LOG.error("This is an ERROR log");

        otherComponent.processData();
        return "Added some log output to console...";
    }
}
package cn.tuyucheng.taketoday.component;

public class OtherComponent {
    private static final Logger LOG = LoggerFactory.getLogger(OtherComponent.class);

    public void processData() {
        LOG.trace("This is a TRACE log from another package");
        LOG.debug("This is a DEBUG log from another package");
        LOG.info("This is an INFO log from another package");
        LOG.error("This is an ERROR log from another package");
    }
}

正如预期的那样,如果我们在测试中调用此API,我们将能够看到TestLogLevelController打印了DEBUG日志。但是,由于OtherComponent类不是位于cn.tuyucheng.taketoday.testloglevel的包中,因此我们只能看到OtherComponent中打印INFO及以上级别的日志。

@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = TestLogLevelApplication.class)
@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
@ActiveProfiles("logback-test")
public class LogbackTestLogLevelIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Rule
    public OutputCaptureRule outputCapture = new OutputCaptureRule();

    private String baseUrl = "/testLogLevel";

    @Test
    public void givenErrorRootLevelAndDebugLevelForOurPackage_whenCall_thenPrintDebugLogsForOurPackage() {
        ResponseEntity<String> response = restTemplate.getForEntity(baseUrl, String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(200);
        assertThatOutputContainsLogForOurPackage("DEBUG");
    }

    @Test
    public void givenErrorRootLevelAndDebugLevelForOurPackage_whenCall_thenNoDebugLogsForOtherPackages() {
        ResponseEntity<String> response = restTemplate.getForEntity(baseUrl, String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(200);
        assertThatOutputDoesntContainLogForOtherPackages("DEBUG");
    }

    @Test
    public void givenErrorRootLevelAndDebugLevelForOurPackage_whenCall_thenPrintErrorLogs() {
        ResponseEntity<String> response = restTemplate.getForEntity(baseUrl, String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(200);
        assertThatOutputContainsLogForOurPackage("ERROR");
        assertThatOutputContainsLogForOtherPackages("ERROR");
    }

    private void assertThatOutputContainsLogForOurPackage(String level) {
        assertThat(outputCapture.toString()).containsPattern("TestLogLevelController." + level + ".");
    }

    private void assertThatOutputDoesntContainLogForOtherPackages(String level) {
        assertThat(outputCapture.toString().replaceAll("(?m)^.TestLogLevelController.$", "")).doesNotContain(level);
    }

    private void assertThatOutputContainsLogForOtherPackages(String level) {
        assertThat(outputCapture.toString().replaceAll("(?m)^.TestLogLevelController.$", "")).contains(level);
    }
}
00:11:53.478 [http-nio-auto-1-exec-1] DEBUG [c.t.t.t.TestLogLevelController] >>> This is a DEBUG log 
00:11:53.478 [http-nio-auto-1-exec-1] INFO  [c.t.t.t.TestLogLevelController] >>> This is an INFO log 
00:11:53.478 [http-nio-auto-1-exec-1] ERROR [c.t.t.t.TestLogLevelController] >>> This is an ERROR log 
00:11:53.478 [http-nio-auto-1-exec-1] INFO  [c.t.t.component.OtherComponent] >>> This is an INFO log from another package 
00:11:53.478 [http-nio-auto-1-exec-1] ERROR [c.t.t.component.OtherComponent] >>> This is an ERROR log from another package 

像这样设置日志级别非常容易,如果我们的测试是使用@SpringBootTest注解,我们绝对应该这样做。但是,如果我们不使用该注解,我们将不得不以不同的方式配置日志级别。

3.1 基于Profiles的日志设置

尽管将属性配置放在src/test/application.properties文件中在大多数情况下都有效,但在某些情况下,我们可能希望为一个测试或一组测试设置不同的配置

在这种情况下,我们可以使用@ActiveProfiles注解将Spring Profile添加到我们的测试中

@RunWith(SpringRunner.class)
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = TestLogLevelApplication.class)
@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
@ActiveProfiles("logging-test")
public class TestLogLevelWithProfileIntegrationTest {
    // ...
}

然后,我们的日志配置将放置在src/test/resources/application-logging-test.properties文件中:

#src/test/resources/application-logging-test.properties
logging.level.cn.tuyucheng.taketoday.testloglevel=TRACE
logging.level.root=ERROR

如果我们使用以上配置从我们的测试中调用TestLogLevelController,我们可以看到TestLogLevelController打印了TRACE日志,而位于cn.tuyucheng.taketoday.testloglevel包之外的类,例如OtherComponent只会打印ERROR级别的日志:

00:30:28.118 [http-nio-auto-3-exec-1] TRACE [c.t.t.t.TestLogLevelController] >>> This is a TRACE log 
00:30:28.118 [http-nio-auto-3-exec-1] DEBUG [c.t.t.t.TestLogLevelController] >>> This is a DEBUG log 
00:30:28.118 [http-nio-auto-3-exec-1] INFO  [c.t.t.t.TestLogLevelController] >>> This is an INFO log 
00:30:28.118 [http-nio-auto-3-exec-1] ERROR [c.t.t.t.TestLogLevelController] >>> This is an ERROR log 
00:30:28.118 [http-nio-auto-3-exec-1] ERROR [c.t.t.component.OtherComponent] >>> This is an ERROR log from another package 

4. 配置Logback

如果我们使用在Spring Boot中默认使用的Logback作为日志实现,我们可以在src/test/resources中的logback-test.xml文件中设置日志级别

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <logger name="cn.tuyucheng.taketoday.testloglevel" level="debug"/>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

上面的示例显示了如何在我们的Logback配置中为测试设置日志级别。根日志级别设置为INFO,我们的cn.tuyucheng.taketoday.testloglevel包的日志级别设置为DEBUG。

同样,让我们在应用上面的设置后检查日志输出:

14:56:58.457 [http-nio-auto-3-exec-1] DEBUG [c.t.t.t.TestLogLevelController] >>> This is a DEBUG log 
14:56:58.457 [http-nio-auto-3-exec-1] INFO  [c.t.t.t.TestLogLevelController] >>> This is an INFO log 
14:56:58.457 [http-nio-auto-3-exec-1] ERROR [c.t.t.t.TestLogLevelController] >>> This is an ERROR log 
14:56:58.457 [http-nio-auto-3-exec-1] INFO  [c.t.t.component.OtherComponent] >>> This is an INFO log from another package 
14:56:58.457 [http-nio-auto-3-exec-1] ERROR [c.t.t.component.OtherComponent] >>> This is an ERROR log from another package

4.1 基于Profile的Logback配置

为我们的测试设置特定于Profile的配置的另一种方法是在application.properties中为我们的Profile设置logging.config属性:

logging.config=classpath:logback-test.xml

或者,如果我们希望类路径上只有一个单一的Logback配置文件,我们可以使用logback.xml中的<springProfile>标签:

<configuration>
    <include resource="/org/springframework/boot/logging/logback/base.xml"/>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="error">
        <appender-ref ref="STDOUT"/>
    </root>

    <springProfile name="logback-test1">
        <logger name="cn.tuyucheng.taketoday.testloglevel" level="info"/>
    </springProfile>

    <springProfile name="logback-test2">
        <logger name="cn.tuyucheng.taketoday.testloglevel" level="trace"/>
    </springProfile>
</configuration>

现在,如果我们使用Profile logback-test1在测试中调用TestLogLevelController,我们将得到以下输出:

15:14:57.422 [http-nio-auto-1-exec-1] INFO  [c.t.t.t.TestLogLevelController] >>> This is an INFO log 
15:14:57.422 [http-nio-auto-1-exec-1] ERROR [c.t.t.t.TestLogLevelController] >>> This is an ERROR log 
15:14:57.422 [http-nio-auto-1-exec-1] INFO  [c.t.t.component.OtherComponent] >>> This is an INFO log from another package 
15:14:57.422 [http-nio-auto-1-exec-1] ERROR [c.t.t.component.OtherComponent] >>> This is an ERROR log from another package 

相反,如果我们将Profile更改为logback-test2,则输出将为:

15:14:57.422 [http-nio-auto-1-exec-1] DEBUG [c.t.t.t.TestLogLevelController] >>> This is a DEBUG log 
15:14:57.422 [http-nio-auto-1-exec-1] INFO  [c.t.t.t.TestLogLevelController] >>> This is an INFO log 
15:14:57.422 [http-nio-auto-1-exec-1] ERROR [c.t.t.t.TestLogLevelController] >>> This is an ERROR log 
15:14:57.422 [http-nio-auto-1-exec-1] INFO  [c.t.t.component.OtherComponent] >>> This is an INFO log from another package 
15:14:57.422 [http-nio-auto-1-exec-1] ERROR [c.t.t.component.OtherComponent] >>> This is an ERROR log from another package 

5. Log4J替代方案

或者,如果我们使用Log4J2,我们可以在src/test/resources中的log4j2-spring.xml文件中设置日志级别

<Configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>

    <Loggers>
        <Logger name="cn.tuyucheng.taketoday.testloglevel" level="debug"/>

        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

我们可以通过在application.properties中设置logging.config属性来设置Log4J配置文件的路径:

logging.config=classpath:log4j-testloglevel.xml

最后,让我们检查应用上述设置后的输出:

2022-12-27 14:08:27.545 DEBUG 56585 --- [nio-8080-exec-1] c.t.t.testloglevel.TestLogLevelController  : This is a DEBUG log
2022-12-27 14:08:27.545  INFO 56585 --- [nio-8080-exec-1] c.t.t.testloglevel.TestLogLevelController  : This is an INFO log
2022-12-27 14:08:27.546 ERROR 56585 --- [nio-8080-exec-1] c.t.t.testloglevel.TestLogLevelController  : This is an ERROR log
2022-12-27 14:08:27.546  INFO 56585 --- [nio-8080-exec-1] c.t.t.component.OtherComponent  : This is an INFO log from another package
2022-12-27 14:08:27.546 ERROR 56585 --- [nio-8080-exec-1] c.t.t.component.OtherComponent  : This is an ERROR log from another package

6. 总结

在本文中,我们学习了如何在测试Spring Boot应用程序时设置日志级别,并演示了许多不同的配置方式。

当我们使用@SpringBootTest注解时,在Spring Boot的application.properties文件中设置日志级别是最简单的选择。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章