由于更早的拥抱开源,Java在解决方案的数量上拥有.NET无法比拟的优势,可这就苦了我们这样的菜鸡玩家,比如日志,光主流的解决方案就有JUL、Log4j、Logback、Log4j2、SLF4j,这么多方案,到底要用哪一个?
若是论资历的话,log4j无疑是老前辈,作为早期使用最广的的日志框架,由Ceki创建,后来成为Apache的顶级项目之一,说是Java社区的日志标准也不为过,不过Sun公司可不这么觉得,觉得Apache就是典型的关公面前耍大刀,于是在jdk1.4之后增加了一个名为“java.util.logging”的包,简称JUL,用于日志记录。不过由于log4j已经有了广大的群众基础,加上JUL时间仓促,有些地方并不好用,所以不少人对此并不买账。Apache一瞅,Sun也太小家子器了,决定教教Sun什么叫格局,就推出了Jakarta Commons Logging,简称JCL,JCL定义了一套日志接口,只要你调用的JCL的接口,你想用log4j用log4j,想用jul用jul,我全都要。据说是受不了Apache的工作氛围,log4j的创始人Ceki于2006年出走Apache,并先后创建了Slf4j(类似于JCL)和logback(类似于log4j),于是Java日志领域一分为二:JCL和Slf4j两大阵营。logback作为后起之秀,船小好调头,又有亲爹加持,发展的越来越快,隐隐有些压住log4j一头,所以Apache在2012年又在log4j的基础上升级为2.0版本(赛亚人变身了属于),也就是现在的log4j2。
其实说白了就是天才老爹入赘豪门,生了个哥哥,后来名门大户的呆不习惯,离家创业,又生了个弟弟,亲爹肯定是想弟弟继承家业,但是大户族老们可不答应,毕竟宝都押在了哥哥头上,于是上演了一出兄弟相争的戏码,倒霉的却是嫡出的JUL。
slf4j
sfl4j全称Simple Logging Facade for Java,它为各种日志框架(log4j、logback等)提供了一个共同的API,而非日志的具体实现方案,所以如果需要实现日志的输出,还需要引入其他框架。由于slf4j将应用程序和具体的日志实现方案解耦,可以让开发人员在不同的日志框架之间进行切换,比如你开始用了log4j后来发现logback更好用,只需要修改依赖和配置即可,而无需修改代码。所以在开发时,我们在项目中要避免直接使用log4j和logback中的API,而应该依赖使用slf4j中API。如果使用了slf4j,那么无论你使用log4j还是logback,都可以使用下面的代码记录日志:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
public static void main(String[] args) {
System.out.println("开始执行");
Logger logger = LoggerFactory.getLogger(Main.class);
logger.trace("跟踪日志");
logger.debug("调试日志");
logger.info("普通日志");
logger.warn("报警日志");
logger.error("错误日志");
System.out.println("执行完毕");
}
}
Maven依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.10</version>
</dependency>
slf4j+log4j2
官网:https://logging.apache.org/log4j/2.x/index.html
在应用中想使用,slf4j+log4j来记录日志,除了需要使用slf4j-api之外,还需要额外引入适配器:
- slf4j 1.7.x及其之前的版本使用
log4j-slf4j-impl
。 - slf4j 2.0.x及其之后的版本使用
log4j-slf4j2-impl
。
项目依赖:
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- Log4j2 API -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<!-- Log4j2 Core Implementation -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<!-- SLF4J Log4j2 Binding -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
不过一般情况下由于log4j-core
和log4j-slf4j-impl
中已经自动依赖了log4j-api
和 slf4j-api
,所以有时候依赖会简写为:
<dependencies>
<!-- Log4j2 Core Implementation -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<!-- SLF4J Log4j2 Binding -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
如果你需要使用log4j 1.x,那么可以使用log4j-to-slf4j
用于log4j 1.x和slf4j之间桥接。
log4j的配置可通过编程方式和配置文件的方式实现,主流的方式都是使用配置文件:
- log4j 2配置文件的名称为log4j2.mxl,放在src目录下,maven项目放在resources下。
- log4j 2也支持json、yaml等格式的配置文件,如log4j2.properties、log4j2.yaml、log4j2.json等
在resources下面新建log4j2.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
<!--定义变量-->
<Properties>
<!--定义日志存储目录的变量-->
<Property name="logDir">D:/logs</Property>
</Properties>
<!--输出源-->
<Appenders>
<!--输出到控制台-->
<Console name="Console" target="SYSTEM_OUT">
<!--可选配置:控制台只输出level及以上级别的信息,其他的直接拒绝,具体的可以查看ThresholdFilter相关的说明-->
<ThresholdFilter level="INFO" onMatch="ACCEPT"/>
<!--日志的输出格式-->
<PatternLayout pattern="[%-level]%d{yyyy-MM-dd HH:mm:ss.SSS} %logger{36} - %msg%n"/>
</Console>
<!--输出到文件,大部分时候都会使用RollingFile-->
<File name="File" fileName="${logDir}/log.txt">
<PatternLayout>
<Pattern>%d %p %c [%t] %m%n</Pattern>
</PatternLayout>
</File>
<!-- 滚动输出:
name:Appender的唯一标识,跟下面AppenderRef中的ref对应。
fileName:指定了日志文件当前活动路径和文件名,决定了日志文件被写入的初始文件位置。
filePattern:定义滚动后的文件命名规则
-->
<RollingFile name="RollingFileInfo" fileName="d:/logs/log.txt" filePattern="d:/logs/log-%d{yyyy-MM-dd}_%i.txt">
<!-- 只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 输出的格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss,SSS}] [%p] - %l - %m%n"/>
<!-- Policies:滚动策略,决定何时应该产生新日志 -->
<Policies>
<!--时间策略:具体的看文件命名格则,如{yyyy-MM-dd},那这个意思就是每天一个,如果是{yyyy-MM-dd HH}那这个意思就是每小时,以此类推-->
<TimeBasedTriggeringPolicy interval="1"/>
<!--大小策略:每当文件大小达到500MB,就开始滚动,产生一个新日志文件,后面的%i即标识产生了第几个文件-->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!--文件夹下最多可以有几个文件,过期的旧文件会被删除,默认是7-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<!-- 记录器 -->
<Loggers>
<!--
name:指定logger对象的名称,用于区别日志记录的来源
level:debug、info、warn、error、fatal,按重要程度从低到高,如果配置为warn,那么就只有warn、error、fatal的日志会被记录
additivity:用于指定日志消息是否要向上传递给父级日志记录器 -->
<Logger name="em.im.pve" level="debug" additivity="true"/>
<!-- 日志系统中的最顶层Logger,代表了日志层次结构的根节点,指定使用哪些Appender -->
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileInfo"/>
</Root>
</Loggers>
</Configuration>
Appenders中远不止控制台和文件这两种形式,还包括数据库、MQ、HTTP等多种输出形式,但业务上最常用的还是输出到文件,如果有其他的输出需求,再去官方阅读文档即可。不过我觉得对大多数人来说,困惑最多的可能还是Layout的使用,比如:[%d{yyyy-MM-dd HH:mm:ss,SSS}] [%p] - %l - %m%n
这到底是个啥?且听我一一拆解:
- %d{yyyy-MM-dd HH:mm:ss.SSS}:就是表示格式化输出的时间,如果你就是示例的输出,一个%d就行。
- [ ]就是字面意思,不用纠结。
- %level:日志级别和%p表示的结果一样。
- %pid:进程id
- %-5P:-表示左对齐,5表示占位空间
- %L:输出行号
- %m:日志内容
- %n:换行,一般就是一条日志一行
- %t:输入制表符
- %logger:和%c的意思一样,输出日志记录器的名称,后面紧跟的数字是精度,以减小记录器名称的大小
slf4j+logback
logback中有三个主要的核心模块:logback-core、logback-classic、logback-access
- logback-core:核心模块,提供了日志框架的基础功能,是我们通常意义上说的logback。
- logback-classic:实现了slf4j api。
- logback-access:用于日志访问,可以通过HTTP访问日志信息。
由于logback-classic中已经引入了logbak-core和slf4j-api,所以直接引入logback-classic即可:
<dependencies>
<!-- LogBack -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
</dependencies>
配置文件(logback.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!--配置根:
scan:设置为true是,如果配置文件发生更改,配置就会重新加载
scanPeriod:当scan为true时,会依据这个进行扫描查看是不是更新,默认单位是ms
-->
<configuration scan="true" scanPeriod="60 seconds">
<!-- 变量定义 -->
<property name="logPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %c{36} - %msg%n"/>
<property name="logPath" value="d:/logs/log.txt" />
<!-- 控制台输出 -->
<appender name="logConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<!-- 文件输出,记录INFO级别以上的日志 -->
<appender name="logFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}</file>
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
<!-- 滚动策略:当然还有FixedWindowRollingPolicy、SizeBasedTriggeringPolicy等策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--滚动输出的路径-->
<fileNamePattern>log/app-%d{yyyy-MM-dd}.txt</fileNamePattern>
<!--文件夹下保留文件的最大数量-->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 根日志级别设置为INFO,附加到控制台和文件输出 -->
<root level="INFO">
<appender-ref ref="logConsole"/>
<appender-ref ref="logFileInfo"/>
</root>
</configuration>