在日常工作中,无论是开发人员还是运维人员,都不得不和一个东西打交道,那便是日志。
什么是日志?
在计算机领域,日志文件(logfile)是一个记录了发生在运行中的操作系统或其他软件中的事件的文件,或者记录了在网络聊天软件的用户之间发送的消息。[来源请求]日志记录(Logging)是指保存日志的行为。最简单的做法是将日志写入单个存放日志的文件。
许多操作系统、软件框架和程序都包含日志系统。
广泛使用的一项日志标准是 syslog,它在互联网工程任务组(IETF)的 RFC 5424 中定义。syslog 标准使专门的标准化子系统得以生成、过滤、记录和分析日志消息。这可以减轻软件开发人员设计和编写自己的临时日志系统的难度。
为什么使用日志?
在软件开发过程中,开发者经常需要去调试程序,通过输出一些状态信息的输出,既可以查询了解出当前程序的运行状况,也可以根据其对代码进行优化。
在软件维护过程中,若软件出现问题,运维人员需要去排查程序出错原因,通过查看软件产生的系统日志,可以定位出错误点,便于后期修复。
简单来讲,通过日志可以分析目标程序当前的运行信息,以便开发者或维护者对程序进行了解或优化或排错。
日志分类
日志信息根据用户与记录内容的不同,分为调试日志、运行日志、异常日志等等。
但总的来讲,日志可以分为 2 类:
- 调试日志
- 系统日志(包括运行日志、异常日志等等)
调试日志
在软件开发过程中,对每个 Java 程序员,都很熟悉在有问题的代码中插入一些System.out.println
(后面简写为sout
)方法调用,来帮助观察程序运行的操作过程。
当然,一旦发现问题的根源, 就要将这些语句从代码中删去。若接下来又出现了问题, 又需要再插入几个调用sout
方法的语句。
可以发现,这无疑是一种很 low 的方法。
那么,有没有一种更好的解决方案呢?
首先,思考一下,sout
代码的目的是什么?
sout
代码的目的,是根据输出结果让开发者了解程序当前运行状态或决定对程序进行修复(优化)。
现在,继续分析一下:
- ① 可不可以在代码执行过程中跟踪以分析,而不是通过结果进行分析?
- ② 输出结果调试完成即废弃(需要再删除),那么有没有一种技术可以对其开关记录呢?
对于第一个问题,可以通过调试进行。调试,即 Debug ,Debug 可以在程序运行中暂停程序运行,查看程序在运行中的情况,这个我们可以借助 IDE 工具来实现。
此处需要注意的一点是,对代码运行中分析与对运行结果分析各有优劣:
- 运行中分析:粒度细,跟踪代码可以更好地理清其运行逻辑,也可以更好地定位 Bug 位置
- 对运行结果分析:粒度粗,可以对程序运行有一个大体的概念,高级程序员可根据经验判断 Bug 大体位置
对于第二个问题,可以通过日志框架来达到该目的。
在日志框架中,不仅可以对日志进行选择性地开关,还可以根据不同的日志级别,选择性地输出不同信息。
举个栗子:在软件项目开发过程中,可用 DEBUG 级别的日志记录 SQL 的运行信息,让程序员可以更好地认知程序,但是项目一旦上线,DEBUG 级别的日志信息就应该去除。毕竟,项目后期关心的是维护,这时候只关心 INFO、ERROR 级别的日志就行了。
系统日志
系统日志是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。用户可以通过它来排查错误发生的原因,或者寻找受到攻击时黑客留下的痕迹。
系统日志包括系统日志、应用程序日志和安全日志等等。
但简而言之,通过系统日志可以快速定位问题,良好的系统日志可以帮助你在最短的时间内发现问题。
日志级别
根据不同的情景需要,需要显示不同种类的日志信息。
因此,引入了日志框架,来对日志内容进行了进一步分级管理。
不同的日志框架划分的级别也不尽相同,但一般都包括以下 4 种常用级别:
ERROR
(错误):用于记录程序中发生的任何异常信息(Throwable)或者出错的业务逻辑WARN
(警告):用于记录一些用户输入参数错误INFO
(信息):默认日志级别,用于记录程序运行中的一些有用的信息(如程序运行开始、结束、耗时、重要参数等信息,一般会选择有意义的输出)DEBUG
(调试):用于记录一些运行中的中间参数信息(只允许在开发环境开启,在测试环境下选择性开启)
日志框架
由于软件系统在不断发展,变得愈来愈复杂。
首先,在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,我们仅仅需要集中精力完成系统的业务逻辑设计即可。
其次,框架一般是成熟,稳健的,它可以处理系统很多细节问题,比如:事务处理,安全性,数据流控制等问题。
最后,框架一般都经过很多人使用,且不断在升级,所以结构、扩展性也很好。
所以,我们为什么不使用已经存在的日志框架呢?
如何设计日志框架?
我们在开发过程中,基本都是集成前人开发好的日志框架到项目中,更多侧重于用,而并不清楚其具体实现,因此有时候会感到很慌。
不要慌,你可以跳出这些,思考一下:如果让你设计一款日志框架,你会怎么做?
你是否需要考虑以下问题:
- 如何记录不同位置的日志?
- 如何记录不同级别的日志?
- 如何控制日志输出的位置?
- 如何控制日志输出的内容和格式?
- 如何输出日志(同步还是异步)?
- 如何处理过时的日志文件(归档压缩删除)?
- 如何在实现时遵循一些规范来兼容早期定义的接口?
没错,日志框架基本上都需要围绕这些问题进行实现,你知道了它们,就能很容易的找到答案。
目前,市面上现有的成熟的日志框架有很多,比如常用的 Log4j、logback 等。
日志门面——管理日志框架
市面上现有的日志框架种类繁多,虽然它解决了一些问题,但是又引入了新的问题:
随着日志系统的发展,新的日志框架也在不断涌现,整个日志体系会变的越来越臃肿,又该如何去维护呢?
简而言之,应该如何去统一不同日志框架的复杂 API?
为了做到日志框架的统一,引入了日志门面。
日志门面,相当于一个接口,一个规范,定义了统一的 API,而日志框架就需要实现这个规范。
这就好像曾经学过的 JDBC,统一了多种数据库连接的规范,下面回忆下 JDBC:
JDBC(Java Data Base Connectivity,Java 数据库连接)是一种用于执行 SQL 语句的 Java API(Application Programming Interface),可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序,是连接数据库和 Java 应用程序的纽带。
因此,若出现一个新的日志框架,遵循日志门面的规范就好了。
那么,开发人员只需要去学习日志门面统一的 API,便可在各种日志框架中切换自如。
分析现有日志框架与日志门面
市面上现有的日志框架包括:
- JUL(Java Util Logging):JDK 原始日志框架
- Log4j: Apache 下一款开源的日志框架
- logback
- log4j2
市面上现有的日志门面包括:
- JCL(Jakarta Commons Logging):实现日志框架包括 JUL 和 log4j
- slf4j(Simple Logging Facade for Java)
日志框架及门面出现的历史顺序:
log4j –>JUL–>JCL–> slf4j –> logback –> log4j2
那么,现在来正式了解它们吧!
JUL(了解)
JUL(Java Util Logging),是 Java 原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用。
Log4j
什么是 Log4j?
Log4j 是 Apache 下的一款开源的日志框架。
通过在项目中使用 Log4J,我们可以:
- 控制日志信息输出到控制台、文件、甚至是数据库中
- 控制每一条日志的输出格式
- 通过定义日志的输出级别,可以更灵活的控制日志的输出过程(方便调试项目)
Log4j 组件
Log4J 主要由以下 3 部分组成:
- Logger:日志记录器,Logger 控制日志的输出级别与日志是否输出
- Appender:输出端,Appender 指定日志的输出位置(控制台、文件、数据库等等)
- Layout:日志格式化器,Layout 控制日志信息的输出格式
Logger
日志记录器,负责收集处理日志记录,实例的命名就是类的全限定名, Logger 的名字大小写敏感,其命名有继承机制。
比如name
为org.apache.commons
的logger
会继承 name
为org.apache
的logger
。
Log4J 中有一个特殊的 logger 叫做“root”,它是所有 logger 的根,也就意味着其他所有的 logger 都会直接或者间接地继承自 root(root logger 可以用Logger.getRootLogger()
方法获取)
Appender
Appender 用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。
Log4j 常用的输出目的地如下表:
Appender 类型 | 说明 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件中 |
DailyRollingFileAppender | 将日志输出到一个日志文件,并且每天输出到一个新的文件 |
RollingFileAppender | 将日志信息输出到一个日志文件,并且指定文件的尺寸 当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件 |
JDBCAppender | 将日志保存到数据库中 |
在快速入门中,我们将详细介绍。
Layout
Layouts用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。
Log4j 常用的 Layout 如下表:
类型 | 说明 |
---|---|
HTMLLayout | 格式化日志输出为HTML表格形式 |
SimpleLayout | 简单的日志输出格式化,打印的日志格式为(info - message) |
PatternLayout | 最强大的格式化器,可以根据自定义格式输出日志 若没有指定转换格式, 就是用默认的输出格式(HTMLLayout) |
PatternLayout 的自定义格式说明如下,一般在log4j.properties
配置文件使用:
1 | * log4j 采用类似 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下: |
快速入门
首先,在 Maven 项目添加相关依赖:1
2
3
4
5
6<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
之后编写代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import org.apache.log4j.Logger;
import org.testng.annotations.Test;
public class Log4jTest {
// 创建日志记录器对象
public final static Logger logger = Logger.getLogger(Log4jTest.class);
public void test() {
// 日志记录输出
logger.info("初识 log4j");
// 日志级别
logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
logger.error("error"); // 错误信息,但不会影响系统运行
logger.warn("warn"); // 警告信息,可能会发生问题
logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
logger.debug("debug"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数
logger.trace("trace"); // 追踪信息,记录程序的所有流程信息
}
}
运行后会发现控制台输出:1
2
3log4j:WARN No appenders could be found for logger (cn.lovike.basicjava.log.Log4jTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
输出日志到控制台
很显然,Log4j 仅仅只要日志记录器对象是不够的,还需要Appender
用来指定日志输出的位置,也需要 Layout 来控制日志输出内容的格式。
这些配置,都通过log4j.properties
配置文件来完成:1
2
3
4
5
6# 指定日志的输出级别需大于等于 INFO 级别,输出端为 console
log4j.rootLogger=INFO,Console
# 控制台输出配置
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
输出日志到文件
当然,日志输出到控制台远远是不够的,一般还需要输出到文件。
那么,我们可以修改下log4j.properties
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 指定日志的输出级别需大于等于 INFO 级别,输出端为 console 与 file
log4j.rootLogger=INFO,console,file
# 控制台输出配置
### 指定 Appender 对象为 ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
### 指定消息格式 Layout 为 PatternLayout
log4j.appender.console.layout=org.apache.log4j.PatternLayout
### 对消息格式的内容进行配置
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
# 文件输出配置
### 指定 Appender 对象为 FileAppender
log4j.appender.file=org.apache.log4j.FileAppender
### 指定消息格式 Layout 为 PatternLayout
log4j.appender.file.layout=org.apache.log4j.PatternLayout
### 对消息格式的内容进行配置
log4j.appender.file.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
### 指定日志文件的保存路径
log4j.appender.file.file=log/log4j.log
### 指定日志文件的编码
log4j.appender.file.encoding=UTF-8
文件按大小拆分
但是呀,若记录的信息越来越多,日志文件就会变的越来越大。
为此,可以将文件按大小进行拆分。
具体而言,在配置文件指定Appender
使用FileAppender
的子类RollingFileAppender
即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24# 指定日志的输出级别续大于 INFO 级别,输出端为 console 与 rollingFile
log4j.rootLogger=INFO,console,rollingFile
# 控制台输出配置
### 指定 Appender 对象为 ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
### 指定消息格式 Layout 为 PatternLayout
log4j.appender.console.layout=org.apache.log4j.PatternLayout
### 对消息格式的内容进行配置
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
# 文件输出配置
### 指定 Appender 对象为 RollingFileAppender
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
### 指定消息格式 Layout 为 PatternLayout
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
### 对消息格式的内容进行配置
log4j.appender.rollingFile.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
### 指定日志文件的保存路径
log4j.appender.rollingFile.file=log/log4j.log
### 指定日志文件的编码
log4j.appender.rollingFile.encoding=UTF-8
### 指定需要拆分时的文件大小
log4j.appender.rollingFile.maxFileSize=1MB
### 指定生成日志文件的数量(超过该数量会循环覆盖,新的日志替换旧的)
log4j.appender.rollingFile.maxBackupIndex=10
文件按日期拆分
当然,日志还可以按照按时间格式进行拆分,这种配置也更为常用。
为此,在配置文件指定Appender
使用FileAppender
的子类DailyRollingFileAppender
即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 指定日志的输出级别续大于 INFO 级别,输出端为 console 与 dailyFile
log4j.rootLogger=INFO,console,dailyFile
# 控制台输出配置
### 指定 Appender 对象为 ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
### 指定消息格式 Layout 为 PatternLayout
log4j.appender.console.layout=org.apache.log4j.PatternLayout
### 对消息格式的内容进行配置
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
# 文件输出配置
### 指定 Appender 对象为 DailyRollingFileAppender
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
### 指定消息格式 Layout 为 PatternLayout
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
### 对消息格式的内容进行配置
log4j.appender.dailyFile.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
### 指定日志文件的保存路径
log4j.appender.dailyFile.file=log/log4j.log
### 指定日志文件的编码
log4j.appender.dailyFile.encoding=UTF-8
### 指定日期拆分规则,一般默认为按天进行拆分,即参数为: '.'yyyy-MM-dd
log4j.appender.dailyFile.datePattern='.'yyyy-MM-dd
输出日志到数据库(了解)
首先在数据库创建一张日志表:1
2
3
4
5
6
7
8
9
10
11
12CREATE TABLE `log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`project_name` varchar(255) DEFAULT NULL COMMENT '项目名',
`gmt_create` varchar(255) DEFAULT NULL COMMENT '创建时间',
`level` varchar(255) DEFAULT NULL COMMENT '优先级',
`category` varchar(255) DEFAULT NULL COMMENT '所在类的全名',
`file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ', `thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
`line` varchar(255) DEFAULT NULL COMMENT '行号',
`all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
`message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
PRIMARY KEY (`log_id`)
);
其次,对配置文件进行修改:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 指定日志的输出级别续大于 INFO 级别,输出端为 console 与 logDB
log4j.rootLogger=trace,console,logDB
# 控制台输出配置
### 指定 Appender 对象为 ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
### 指定消息格式 Layout 为 PatternLayout
log4j.appender.console.layout=org.apache.log4j.PatternLayout
### 对消息格式的内容进行配置
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
# MySQL
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=123
log4j.appender.logDB.Sql=INSERT INTO log(project_name,gmt_create,level,category,file_name,thread_name,line,all_categ ory,message) values('itcast','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
最好,日志产生时就会输出到数据库啦。
JCL(了解)
什么是 JCL?
JCL(Jakarta Commons Logging),是 Apache 提供的一个通用日志 API。
JCL为”部分 Java 日志实现”提供一个统一的接口,虽然它自身也提供一个日志的实现(SimpleLog),但是由于功能非常弱 ,所以一般不会单独使用它。
JCL 允许开发人员使用不同的具体日志实现工具:SimpleLog、JUL、Log4j,如下图:
JCL 有两个基本的抽象类:
- Log:基本记录器
- LogFactory:负责创建 Log 实例
快速入门
首先,在 Maven 项目添加相关依赖:1
2
3
4
5
6<!-- JCL -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
之后编写代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.testng.annotations.Test;
public class JCLLog4jTest {
public void test() {
// 创建日志对象
Log log = LogFactory.getLog(JCLLog4jTest.class);
// 日志记录输出
log.fatal("fatal");
log.error("error");
log.warn("warn");
log.info("info");
log.debug("debug");
}
}
原理
JCL 通过 LogFactory 动态加载 Log 实现类,具体而言它是根据LogFactoryImpl
中的classesToDiscover
数组来判断使用哪个日志框架实现,源码如下:1
2
3
4
5
6
7public class LogFactoryImpl extends LogFactory {
private static final String[] classesToDiscover = new String[]
{"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"};
}
根据项目现有日志框架情况,按优先级选择一个进行加载:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class LogFactoryImpl extends LogFactory {
......
private Log discoverLogImplementation(String logCategory)
......
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
if (result == null) {
throw new LogConfigurationException
("No suitable Log implementation");
}
return result;
}
}
Slf4j
与 JCL 相比,Slf4j 支持的日志实现框架更多,它也是目前市面上最流行的日志门面。
现在的项目中,基本上都是使用 Slf4j 作为我们的日志系统。
什么是 Slf4j?
Slf4j(Simple Logging Facade For Java),即简单日志门面。
Slf4j 主要是为了给 Java 日志访问提供一套标准、规范的 API 框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如 log4j 和 logback 等。
当然 Slf4j 自己也提供了功能较为简单的实现(slf4j-simple),但是一般很少用到。
对于一般的Java项目而言,日志框架会选择 slf4j-api 作为门面,配上具体的实现框架(log4j、logback 等),中间使用桥接器完成桥接。
Slf4j 日志门面主要提供两大功能:
- 日志框架的绑定
- 日志框架的桥接
快速入门
首先,在 Maven 项目添加相关依赖:1
2
3
4
5
6
7
8
9
10
11
12<!--slf4j 核心:使用 slf4j 必须添加 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!-- slf4j 必须使用一个框架实现,如 slf4j-simple -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.27</version>
</dependency>
之后,编写代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
public class Slf4jTest {
// 声明日志对象
public final static Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
public void test() {
//打印日志信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
// 使用占位符输出日志信息
String name = "lovike";
Integer age = 18;
LOGGER.info("用户姓名:{},年龄{}", name, age);
// 将系统异常信息写入日志
try {
int i = 1 / 0;
} catch (Exception e) {
LOGGER.info("输出异常:", e);
}
}
}
Logback
什么是 Logback?
Logback 是由 log4j 创始人设计的另一个开源日志组件,性能比 log4j 要好。
Logback 主要分为三个模块:
- logback-core:其它两个模块的基础模块
- logback-classic:log4j 的一个改良版本,同时它完整实现了 slf4j API
- logback-access:访问模块与 Servlet 容器集成,供 HTTP 来访问日志
快速入门
首先,在 Maven 项目添加相关依赖:1
2
3
4
5
6
7
8
9
10
11
12<!--slf4j 核心:使用 slf4j 必须添加 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!-- slf4j 必须使用一个框架实现,如 logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
之后,编写代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
public class LogbackTest {
public final static Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
public void test() {
// 打印日志信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
默认无配置情况下,该日志是输出到控制台的,因此我们还需要配置它。
Logback 配置
Logback 在启动时会依次读取以下类型配置文件:
- logback.groovy
- logback-test.xml
- logback.xml
- 若以上均不存在则采用默认配置
与 Log4j 类似,Logback 也包含 3 个组件:
- Logger:日志的记录器,主要用于存放日志对象,也可以定义日志类型与级别
- Layout:日志格式化器,用于控制日志信息的输出格式。Logback 中 Layout 对象会被封
装在 encoder 中 - Appender:输出端,用于指定日志的输出位置(控制台、文件、数据库等等),常用的有以下:
ch.qos.logback.core.ConsoleAppender
:输出到控制台ch.qos.logback.core.FileAppender
:输出到文件ch.qos.logback.core.rolling.RollingFileAppender
:追加文件
下面对日志输出到控制台,文件(按大小切分或按日期切分)进行详细说明
日志输出
日志输出到控制台
在资源文件夹下添加logback.xml
文件,进行如下配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<configuration>
<!-- 指定日志输出格式 -->
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %m%n"/>
<!--
日志输出格式说明
%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
%c:类的完整名称
%M:方法
%L:行号
%t 或 %thread:线程名称
%m 或 %msg:信息
%-5level 或 %-5le 或 %-5p:从左显示 5 个字符宽度
%n:换行
-->
<!-- Appender: 指定日志输出位置为控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式配置:${pattern} 表示使用前面设置的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!--
root:根级别的 logger 对象,通过 <appender-ref> 元素可以引用一个或多个 appender,用于将相关 appender 添加到该 logger
level:用于设置允许打印的日志级别,大小写均可,详细说明如下:
ALL: 允许打印所有级别日志
TRACE:允许打印 TRACE 以上(包括)级别的日志
DEBUG:允许打印 DEBUG 以上(包括)级别的日志
INFO: 允许打印 INFO 以上(包括)级别的日志
WARN: 允许打印 WARN 以上(包括)级别的日志
ERROR:允许打印 ERROR 以上(包括)级别的日志
OFF :不允许打印日志
-->
<root level="ERROR">
<appender-ref ref="console"/>
</root>
</configuration>
日志输出到文件
修改logback.xml
文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<configuration>
<!-- 指定日志输出格式 -->
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %m%n"/>
<!--
日志输出格式说明
%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
%c:类的完整名称
%M:方法
%L:行号
%thread:线程名称
%m 或 %msg:信息
%-5level:从左显示 5 个字符宽度
%n:换行
-->
<!-- 自定义日志文件存放目录 -->
<property name="log_dir" value="log/"/>
<!-- Appender: 指定日志输出位置为控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式配置:${pattern} 表示使用前面设置的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- Appender: 指定日志输出位置为文件 -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!-- 日志格式配置:${pattern} 表示使用前面设置的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 指定日志输出位置 -->
<file>${log_dir}/logback.log</file>
</appender>
<!--
root:根级别的 logger 对象,通过 <appender-ref> 元素可以引用一个或多个 appender,用于将相关 appender 添加到该 logger
level:用于设置允许打印的日志级别,大小写均可,详细说明如下:
ALL: 允许打印所有级别日志
TRACE:允许打印 TRACE 以上(包括)级别的日志
DEBUG:允许打印 DEBUG 以上(包括)级别的日志
INFO: 允许打印 INFO 以上(包括)级别的日志
WARN: 允许打印 WARN 以上(包括)级别的日志
ERROR:允许打印 ERROR 以上(包括)级别的日志
OFF :不允许打印日志
-->
<root level="ALL">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</configuration>
文件拆分
有时候记录的信息越来越多,日志文件就会变的越来越大。
为此,可以将文件按大小或日期进行拆分。
修改logback.xml
文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<configuration>
<!-- 指定日志输出格式 -->
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %m%n"/>
<!--
日志输出格式说明
%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
%c:类的完整名称
%M:方法
%L:行号
%thread:线程名称
%m 或 %msg:信息
%-5level:从左显示 5 个字符宽度
%n:换行
-->
<!-- 自定义日志文件存放目录 -->
<property name="log_dir" value="log/"/>
<!-- Appender: 指定日志输出位置为控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式配置:${pattern} 表示使用前面设置的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- Appender: 指定日志输出位置为文件,并进行相关拆分 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 日志输出路径 -->
<file>${log_dir}/roll_logback.log</file>
<!-- 指定日志文件拆分和压缩规则 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 通过指定压缩文件名称,来确定分割文件方式 -->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
root:根级别的 logger 对象,通过 <appender-ref> 元素可以引用一个或多个 appender,用于将相关 appender 添加到该 logger
level:用于设置允许打印的日志级别,大小写均可,详细说明如下:
ALL: 允许打印所有级别日志
TRACE:允许打印 TRACE 以上(包括)级别的日志
DEBUG:允许打印 DEBUG 以上(包括)级别的日志
INFO: 允许打印 INFO 以上(包括)级别的日志
WARN: 允许打印 WARN 以上(包括)级别的日志
ERROR:允许打印 ERROR 以上(包括)级别的日志
OFF :不允许打印日志
-->
<root level="ALL">
<appender-ref ref="console"/>
<appender-ref ref="rollFile"/>
</root>
</configuration>
文件日志过滤
在文件日志中,可以选择性的对需要级别的日志进行输出,此时需要配置拦截器:1
2
3
4
5
6
7
8<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置拦截日志级别 -->
<level>error</level>
<!-- 匹配该日志级别允许通过 -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配的进行拦截 -->
<onMismatch>DENY</onMismatch>
</filter>
异步日志
对于文件日志的输出,可以采用异步方式以提高性能:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<configuration>
<!-- 指定日志输出格式 -->
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %m%n"/>
<!--
日志输出格式说明
%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
%c:类的完整名称
%M:方法
%L:行号
%thread:线程名称
%m 或 %msg:信息
%-5level:从左显示 5 个字符宽度
%n:换行
-->
<!-- 自定义日志文件存放目录 -->
<property name="log_dir" value="log/"/>
<!-- Appender: 指定日志输出位置为控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式配置:${pattern} 表示使用前面设置的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- Appender: 指定日志输出位置为文件,并进行相关拆分 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 日志输出路径 -->
<file>${log_dir}/roll_logback.log</file>
<!-- 指定日志文件拆分和压缩规则 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 通过指定压缩文件名称,来确定分割文件方式 -->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!-- 文件拆分大小 -->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!-- 异步日志 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="rollFile"/>
</appender>
<!--
root:根级别的 logger 对象,通过 <appender-ref> 元素可以引用一个或多个 appender,用于将相关 appender 添加到该 logger
level:用于设置允许打印的日志级别,大小写均可,详细说明如下:
ALL: 允许打印所有级别日志
TRACE:允许打印 TRACE 以上(包括)级别的日志
DEBUG:允许打印 DEBUG 以上(包括)级别的日志
INFO: 允许打印 INFO 以上(包括)级别的日志
WARN: 允许打印 WARN 以上(包括)级别的日志
ERROR:允许打印 ERROR 以上(包括)级别的日志
OFF :不允许打印日志
-->
<root level="ALL">
<appender-ref ref="console"/>
<appender-ref ref="rollFile"/>
</root>
</configuration>
自定义 Logger
前面定义的 root 时根级别的 Logger,我们也可以自定义 Logger:1
2
3
4<!--自定义logger:name 指定包名,level 定义级别,additivity 表示是否从 root Logger 继承配置-->
<logger name="cn.lovike" level="debug" additivity="false">
<appender-ref ref="console"/>
</logger>
logback-access
logback-access 模块可与 Servlet 容器(如 Tomcat、Jetty)进行集成,用于提供 HTTP 访问日志。
下面使用 logback-access 模块来替换 tomcat 的访问日志:
- 将
logback-access.jar
与logback-core.jar
复制到$TOMCAT_HOME/lib/
目录下 修改 Tomcat 的
conf/server.xml
,在 Host 元素中添加:1
<Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
logback 默认会在 Tomcat 的
conf
下查找文件logback-access.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<configuration>
<!-- always a good activate OnConsoleStatusListener -->
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
<property name="LOG_DIR" value="${catalina.base}/logs"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
</rollingPolicy>
<encoder>
<!-- 访问日志的格式 -->
<pattern>combined</pattern>
</encoder>
</appender>
<appender-ref ref="FILE"/>
</configuration>具体可见官方配置
其他:日志配置转换
logback 官方提供了将log4j.properties
文件转换成logback.xml
文件的功能。
具体可按需要去官方进行转换
Log4j2
Apache Log4j 2 是对 Log4j 的升级版,参考了 logback 的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:
- 异常处理,在 logback 中,Appender 中的异常不会被应用感知到,但是在 log4j2 中,提供了一些异常处理机制
- 性能提升, log4j2 相较于 log4j 和 logback 都具有很明显的性能提升
- 自动重载配置,参考了 logback 的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产 上可以动态的修改日志的级别而不需要重启应用
- 无垃圾机制,log4j2 在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集 导致的 JVM GC
部分后续再研究。
SpringBoot 中的日志
SpringBoot 默认使用 SLF4J 作为日志门面,logback 作为日志实现来记录日志。
快速入门
首先,在 Maven 项目添加相关依赖:1
2
3
4<dependency>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
注
:spring-boot-starter-web
已包含它。
其次,编写代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SpringbootLogApplicationTests {
//记录器
public static final Logger LOGGER = LoggerFactory.getLogger(SpringbootLogApplicationTests.class);
public void contextLoads() {
// 打印日志信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info"); // 默认日志级别
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
配置详解
在 Spring Boot 项目中,可以在application.yml
文件修改日志的配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14# 日志
logging:
# 日志输出路径
file.path: var/log/community.log
# 日志最大值
file.max-size: 200MB
# 日志历史
file.max-history: 30
# 设置输出的日志级别
level:
# 输出全部 info 日志
root: info
# 输出特定包 debug 日志
cn.lovike.springboot.community.system.mapper: debug
优先使用其他日志配置
当然,若在项目类路径下存在相应日志框架的配置文件,SpringBoot 将不使用默认application.yml
中的配置,而是使用日志框架的配置。具体见下表:
日志框架 | 配置文件 |
---|---|
Logback | logback-spring.xml , logback.xml |
Log4j2 | log4j2-spring.xml , log4j2.xml |
JUL | logging.properties |
环境配置与切换
由于开发环境的不同,因此不同环境的日志配置可能不同,我们最好使用logback-spring.xml
来设置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<configuration>
<!-- 指定日志输出格式 -->
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %m%n"/>
<!--
日志输出格式说明
%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
%c:类的完整名称
%M:方法
%L:行号
%thread:线程名称
%m 或 %msg:信息
%-5level:从左显示 5 个字符宽度
%n:换行
-->
<!-- 自定义日志文件存放目录 -->
<property name="log_dir" value="log/"/>
<!-- Appender: 指定日志输出位置为控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式配置:${pattern} 表示使用前面设置的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- Appender: 指定日志输出位置为文件,并进行相关拆分 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<springProfile name="dev">
<pattern>${pattern}</pattern>
</springProfile>
<springProfile name="pro">
<pattern>%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
</springProfile>
</encoder>
<!-- 日志输出路径 -->
<file>${log_dir}/roll_logback.log</file>
<!-- 指定日志文件拆分和压缩规则 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 通过指定压缩文件名称,来确定分割文件方式 -->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!-- 文件拆分大小 -->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置拦截日志级别 -->
<level>error</level>
<!-- 匹配该日志级别允许通过 -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配的进行拦截 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步日志 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="rollFile"/>
</appender>
<!--
root:根级别的 logger 对象,通过 <appender-ref> 元素可以引用一个或多个 appender,用于将相关 appender 添加到该 logger
level:用于设置允许打印的日志级别,大小写均可,详细说明如下:
ALL: 允许打印所有级别日志
TRACE:允许打印 TRACE 以上(包括)级别的日志
DEBUG:允许打印 DEBUG 以上(包括)级别的日志
INFO: 允许打印 INFO 以上(包括)级别的日志
WARN: 允许打印 WARN 以上(包括)级别的日志
ERROR:允许打印 ERROR 以上(包括)级别的日志
OFF :不允许打印日志
-->
<root level="ALL">
<appender-ref ref="console"/>
<appender-ref ref="rollFile"/>
</root>
<!--自定义logger:name 指定包名,level 定义级别,additivity 表示是否从 root Logger 继承配置-->
<logger name="cn.lovike" level="debug" additivity="false">
<appender-ref ref="console"/>
</logger>
</configuration>
之后,就可以根据相应环境在application.yml
文件进行切换啦:1
2
3
4spring:
profiles:
active:
dev # 开发环境