写点什么

Spring Boot「02」日志配置

作者:Samson
  • 2022 年 10 月 10 日
    上海
  • 本文字数:4208 字

    阅读完需:约 14 分钟

Spring Boot「02」日志配置

今天我们来学习下 Spring Boot 中的日志配置。

01-Logback

Spring Boot 中默认使用的是 Logback 日志组件。Logback 是 Log4j 作者发起的另外一个开源日志组件,目的是提供比 Log4j 更好地性能。


Logback 日志组件共分为三部分:


  1. logback-core 是 Logback 的核心模块与实现;

  2. logback-classic 是 Log4j 的改良版本,也是 SLF4j 的一个实现;

  3. logback-access 提供通过 HTTP 访问日志的功能;


接下来,我们来看一下一个 Spring Boot 工程是如何将 Logback 引入进来的?


使用 Spring Initializr 初始化的工程,一般项目的 parent 设置为spring-boot-starter-parentspring-boot-dependencies。例如,我们在之前文章中使用到的 payroll 项目(可以在我的 gitee 中获取完整代码)。在spring-boot-dependencies中为项目引入了 spring-boot-starter 和 spring-boot-starter-logging 两个编译时依赖,而正是后者引入了对 logback-classic 的依赖:


<dependencies>    <dependency>      <groupId>ch.qos.logback</groupId>      <artifactId>logback-classic</artifactId>      <scope>compile</scope>    </dependency>    <dependency> <!-- 将 log4j 桥接到 slf4j -->      <groupId>org.apache.logging.log4j</groupId>      <artifactId>log4j-to-slf4j</artifactId>      <scope>compile</scope>    </dependency>    <dependency> <!-- 将 jul 桥接到 slf4j -->      <groupId>org.slf4j</groupId>      <artifactId>jul-to-slf4j</artifactId>      <scope>compile</scope>    </dependency>  </dependencies>
复制代码


知道了 Spring Boot 工程是如何引入 Logback 日志组件的之后,我们接下来看一下 Spring Boot 应用是如何完成日志组件的初始化的?(当前使用的 Spring Boot 版本为 2.7.4


spring-boot-*.jar/META-INF/spring.factories 中配置了许多事件监听者(如下所示),监听应用启动事件、准备环境事件等。


# Application Listenersorg.springframework.context.ApplicationListener=\org.springframework.boot.ClearCachesApplicationListener,\org.springframework.boot.builder.ParentContextCloserApplicationListener,\org.springframework.boot.context.FileEncodingApplicationListener,\org.springframework.boot.context.config.AnsiOutputApplicationListener,\org.springframework.boot.context.config.DelegatingApplicationListener,\**org.springframework.boot.context.logging.LoggingApplicationListener**,\org.springframework.boot.env.EnvironmentPostProcessorApplicationListener
复制代码


Logback 就是通过LoggingApplicationListener来进行初始化的,其类中的onApplicationEvent实现了对不同事件的响应动作。


public void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationStartingEvent) { // 应用启动事件        this.onApplicationStartingEvent((ApplicationStartingEvent)event);    } else if (event instanceof ApplicationEnvironmentPreparedEvent) { // 环境准备完成时间        this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);    } else if (event instanceof ApplicationPreparedEvent) {        this.onApplicationPreparedEvent((ApplicationPreparedEvent)event);    } else if (event instanceof ContextClosedEvent) {        this.onContextClosedEvent((ContextClosedEvent)event);    } else if (event instanceof ApplicationFailedEvent) {        this.onApplicationFailedEvent();    }}
复制代码


ApplicationStartingEvent事件发生时,onApplicationStartingEvent方法会对日志组件进行初始化:


private void onApplicationStartingEvent(ApplicationStartingEvent event) {  this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());  this.loggingSystem.beforeInitialize();}
复制代码


其中,LoggingSystem.get方法就是在遍历 spring-boot-*.jar/META-INF/spring.factories 中的LoggingSystemFactory


