SpringBoot集成日志框架
Java生态体系日志框架介绍
简介
在Java生态体系中,围绕着日志,有很多成熟的解决方案。关于日志输出,主要有两类工具。
一类是日志框架(Log4j、Logback),主要用来进行日志的输出的,比如输出到哪个文件,日志格式如何等。 另外一类是日志门面(slf4j,commons-logging),主要一套通用的API,用来屏蔽各个日志框架之间的差异的。
对于Java工程师来说,关于日志工具的使用,最佳实践就是在应用中使用如日志门面+日志框架(SLF4J + Log4j 或 SLF4J + Logback) 这样的组合来进行日志输出。
这样做的最大好处,就是业务层的开发不需要关心底层日志框架的实现及细节,在编码的时候也不需要考虑日后更换框架所带来的成本。这也是门面模式所带来的好处。
常用的日志框架
j.u.l
j.u.l是java.util.logging包的简称,是JDK在1.4版本中引入的Java原生日志框架。Java Logging API 提供了七个日志级别用来控制输出。这七个级别分别是: SEVERE 、 WARNING 、 INFO 、 CONFIG 、 FINE 、 FINER 、 FINESTO
Log4j
Log4j 是 Apache 的一个开源项目,通过使用 Log4j, 我们可以控制日志信息输送的目的地是控制台、文件、 GUI组件,甚至是套接口服务器、 NT 的事件记录器、 UNIX Syslog 守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
Log4 也有七种日志级别: OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 和 TRACE。
LogBack
LogBack 也是一个很成熟的日志框架,其实 LogBack 和 Log4j 出自一个人之手,这个人就是 Ceki Gülcü。
LogBack 当前分成三个模块 logback-core,logback- classic 和 logback-access。 logback-core 是其它两个模块的基础模块。 logback-classic 是 Log4j 的一个改良版本。此外 logback-classic 完整实现 SLF4J API 使你可以很方便地更换成其它日记系统如 Log4j 或j.u.l。 logback-access 访问模块与 Servlet 容器集成提供通过 Http来访问日记的功能。
Log4j2
Log4j2是ApacheLogging Services项目开发的一款优秀的日志框架,是Log4j的升级版本。
Log4j2相比Log4j 1.x有很多新的特性和改进。
什么是日志门面
日志门面,是门面模式的一个典型的应用。
门面模式 (Facade Pattern) ,也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。
在应用中屏蔽掉底层日志框架的具体实现,即使有一天要更换代码的日志框架,只需要修 jar 包,最多再改改日志输出相关的配置文件就可以了。这就是解除了应用和日志框架之间的耦合。
常用的日志门面
SLF4J
Java 简易日志门面 (Simple Logging Facade for Java,缩写 SLF4J),是一套包装 Logging 框架的界面程式,以外观模式实现。可以在软件部署的时候决定要使用的 Logging 框架,目前主要支援的有Java Logging API 、Log4j 及 logback 等框架。以 M 授权方式发布。
SLF4J 的作者就是 Log4j 的作者 Ceki Gülcü,他宣称 SLF4J 比 Log4j 更有效率,而且比 Apache CommonsLogging) 简单、稳定。
其实, SLF4J 其实只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖Log4J 、 logback 等日志框架。
commons-logging
Apache Commons Logging 是一个基于 Java 的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供 API,日志实现和包装器实现。
commons-logging 和 SLF4J 的功能是类似的,主要是用来做日志门面的。提供更加好友的 API 工具。
SpringBoot集成 SLF4J + Log4j
去除LogBack依赖
因为 Spring Boot 已经内置了Logback,所以需要先将 Logback 移除。移除的方法是在 Spring Boot 依赖包中移除 Logback。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
如何使用了 Maven 多模块,必须在所有依赖 Logback 的依赖包中将 Logback 排除,否则 Spring Boot 启动时会发生如下报错:
SLF4J: Class path contains multiple SLF4J providers.
SLF4J: Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@4f51b3e0]
SLF4J: Found provider [org.apache.logging.slf4j.SLF4JServiceProvider@4b9e255]
SLF4J: See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual provider is of type [ch.qos.logback.classic.spi.LogbackServiceProvider@4f51b3e0]
比如,下面这两个 Spring Boot 依赖包中都依赖了 Logback,所以都要排除 Logback。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
引入LogBack
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>Lombok 的版本</version>
<scope>provided</scope>
</dependency>
引入Log4j2 依赖包
<!-- 设置 SLF4J 与之绑定的日志包。无需提供 SLF4J 的 JAR 包,因为 Lombok 已经提供了 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>Spring Boot 的版本</version>
</dependency>
此依赖包的版本与 Spring Boot 是一致的。如果不清楚应该使用什么版本,可以去 Maven 仓库中查询。Maven 仓库官网:Maven
配置 Log4j2 的日志配置文件
如果不提供此配置,则 Spring Boot 会提供一个默认配置。通常,默认配置也不是很糟糕,但它有一个严重的问题,它不会将日志输出至文件来备份。因此,不能使用默认配置。
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<Properties>
<property name="console_log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%t] %l %n %m%n</property>
<property name="file_log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%t] %C.%M[%L line] %n %m%n</property>
<property name="every_file_size">20MB</property>
</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${console_log_pattern}"/>
</Console>
<RollingFile name="DEBUG" fileName="./log/log4j2/debug.log"
filePattern="./log/log4j2/debug_log_archive/debug-%d{yyyy-MM-dd}-%i.log.zip">
<PatternLayout pattern="${file_log_pattern}"/>
<Policies>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<Filters>
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingFile>
<RollingFile name="INFO" fileName="./log/log4j2/info.log"
filePattern="./log/log4j2/info_log_archive/info-%d{yyyy-MM-dd}-%i.log.zip">
<PatternLayout pattern="${file_log_pattern}"/>
<Policies>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingFile>
<RollingFile name="WARN" fileName="./log/log4j2/warn.log"
filePattern="./log/log4j2/warn_log_archive/warn-%d{yyyy-MM-dd}-%i.log.zip">
<PatternLayout pattern="${file_log_pattern}"/>
<Policies>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<Filters>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingFile>
<RollingFile name="ERROR" fileName="./log/log4j2/error.log"
filePattern="./log/log4j2/error_log_archive/error-%d{yyyy-MM-dd}-%i.log.zip">
<PatternLayout pattern="${file_log_pattern}"/>
<Policies>
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</Policies>
<Filters>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingFile>
</appenders>
<loggers>
<!-- 属性 level 是用于设置最低需要输出的日志输出级别 -->
<root level="DEBUG">
<appender-ref ref="Console"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="INFO"/>
<appender-ref ref="WARN"/>
<appender-ref ref="ERROR"/>
</root>
</loggers>
</configuration>
Log4j2 的日志配置文件编写完成之后,可以放在 Maven 模块的 resource
目录下,如下图所示。
然后,在 Spring Boot 配置文件(如 application.yml
)中使用如下代码引入该配置。
logging:
config: classpath:log4j2.xml
使用
然后,在需要使用 SLF4J 的类上使用注解 @Slf4j
。这样就可以直接在代码中使用 log.xxx(...)
来使用 Log4j2 日志了。
@Slf4j
public class UserController {
// ...省略其它内容...
public void fun(HttpServletRequest request, HttpServletResponse response) {
log.info("fun called");
}
}
SpringBoot集成 SLF4J + LogBack
简介
在Spring boot
使用是非常方便的,不需要我们有什么额外的配置,因为Spring boot
默认支持的就是slf4j
+logback
的日志框架,想要灵活的定制日志策略,只需要我们在src/main/resources
下添加配置文件即可,只是默认情况下配置文件的命名需要符合以下规则:
- logback.xml
- logback-spring.xml
其中
logback-spring.xml
是官方推荐的,并且只有使用这种命名规则,才可以配置不同环境使用不同的日志策略这一功能。
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="./logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %F:%M:%L - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%yellow(%d{HH:mm:ss.SSS}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) - %F:%M:%L - %magenta(%msg) %n</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用户访问日志输出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>
当想让日志输出到sys-user
模块时,Logger需指定名称
private Logger xiaoming = LoggerFactory.getLogger("sys-user");
如果使用的是lomok插件
@Slf4j(topic = "sys-user")