常用日志框架
j.u.l
j.u.l是java.util.logging包的简称,是JDK在1.4版本中引入的Java原生日志框架。
Java Logging API提供了七个日志级别用来控制输出。这七个级别分别是:SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST。
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不仅仅是Log4j的一个升级版本了,而是从头到尾被重写的,可以认为这其实就是完全不同的两个框架。
注:
logback是直接实现了slf4j的接口,不消耗内存和计算开销的。
slf4j的api在调用log4j时需要一个适配层(所以maven配置里slf4j+logback只需要两个依赖,而log4j需要3个依赖)
我们想要在应用中打印日志的时候,可以使用以上四种类库中的任意一种。比如想要使用Log4j,那么只要依赖Log4j的jar包,配置好配置文件并且在代码中使用其API打印日志就可以了
但是阿里巴巴开发文档规定:
所以我们一般使用都是门面框架slf4j和上述四种之一的API结合。
日志门面
什么是日志门面
日志门面,是门面模式的一个典型的应用。
门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。
就像前面介绍的几种日志框架一样,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。
为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。
在软件开发领域有这样一句话:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。而门面模式就是对于这句话的典型实践
为什么要使用日志门面
解耦
为了在应用中屏蔽掉底层日志框架的具体实现:即使有一天要更换代码的日志框架,只需要修改jar包,最多再改改日志输出相关的配置文件就可以了。这就是解除了应用和日志框架之间的耦合。
常用日志门面
SLF4J(用的比较多)
Java简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现。可以在软件部署的时候决定要使用的 Logging 框架,目前主要支援的有Java Logging API、Log4j及logback等框架。以MIT 授权方式发布。
SLF4J 的作者就是 Log4j和Logback 的作者 Ceki Gülcü,他宣称 SLF4J 比 Log4j 更有效率,而且比 Apache Commons Logging (JCL) 简单、稳定。
其实,SLF4J其实只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖Log4j、logback等日志框架的 。
由于SLF4J比较常用,这里多用一些篇幅,再来简单分析一下SLF4J,主要和Log4J做一下对比。相比较于Log4J的API,SLF4J有以下几点优势:
- Log4j 提供 TRACE, DEBUG, INFO, WARN, ERROR 及 FATAL 六种纪录等级,但是 SLF4J 认为 ERROR 与 FATAL 并没有实质上的差别,所以拿掉了 FATAL 等级,只剩下其他五种。
- 大部分人在程序里面会去写logger.error(exception),其实这个时候Log4j会去把这个exception tostring。真正的写法应该是logger(message.exception);而SLF4J就不会使得程序员犯这个错误。
- Log4j间接的在鼓励程序员使用string相加的写法(这种写法是有性能问题的),而SLF4J就不会有这个问题 ,你可以使用
logger.error(“{}is+serviceid”,serviceid);
- 使用SLF4J可以方便的使用其提供的各种集体的实现的jar。(类似commons-logger)
- 从commons–logger和Log4j merge非常方便,SLF4J也提供了一个swing的tools来帮助大家完成这个merge。
- SLF4J 只支持 MDC,不支持 NDC。
- 提供字串内容替换的功能,会比较有效率,说明如下:
1 | // 传统的字符串产生方式,如果没有要记录Debug等级的信息,就会浪费时间在产生不必要的信息上 |
commons-logging
Apache Commons Logging是一个基于Java的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供API,日志实现和包装器实现。
commons-logging和SLF4J的功能是类似的,主要是用来做日志 门面的。提供更加好友的API工具。
总结
在Java生态体系中,围绕着日志,有很多成熟的解决方案。关于日志输出,主要有两类工具。
一类是日志框架,主要用来进行日志的输出的,比如输出到哪个文件,日志格式如何等。
另外一类是日志门面,主要一套通用的API,用来屏蔽各个日志框架之间的差异的。
所以,对于Java工程师来说,关于日志工具的使用,最佳实践就是在应用中使用如Log4j + SLF4J 这样的组合来进行日志输出。
这样做的最大好处,就是业务层的开发不需要关心底层日志框架的实现及细节,在编码的时候也不需要考虑日后更换框架所带来的成本。这也是门面模式所带来的好处。
综上,请不要在你的Java代码中出现任何Log4j等日志框架的API的使用,而是应该直接使用SLF4J这种日志门面。
Slf4j+Logback
默认日志 Logback:
springboot推荐的日志类库是slf4j、日志系统为logback
默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台
时间日期:精确到毫秒
日志级别:ERROR, WARN, INFO, DEBUG or TRACE
进程ID
分隔符:— 标识实际日志的开始
线程名:方括号括起来(可能会截断控制台输出)
Logger名:通常使用源代码的类名
日志内容
添加日志依赖
假如maven依赖中添加spring-boot-starter-logging:
1 | <dependency> |
但是呢,实际开发中我们不需要直接添加该依赖。
你会发现spring-boot-starter其中包含了 spring-boot-starter-logging,该依赖内容就是 Spring Boot 默认的日志框架 logback。
如果工程中有用到了Thymeleaf,而Thymeleaf依赖包含了spring-boot-starter,最终我只要引入Thymeleaf即可。
1 | <dependency> |
实际上使用只要指定了spring-boot-starter,就不用再添加依赖,使用默认的依赖logback
控制台输出
日志级别从低到高分为:
TRACE < DEBUG < INFO < WARN < ERROR < FATAL。
如果设置为 WARN ,则低于 WARN 的信息都不会输出。
Spring Boot中默认配置ERROR、WARN和INFO级别的日志输出到控制台。
您还可以通过启动您的应用程序 –debug 标志来启用“调试”模式(开发的时候推荐开启),以下两种方式皆可:
- 在运行命令后加入–debug标志,如:
$ java -jar springTest.jar --debug
- 在application.properties中配置debug=true,该属性置为true的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,但是你自己应用的日志并不会输出为DEBUG级别。
如果每次都写这行代码会很麻烦,可以使用注解,但是需要使用lombok
:
允许注解处理,Settings -> Compiler -> Annotation Processors
可以使用{}
占位符来拼接字符串,而不需要使用““+””
来连接字符串。
文件输出
默认情况下,Spring Boot将日志输出到控制台,不会写到日志文件。
使用Spring Boot喜欢在application.properties或application.yml配置,这样只能配置简单的场景:保存路径、日志格式等,复杂的场景(区分 info 和 error 的日志、每天产生一个日志文件等)满足不了,只能自定义配置。
默认会在设置的 path
生成一个spring.log
文件。
如果要编写除控制台输出之外的日志文件,则需在application.properties中设置logging.file或logging.path属性。
logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log
如果只配置 logging.file,会在项目的当前路径下生成一个 xxx.log 日志文件。
如果只配置 logging.path,在 /var/log文件夹生成一个日志文件为 spring.log
注:二者不能同时使用,如若同时使用,则只有logging.file生效(没有logback-spring.xml配置文件,系统只认识logging.file,不认识logging.path)
默认情况下,日志文件的大小达到10MB时会切分一次,产生新的日志文件,默认级别为:ERROR、WARN、INFO
级别控制
所有支持的日志记录系统都可以在Spring环境中设置记录级别(例如在application.properties中)
格式为:logging.level.* = LEVEL
logging.level:日志级别控制前缀,*为包名或Logger名
LEVEL:选项TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
每个Logger都被了一个日志级别(log level),用来控制日志信息的输出。日志级别从高到低分为:
A:off 最高等级,用于关闭所有日志记录。
B:fatal 指出每个严重的错误事件将会导致应用程序的退出。
C:error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
D:warn 表明会出现潜在的错误情形。
E:info 一般和在粗粒度级别上,强调应用程序的运行全程(对用户有用)。
F:debug 一般用于细粒度级别上,对调试应用程序非常有帮助(对程序调试有利)。
G:all 最低等级,用于打开所有日志记录。
例:如果设置了级别为info,则debug级别就不会被打印出来
1 | 举例: |
自定义日志配置(适合复杂的日志需求)
根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:
Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
Log4j2:log4j2-spring.xml, log4j2.xml
JDK (Java Util Logging):logging.properties
Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项(下面会提到)。
默认的命名规则,并且放在 src/main/resources 下面即可
如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,application.yml可以通过logging.config属性指定自定义的名字:
logging.config=classpath:logging-config.xml
虽然一般并不需要改变配置文件的名字,但是如果你想针对不同运行时Profile使用不同的日志配置,这个功能会很有用。
一般不需要这个属性,而是直接在logback-spring.xml中使用springProfile配置,不需要logging.config指定不同环境使用不同配置文件。
springProfile配置:
根节点包含的属性
1 | <configuration scan="true" scanPeriod="10 seconds"> |
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
根节点<configuration>
有5个子节点
子节点一 <root>
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。
1 | <root level="debug"> |
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG。
可以包含零个或多个元素,标识这个appender将会添加到这个loger。上述的console和file是已经指定的appender的name
子节点二:<contextName>
设置上下文名称
每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称,一般来说我们不用这个属性,可有可无。
1 | <contextName>logback</contextName> |
子节点三:<property>
设置变量
1 | <property name="logback.logdir" value="/Users/inke/dev/log/tomcat"/> |
用来定义变量值的标签, 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使${}
来使用变量。
这里可以通过 application.yml 传递参数过来。
多环境配置下,通过 application.yml 传递参数过来,< property >取不到环境参数,得用< springProperty >
1 | <springProperty scope="context" name="appname" source="logback.appname"/> |
子节点四:<appender>
appender用来格式化日志输出节点,有俩个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。
控制台输出ConsoleAppender
:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
例子1:
1 |
|
可以看到layout
和encoder
,都可以将事件转换为格式化后的日志记录.
但是控制台输出使用layout
,文件输出使用encoder
,具体原因可参考http://blog.csdn.net/cw_hello1/article/details/51969554
1 | layout:将一个event事件转换成一个字符串,不能控制将字符串写出到文件 |
例子2:
1 |
|
1 | 18:15:22.148 logback-demo [http-nio-9010-exec-1] INFO c.e.demo.controller.UserContorller - 日志输出 info |
输出到文件 RollingFileAppender
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
另一种常见的日志输出到文件,随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。
RollingFileAppender用于切分文件日志:
1 | <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
<encoder>
表示对日志进行编码:
%d{HH: mm:ss.SSS}——日志输出时间
%thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
%-5level——日志级别,并且使用5个字符靠左对齐
%logger{36}——日志输出者的名字
%msg——日志消息
%n——平台的换行符
ThresholdFilter为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志~
如果同时有<File>
和<FileNamePattern>
,根据日期分割日志
如果要区分 Info
和 Error
级别的日志,那么需要使用过滤规则的策略
子节点五<loger>
1 | <logger name="com.swx" additivity="false" level="DEBUG"> |
<loger>
用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>
。
<loger>
仅有一个name属性,一个可选的level和一个可选的addtivity属性。
name:用来指定受此loger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前loger将会继承上级的级别。
addtivity:是否向上级loger传递打印信息。默认是true。
例子:
1 | package com.dudu.controller; |
第一种:带有loger的配置,不指定级别,不指定appender
logback-spring.xml增加 loger 配置如下:
1 | <logger name="com.dudu.controller" /> |
1 | <root level="info"> |
当执行com.dudu.controller.LearnController类的login方法时,LearnController 在包com.dudu.controller中,所以首先执行
root接到下级传递的信息,交给已经配置好的名为“console”的appender处理,“console” appender 将信息打印到控制台;
打印结果如下:
1 | 16:00:17.407 logback [http-nio-8080-exec-8] INFO com.dudu.controller.LearnController - 日志输出 info |
第二种:带有多个loger的配置,指定级别,指定appender
logback-spring.xml增加 loger 配置如下:
1 | <configuration> |
控制com.dudu.controller.LearnController类的日志打印,打印级别为“WARN”;
additivity属性为false,表示此loger的打印信息不再向上级传递;
指定了名字为“console”的appender;
这时候执行com.dudu.controller.LearnController类的login方法时,先执行
打印结果如下:
1 | 16:00:17.408 logback [http-nio-8080-exec-8] WARN com.dudu.controller.LearnController - 日志输出 warn |
当然如果你把additivity=”false”改成additivity=”true”的话,就会打印两次,因为打印信息向上级传递,logger本身打印一次,root接到后又打印一次。
注意:
1 | <configuration> |
范围有重叠的话,范围小的有效。
配置多个环境日志输出
1 | <configuration> |
application.yml增加环境选择的配置active: dev
1 | server: |
active: 【test、dev、prod】
,根据 active
的环境,自动采用上面配置的springProfile
的 logger
日志
自定义日志路径(application.yml)
application.yml增加日志相关自定义配置
1 | logback: |
在logback-spring.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
Log4j2
Log4j2中,分为 API(log4j-api)和实现(log4j-core) 两个模块。API 和slf4j 是一个类型,属于日志抽象/门面,而实现部分,才是Log4j 2的核心
org.apache.logging.log4j » log4j-api
org.apache.logging.log4j » log4j-core
最强的异步性能
这个特性,算是Log4j2最强之处了。log4j2 在目前JAVA中的日志框架里,异步日志的性能是最高的,没有之一。
先来看一下,几种日志框架benchmark对比结果(log4j2官方测试结果):
从图上可以看出,log4j2的异步(全异步,非混合模式)下的性能,远超log4j1和logback,简直吊打。压力越大的情况下,吞吐上的差距就越大。在64线程测试下,log4j2的吞吐达到了180w+/s,而logback/log4j1只有不到20w,相差近十倍
零GC(Garbage-free)
从2.6版本开始(2016年),log4j2 默认就以零GC模式运行了。什么叫零GC呢?就是不会由于log4j2而导致GC。
log4j2 中各种Message对象,字符串数组,字节数组等全部复用,不重复创建,大大减少了无用对象的创建,从而做到“零GC”。
更高性能 I/O 写入的支持
log4j 还提供了一个MemoryMappedFileAppender,I/O 部分使用MemoryMappedFile来实现,可以得到极高的I/O性能。不过在使用MemoryMappedFileAppender之前,得确定你足够了解MemoryMappedFile的相关知识,否则不要轻易使用呦。
更强大的参数格式化
API模块和slf4j相比,提供了更丰富的参数格式化功能。
使用{}占位符格式化参数
在slf4j里,我们可以用{}的方式来实现“format”的功能(参数会直接toString替换占位符),像下面这样:
logger.debug(“Logging in user {} with birthday {}”, user.getName(), user.getBirthdayCalendar());
使用String.format的形式格式化参数
log4j2 中除了支持{}
的参数占位符,还支持String.format
的形式:
1 | public static Logger logger = LogManager.getFormatterLogger("Foo"); |
注意,如果想使用
String.format
的形式,需要使用LogManager.getFormatterLogger
而不是LogManager.getLogger
使用logger.printf格式化参数
log4j2 的 Logger接口中,还有一个printf方法,无需创建LogManager.getFormatterLogger,就可以使用String.format的形式
logger.printf(Level.INFO, “Logging in user %1$s with birthday %2$tm %2$te,%2$tY”, user.getName(), user.getBirthdayCalendar());
logger.debug(“Opening connection to {}…”, someDataSource);
“惰性”打日志(lazy logging)
这个功能虽然小,但非常实用。
在某些业务流程里,为了留根或追溯问题,需要完整的打印入参,一般是把入参给用JSON/XML序列化后用debug级别打印:
1 | logger.debug("入参报文:{}",JSON.toJSONString(policyDTO)); |
如果需要追溯问题时,会将系统的日志级别调到debug/trace,这样就可以打印。但是这里有个问题,虽然在info级别下debug不会输出内容,但JSON.toJSONString()
这个序列化的代码一定会执行,严重影响正常流程下的执行效率。
我们期望的结果是info级别下,连序列化都不执行。这里可以通过isDebugEnable来判断当前配置下debug级别是否可以输出:
1 | if(logger.isDebugEnabled()){ |
这样虽然可以避免不必要的序列化,但每个地方都这么写还是有点难受的,一行变成了三行。
log4j2 的 logger 对象,提供了一系列lambda的支持,通过这些接口可以实现“惰性”打日志:
1 | void debug(String message, Supplier<?>... paramSuppliers); |
这种 Supplier + Lambda 的形式,等同于上面的先判断 isDebugEnable 然后打印,三行的代码变成了一行。嗯,真香。
更简化的配置
Log4j2 同时支持XML/JSON/YML/Properties 四种形式的配置文件,不过最主流的还是XML的方式,最直观。
来看一下logback和log4j2的配置文件对比,同样功能的配置下:
logback.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
log4j2.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
在log4j2中,appender的配置从使用 Appender 实现名即标签名的形式,语法上更简洁一些:
1 | <RollingFile name="File"> |
与其他日志抽象/门面适配
log4j2 由于拆分为 API 和 实现两部分,所以可能也需要和其他日志框架进行适配
其他的特点
- 异步队列使用高性能队列 - LMAX Disruptor
- Appender丰富,有JMS/JPA/KAFKA/Http/MONGODB/CouchDB/Socket/Script等各种Appender的支持
- 支持自定义日志级别 ……
基本用法
终于介绍完了Log4j2的强大,现在来介绍下Log4j2的基本使用。
引用log4j2的maven依赖
log4j-api在log4j-core中已经有依赖了,直接依赖core即可
1 | <dependency> |
注意,引用log4j2时,需要注意项目中是否有多套日志框架共存/冲突,需要适配的问题。细节请参考上面的与其他日志抽象/门面适配
配置文件示例
首先是配置文件,默认的配置文件路径为:classpath:log4j2.xml
(推荐使用xml)
1 | <?xml version="1.0" encoding="UTF-8"?> |
XML配置文件语法
1 | <?xml version="1.0" encoding="UTF-8"?>; |
创建Logger
直接使用log4j2的api:
1 | import org.apache.logging.log4j.LogManager; |
如果是配合slf4j使用也是可以的,只需要按照前面说的,提前做好适配,然后使用slf4j的api即可。不过如果是新系统的话,建议直接上log4j2的api吧,可以享受所有log4j2的功能,使用slf4j之类的api时,上面说的参数格式化之类的功能就无法使用了。
全异步配置(重要!!)
推荐配置log4j2 全异步(all async),在你的启动脚本中增加一个系统变量的配置:
1 | -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector |
日志记录的思考
1.用spring aop的方式记录:只需要配置,但是不能区分方法
2.在每个方法种都写上记录日志的语句:针对性强,但是重复性代码多
最后项目里采用:
1.自定义一个注解:
1 | @Retention(RetentionPolicy.RUNTIME) |
2.设置springboot的切面
1 | //切面注解 |
3.在项目里的方法上加(最好加在controller层)
1 | @PrintlnLog(description = "方法说明") |
4.实现效果
注:这种Aop打印日志的方式一般只在测试、开发环境使用,因为会损耗一定的性能而延缓接口返回时间。所以如果需要在返回时间要求很高的生产环境使用的话,最好少在切点执行损耗性能的方法
aop切面的执行正常顺序为:@Before ,@Around,@After,@AfterReturning,@AfterThrowing
从Spring5.2.7开始,在相同@Aspect类中,通知方法将根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@After,@AfterReturning,@AfterThrowing