Bootstrap

SpringBoot集成日志框架

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")

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;