精尽Spring Boot源码分析 - 日志系统

概述

日志是一个系统必不可缺少的东西,记录了系统运行时的点点滴滴,便于我们了解自己系统的运行状态,在我们使用 Spring Boot 时,默认就已经提供了日志功能,使用 Logback 作为默认的日志框架。那么,接下来我们依赖来看看 Spring Boot 是如何初始化好日志系统的。

为什么 Spring Boot 默认的日志框架是 Logbasck 呢?

因为在 spring-boot-starter 模块中引入 spring-boot-starter-logging 模块,该 Starter 引入了 logback-classic 依赖。

Log 日志体系

在我们的日常工作中,可能看到项目中依赖的跟日志相关的 jar 包有很多,例如 commons-logginglog4jlog4j2sl4j 和 logback 等等,眼花缭乱。经常会碰到各种依赖冲入的问题,非常烦恼,例如这几个问题:

  1. Failed to load class org.slf4j.impl.StaticLoggerBinder,没找到日志实现,如果你觉得你已经添加了对应的日志实现依赖了,那应该检查一下版本是否兼容
  2. Multiple bindings,找到了多个日志实现,也可能是版本问题,slf4j 会找其中一个作为日志实现

如果想要正确地使用它们,有必要先理清它们之间的关系,我们可以来看看 Log 的发展史,首先从 Java Log 的发展历程开始说起:

  1. log4j(作者Ceki Gülcü)出来后,被开发者们广泛的应用(注意,这里是直接使用),当初是 Java 日志事实上的标准,并成为了 Apache 的项目
  2. Apache 要求把 log4j 并入到 jdk,SUN 表示拒绝,并在 jdk1.4 版本后增加了 JULjava.util.logging);
  3. 毕竟是 JDK 自带的,JUL 也被很多人使用。同时还有其他的日志组件,如 SimpleLog 等。这个时候如果有人想换成其他日志组件,如 log4j 换成 JUL,因为 API 完全不同,就需要改动代码,当然很多人不愿意呀;
  4. Apache 见此,开发了 JCL(Jakarta Commons Logging),即 commons-logging-xx.jar。它只提供一套通用的日志接口 API,并不提供日志的实现。很好的设计原则嘛,依赖抽象而非实现。这样一来,我们的应用程序可以在运行时选择自己想要的日志实现组件;
  5. 这样看上去也挺美好的,但是 log4j 的作者觉得 JCL 不好用,自己开发出一套 slf4j,它跟 JCL 类似,本身不替供日志的具体实现,只对外提供接口或门面。目的就是为了替代 JCL。同时,还开发出 logback,一个比 log4j 拥有更高性能的组件,目的是为了替代 log4j
  6. Apache 参考了 logback,并做了一系列优化,推出了一套 log4j2 日志框架。

对于性能没什么特别高要求的使用 Spring Boot 中默认的 logback 就可以了,如果想要使用 log4j2 可以参考我的 《MyBatis 使用手册》 这篇文章,有提到过。

回顾

回到前面的 《SpringApplication 启动类的启动过程》 这篇文章,Spring Boot 启动应用的入口和主流程都是在 SpringApplication#run(String.. args) 方法中。

在启动 Spring 应用的整个过程中,到了不同的阶段会发布不同类型的事件,例如最开始会发布一个 应用正在启动 的事件,对于不同类型的事件都是通过 EventPublishingRunListener 事件发布器来发布,里面有一个事件广播器,封装了几个 ApplicationListener 事件监听器,如下:

 

# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

其中有一个 LoggingApplicationListener 对象,监听到不同事件后,会对日志系统进行一些相关的初始化工作

提示:Spring Boot 的 LoggingSystem 日志系统的初始化过程有点绕,嵌套的方法有点多,可参考序号耐心查看

LoggingApplicationListener

org.springframework.boot.context.logging.LoggingApplicationListener,Spring Boot 事件监听器,用于初始化日志系统

onApplicationEvent 方法

onApplicationEvent(ApplicationEvent 方法,处理监听到的事件

 

@Override public void onApplicationEvent(ApplicationEvent event) { // 应用正在启动的事件 if (event instanceof ApplicationStartingEvent) { onApplicationStartingEvent((ApplicationStartingEvent) event); } // Environment 环境已准备事件 else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } // 应用已准备事件 else if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } // Spring 上下文关闭事件 else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) { onContextClosedEvent(); } // 应用启动失败事件 else if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent(); } }

对于不同的事件调用不同的方法,事件的发布顺序也就是上面从上往下的顺序

1. onApplicationStartingEvent 方法

处理应用正在启动的事件

 

private void onApplicationStartingEvent(ApplicationStartingEvent event) { // <1> 创建 LoggingSystem 对象 // 指定了类型则使用指定的,没有则尝试创建对应的对象,ClassLoader 中有对应的 Class 对象则创建(logback > log4j2 > java logging) this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); // <2> LoggingSystem 的初始化前置处理 this.loggingSystem.beforeInitialize(); }

过程如下:

  1. 创建 LoggingSystem 对象,指定了类型则使用指定的,没有则尝试创建对应的对象,ClassLoader 中有对应的 Class 对象则创建(logback > log4j2 > java logging
  2. 调用 LoggingSystem 的 beforeInitialize() 方法,初始化前置处理

2. onApplicationEnvironmentPreparedEvent 方法

处理环境已准备事件

 

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { // <1> 如果还未明确 LoggingSystem 类型,那么这里继续创建 LoggingSystem 对象 if (this.loggingSystem == null) { this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); } // <2> 初始化 LoggingSystem 对象,创建日志文件,设置日志级别 initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader()); }

过程如下:

  1. 如果还未明确 LoggingSystem 类型,那么这里继续创建 LoggingSystem 对象
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值