public static LoggingSystem get(ClassLoader classLoader) {  String loggingSystemClassName = System.getProperty(SYSTEM_PROPERTY);  if (StringUtils.hasLength(loggingSystemClassName)) {    if (NONE.equals(loggingSystemClassName)) {      return new NoOpLoggingSystem();    }    return get(classLoader, loggingSystemClassName);  }  // spring-boot-*.jar/META-INF/spring.factories 中配置的 LoggingSystemFactory  LoggingSystem loggingSystem = SYSTEM_FACTORY.getLoggingSystem(classLoader);  Assert.state(loggingSystem != null, "No suitable logging system located");  return loggingSystem;}
复制代码


# Logging Systemsorg.springframework.boot.logging.LoggingSystemFactory=\org.springframework.boot.logging.logback.**LogbackLoggingSystem.Factory**,\org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\org.springframework.boot.logging.java.JavaLoggingSystem.Factory
复制代码


ApplicationEnvironmentPreparedEvent事件发生时,onApplicationEnvironmentPreparedEvent方法会进行初始化动作:


private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {  SpringApplication springApplication = event.getSpringApplication();  if (this.loggingSystem == null) {    this.loggingSystem = LoggingSystem.get(springApplication.getClassLoader());  }  // 初始化  initialize(event.getEnvironment(), springApplication.getClassLoader());}
复制代码


protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {  getLoggingSystemProperties(environment).apply();  this.logFile = LogFile.get(environment);  if (this.logFile != null) {    this.logFile.applyToSystemProperties();  }  this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);  initializeEarlyLoggingLevel(environment);  initializeSystem(environment, this.loggingSystem, this.logFile);  initializeFinalLoggingLevels(environment, this.loggingSystem);  registerShutdownHookIfNecessary(environment, this.loggingSystem);}
复制代码

02-日志门面、日志实现之间的关系

在学习日志组件的时候,经常会看到说 JCL、JUL、Log4j、Log4j2、SLF4j 等等的名词,它们之间的关系是如何的?接下来,我们将一块学习下这些名词具体指代什么,它们之间的关系又是如何的。


首先,我们先梳理一下常用的日志组件(或者说日志实现):


  • JUL,java.util.logging,是 JDK 内置的日志组件

  • Log4j,是 Apache 软件基金会下的开源项目,也是 Java 中的老牌日子组件

  • Log4j2,是 Log4j 的升级版本,2.*

  • Logback,是 Log4j 作者发起的另一个开源日志项目,旨在提供更高效的日志组件;Spring Boot 2.0 以后,Logback 称为其默认的日志组件


接下来,我们梳理下常用的日志门面接口:


  • SLF4j,是最常见的日志门面接口

  • JCL,Jakarta commons logging 或者称为 Apache commons logging,是 Apache 旗下的一个日志门面接口


我们的 Spring Boot 项目 payroll 使用的是 SLF4j 作为日志门面,以 Logback-classic 作为日志实现。日志门面与日志实现的关系可以用 SLF4j 官网的一个图片很好的诠释:



SLF4j 将应用与底层的日志组件解耦,屏蔽了具体地日志实现细节。除此之外,日志门面还会提供桥接接口,将那些对 Log4j、JCL、JUL 的调用重定向到对 SLF4j 的调用,SLF4j 官网的一个图片很好的诠释了这个思路:



最后,我们梳理下 SLF4j 中各类 jar 包的具体作用:


  1. 桥接

  2. jcl-over-slf4j.jar,将 JCL 桥接到 SLF4j

  3. jul-to-slf4j.jar,将 JUL 桥接到 SLF4j

  4. log4j-over-slf4j.jar,将 log4j 桥接到 SLF4j

  5. osgi-over-slf4j.jar,将 osgi 桥接到 SLF4j

  6. slf4j-android.jar,将 android 桥接到 SLF4j

  7. 适配(不能与对应的桥接包共存,例如 slf4j-jcl.jar 与 jcl-over-slf4j.jar 不能共存

  8. slf4j-jcl.jar,提供 SLF4j 与日志实现 JCL 之间的适配层

  9. slf4j-jdk14.jar,提供 SLF4j 与日志实现 JUL 之间的适配层

  10. slf4j-log4j12.jar,提供 SLF4j 与日志实现 Log4j 之间的适配层

  11. slf4j-nop.jar,提供 SLF4j 输出到 /dev/null

  12. slf4j-simple.jar,SLF4j 自带的简单日志实现

  13. 核心包

  14. slf4j-api.jar,日志门面核心 jar 包,主要用于应用层调用

  15. slf4j-ext.jar,扩展包

03-Spring Boot 项目中日志配置

了解了上述内容,我们来简单看下 Spring Boot 项目中是如何修改日志配置的。使用的日志实现是 Logback。


Spring Boot 项目中,日志配置文件可以是 logback-spring.xml(会自动被 Spring Boot 框架识别),也可在 application.yml 中通过logging.config: classpath:xxx.xml指定。通常推荐使用上述这两种方式,而不推荐使用 logback.xml 这种可以直接被 Logback 组件识别并解析的配置文件。原因主要是交由 Spring Boot 来解析日志配置,可以使用 Spring Boot 的一些特性,而这些特性是 Logback 组件不支持的。


LogbackLoggingSystem 中定义了标准配置文件的名称:


@Override  protected String[] getStandardConfigLocations() {    return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };  }
复制代码


Spring Boot 框架可扫描到的配置文件名称:


protected String[] getSpringConfigLocations() {    String[] locations = getStandardConfigLocations();    for (int i = 0; i < locations.length; i++) {      String extension = StringUtils.getFilenameExtension(locations[i]);      locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1) + "-spring."          + extension;    }    return locations;  }
复制代码


可以看出,即getStandardConfigLocations返回值加上“-spring”形成的文件名。

refs

[1] SpringBoot揭密:spring-boot-starter-logging及源码解析

[2] 关于slf4j log4j log4j2的jar包配合使用的那些事

[3] SpringBoot入门 - 添加Logback日志

发布于: 刚刚阅读数: 3
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

InfoQ签约作者 | 阿里云社区签约作者

评论

发布
暂无评论
Spring Boot「02」日志配置_Java_Samson_InfoQ写作社区