本文目录
Log在Spring的前世今生
Log对于任一系统的重要性不言而喻。
跟JPA类似,Spring中的Log也只是提供了接口定义,然后在项目中,需要项目自行选用Log的具体实现。
值得一提的是,Spring跟Spring Boot使用的Log接口与默认实现不同:
- Spring的Log接口是JCL (Jakarta Commons Logging)。Spring框架采用了JUL(属于java.util.logging)来作为默认的Log实现。
- Spring Boot的Log接口是SLF4J,Simple Logging Facade for Java。且使用Logback来作为标准实现。
另外一个有名的Log的实现是Log4j和Log4j2。当然最近的Log4j2的漏洞问题,导致Log4j2颇有些谈虎色变的味道。
log4j, logback和log4j2
log4j
是Apache的一个开源项目,已经结束了生命周期;
logback
官网上介绍说Logback is intended as a successor to the popular log4j project
,也就是log4j 1.x的一个后续,基于SLF4J API的原生实现。
log4j2
,官网Log4j2是log4j 1.x和logback的改进版。其采用了一些新技术(无锁异步等),使得日志的吞吐量、性能比log4j 1.x提高了10倍,并解决了一些死锁的bug,而且配置更加简单灵活。
SLF4J API
不管是Logback还是log4j2,应用程序实际上通过SLF4J API再进行编程。
SLF4J Manual上有个非常基础的例子:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
另外一个典型案例(同样来自SLF4J Manual):
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Wombat {
final Logger logger = LoggerFactory.getLogger(Wombat.class);
Integer t;
Integer oldT;
public void setTemperature(Integer temperature) {
oldT = t;
t = temperature;
logger.debug("Temperature set to {}. Old value was {}.", t, oldT);
if(temperature.intValue() > 50) {
logger.info("Temperature has risen above 50 degrees.");
}
}
}
还可以使用Fluent Logging API
:
int newT = 15;
int oldT = 16;
// using traditional API
logger.debug("Temperature set to {}. Old value was {}.", newT, oldT);
// using fluent API, log message with arguments
logger.atDebug().log("Temperature set to {}. Old value was {}.", newT, oldT);
// using fluent API, add arguments one by one and then log message
logger.atDebug().setMessage("Temperature set to {}. Old value was {}.").addArgument(newT).addArgument(oldT).log();
// using fluent API, add one argument with a Supplier and then log message with one more argument.
// Assume the method t16() returns 16.
logger.atDebug().setMessage("Temperature set to {}. Old value was {}.").addArgument(() -> t16()).addArgument(oldT).log();
使用Logback
Logback是Spring Boot的默认实现,且默认的输出是Console。
想要更改到别的位置,需要修改application.properties
来指定(一般来说,只需要指定其中一个):
- logging.file (更高优先级),设置文件。
- logging.path,设置目录,会在指定目录下创建spring.log文件并写入。
一个例子:
logging.path=/log/
logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
logging.pattern.file=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
默认条件下,Logback会在%classpath%
下查找一下文件:
- logback-spring.xml
- logback-spring.groovy
- logback.xml
- logback.groovy
一个基础的logback-spring.xml:
<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="chapters.configuration" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
logger
logger
,用来设置某一个包或者具体的某一个类的日志打印级别,并用来关联appender。
logger
节点下有三个属性,和一个子节点。
name
:用来指定受此logger约束的某一个包或者具体的某一个类。level
:用来设置打印级别(TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF),还有一个值INHERITED或者同义词NULL,代表强制执行上级的级别。如果没有设置此属性,那么当前logger将会继承上级的级别。addtivity
:是否向上级logger传递打印信息。默认是true
。
子节点appender-ref
则是用来指定具体appender的。
root
root
是一种特殊的logger
。它不支持name
和addtivity
属性。
appender
appender
节点,有两个属性。
name
: 指定appender
的名称,logger
就通过该名称进行引用;class
:指定appender
的类名。一般就三个类。ch.qos.logback.core.ConsoleAppender
。用来写入Console。- FIleAdapter。
ch.qos.logback.core.rolling.RollingFileAppender
。用来写入文件,通过rolling的方式来决定是否写入新的文件。
该节点有一些常用的子节点。
append
<append>true</append>
当这个子节点为true
时,日志被追加到文件结尾。为false
时,清空现存文件。默认是true
。
filter
filter
子节点为appender
添加过滤器,可以添加多个过滤器。当appender
有多个过滤器时,按照配置顺序执行。
filter
的返回值:
- DENY:类似于循环中的break。日志将立即被抛弃,并且不经过剩余的过滤器;
- NEUTRAL:类似于循环的continue。 并且会由列表里中的下个过滤器过接着处理日志;
- ACCEPT:类似于循环中的return。日志会被立即处理,且不经过剩余的过滤器;
常用的filter
有两种:
ThresholdFilter
。临界值过滤器,用于过滤掉低于指定临界值的日志。当日志级别大于或等于临界值时,过滤器返回NEUTRAL
;当日志级别低于临界值时,日志会被放弃。
下列示例会记录INFO及以上的Log:
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
LevelFilter
。 级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath
(用于配置符合过滤条件的操作) 和onMismatch
(用于配置不符合过滤条件的操作)接收或拒绝日志
下述示例只会记录INFO级别的LOG。
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
encoder
encoder
子节点主要用来定义写入日志的格式。
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
file和rollingPolicy
file
和rollingPolicy
主要用来处理写入日志文件的appender。其中file
用以确定文件路径。而rollingPolicy
主要用来定义写入的频率。
一个简单的示例:
<appender name="FILEOUT"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${logging.path}/logs/logs.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/logs/logs.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
一些实际案例
不写入Console的日志
以下的示例会在程序运行时写入文件日志,但不会打印在Console上。
<configuration>
<appender name="FILEOUT"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${logging.path}/logs/log.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/logs/log.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="FILEOUT"/>
</root>
</configuration>
在Console显示日志,但将错误写入文件
以下的示例会在程序运行时打印在Console,但将错误信息写入文件日志。
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
</encoder>
</appender>
<appender name="FILEOUT"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<file>
${logging.path}/logs/log.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/logs/log.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="${logging.level}">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
使用log4j2
在POM文件中引入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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
具体使用,呃,等有时间再写吧。