目录
1.体系结构
现在的应用开发中,对于日志的使用一般都是日志门面(日志接口)+日志实现的方式,如下图所示。产生日志门面的原因,会在2.4.1中详述。
1.1日志门面
JCL:Apache基⾦会所属的项⽬,是⼀套Java⽇志接⼝,之前叫Jakarta Commons Logging,后更 名为Commons Logging,简称JCL。
SLF4J:Simple Logging Facade for Java,缩写Slf4j,是⼀套简易Java⽇志⻔⾯,只提供相关接 ⼝,和其他⽇志⼯具之间需要桥接。
1.2日志实现
JUL:JDK中的⽇志⼯具,也称为jdklog、jdk-logging,⾃Java1.4以来sun的官⽅提供。
Log4j:⾪属于Apache基⾦会的⼀套⽇志框架,现已不再维护。
Log4j2:Log4j的升级版本,与Log4j变化很⼤,不兼容。
Logback:⼀个具体的⽇志实现框架,和Slf4j是同⼀个作者,性能很好。
2.发展过程
以时间线为线索,对日志进行简单的介绍。
2.1诞生之前
在JDK 1.3及以前,Java打⽇志依赖System.out.println(), System.err.println()或者e.printStackTrace(),Debug⽇志被写到STDOUT流,错误⽇志被写到STDERR流。这样打⽇志有⼀个⾮常⼤的缺陷,⾮常机械,⽆法定制,且⽇志粒度不够细分。
System.out.println("123");
System.err.println("456");
2.2 log4j(实现)
Ceki Gulcu于2001年发布了Log4j,并将其捐献给了Apache软件基⾦会,成为Apache 基⾦会的顶级项⽬。后来衍⽣⽀持C, C++, C#, Perl, Python, Ruby等语⾔。
Log4j在设计上⾮常优秀,它定义的Logger、Appender、Level等概念对后续的 Java Log 框架有深远的影响,如今的很多⽇志框架基本沿⽤了这种思想。
但Log4j 的性能是个问题,在Logback 和 Log4j2 出来之后,2015年9⽉,Apache软件基⾦会宣布,Log4j不再维护,建议所有相关项⽬升级到Log4j2。
2.3 jul(实现)
sun公司对于log4j的出现内⼼隐隐表示嫉妒。于是在jdk1.4版本后,开始搞事情,增加了⼀个包为java.util.logging,简称为JUL,⽤以对抗log4j。
但是却给开发造成了麻烦。相互引⽤的项⽬之间可能使⽤了不同的⽇志框架,经常将代码搞得⼀⽚混乱。
JUL功能远不如log4j完善,⾃带的Handlers有限,性能和可⽤性上也⼀般,JUL在Java1.5以后才有所提升。
2.4 jcl(门面)
2.4.1 门面的产生
从上⾯可以看出,JUL的api与log4j是完全不同的(参数只接受string)。例如:日志级别就有所不同。
JUL | LOG4J |
---|---|
severe | fatal |
warning | error |
info(默认级别) | warn |
config | info |
fine | debug(默认级别) |
finer | trace |
finest |
由于⽇志系统互相没有关联,彼此没有约定,不同⼈的代码使⽤不同⽇志,替换和统⼀也就变成了⽐较棘⼿的⼀件事。
例如:假如你的应⽤使⽤log4j,然后项⽬引⽤了⼀个其他团队的库,他们使⽤了JUL,你的应⽤就得使⽤两个⽇志系统了,然后其他团队⼜使⽤了simplelog……这个时候如果要调整⽇志的输出级别,⽤于跟踪某个信息,简直就是⼀场灾难。
解决这一问题的办法就是抽象,抽象出⼀个接⼝层,对每个⽇志实现都适配或者转接,这样这些提供给别⼈的库都直接使⽤抽象层即可,以后调⽤的时候,就调⽤这些接⼝。(⾯向接⼝思想)
自此,日志门面诞生。
2.4.2 jcl
JCL(Jakarta Commons Logging)应运⽽⽣,也就是commons-logging-xx.jar组件。JCL 只提供 log 接⼝,具体的实现则在运⾏时动态寻找。这样⼀来组件开发者只需要针对JCL 接⼝开发,⽽调⽤组件的应⽤程序则可以在运⾏时搭配⾃⼰喜好的⽇志实践⼯具。
在JCL中,如果能找到Log4j 则默认使⽤log4j 实现,如果没有则使⽤JUL(jdk⾃带的)实现,再没有则使⽤JCL内部提供的SimpleLog 实现。
缺点:
JCL的缺点有以下几点,效率低,容易引发混乱,其机制有很大的可能引发内存泄漏。
同时,JCL的日志书写为logger.debug("this is a debug info , message :" + msg);过多的字符串连接,对性能会有影响。
2.5 slf4j(门面)+logback(实现)
针对以上情况,log4j的作者再次出⼿,他觉得JCL不好⽤,⾃⼰⼜写了⼀个新的接⼝api,就是slf4j,并且为了追求更极致的性能,新增了⼀套⽇志的实现,就是logback。
在logback中,logback-core 提供基础抽象,logback-classic 提供⽇志实现,并且直接就是基于Slf4j API。所以slf4j配合logback来完成⽇志时,不需要像其他的⽇志框架⼀样提供适配器。
slf4j本身并没有实际的⽇志输出能⼒,它底层还是需要去调⽤具体的⽇志框架API,也就是它需要跟具体的⽇志框架结合使⽤。由于具体⽇志框架⽐较多,⽽且互相也⼤都不兼容,⽇志⻔⾯接⼝要想实现与任意⽇志框架结合就需要额外对应的桥接器。具体如图所示。
有了新的slf4j后,上⾯的字符串拼接问题,被以下代码所取代,⽽logback也提供了更⾼级的特性,如异步 logger,Filter等。
logger.debug("this is a debug info , message : {}", msg);
2.6 log4j2(门面+日志)
log4j由apache宣布,2015年后,不再维护。推荐⼤家升级到log4j2,虽然log4j2沿袭了log4j的思想,然⽽log4j2和log4j完全是两码事,并不兼容。
log4j2以性能著称,它⽐其前身Log4j 1.x提供了重⼤改进,同时类⽐logback,它提供了Logback中可⽤的许多改进,同时修复了Logback架构中的⼀些固有问题。功能上,它有着和Logback相同的基本操作,同时⼜有⾃⼰独特的部分,⽐如:插件式结构、配置⽂件优化、异步⽇志等。
3.常见日志详解(可直接阅读此处)
3.1 JUL
3.1.1 日志架构的介绍,其余的日志框架与其基本类似
Loggers:记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常是应用程序访问日志系统的入口程序。
Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。
Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。
Level: 每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,可以将Level和Loggers,Appenders做关联以便于我们过滤消息。
Filters: 过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。
总结:用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。
在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日 志内容输出到指定位置(日志文件、控制台等)。
Handler在输出日志时会使用Layout,将输出内容进行排版。
3.1.2 创建对象及输出方式
//对应的包
import java.util.logging.*;
@Test
public void testQuick()throws Exception{
// 1.获取日志记录器对象
//形参:name–记录器的名称。这应该是一个点分隔的名称,通常应该基于子系统的包名或类名,例如java。net或javax.swing
Logger logger = Logger.getLogger("com.mahang.JULTest");
// 2.日志记录输出
logger.info("hello jul");
// 通用方法进行日志记录
logger.log(Level.INFO,"info msg");
// 通过占位符 方式输出变量值
String name = "itcast";
Integer age = 13;
logger.log(Level.INFO,"用户信息:{0},{1}",new Object[]{name,age});
}
3.1.3 日志级别
jul中定义的日志级别共有7种,分别是
SEVERE(最高值) |
---|
WARNING |
INFO(默认级别) |
CONFIG |
FINE |
FINER |
FINEST(最低值) |
此外,还有两个特殊级别
OFF,可以用来关闭日志记录 |
---|
ALL,启用所有消息的日志记录 |
// 日志级别
@Test
public void testLogLevel()throws Exception{
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.JULTest");
// 2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info"); // 默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
//因为默认级别是info,所以打印的日志信息只会显示info 级别以上的
11月 16, 2022 1:56:26 下午 com.itheima.JULTest testLogLevel
严重: severe
11月 16, 2022 1:56:26 下午 com.itheima.JULTest testLogLevel
警告: warning
11月 16, 2022 1:56:26 下午 com.itheima.JULTest testLogLevel
信息: info
3.1.4 自定义日志级别
自定义日志级别的方式,类似于3.1.1的架构图
1.获取日志记录器对象 (获取loLogger)
2.关闭系统默认配置
3.创建ConsoleHandler 控制台输出 (获取Appender)
4.创建简单格式转换对象 ( **Layout)
5.进行关联 ( Filter过滤器,Layout和Appender都需要)
5.1 将Layout关联到Appender
5.2 将Appender关联到Logger
6.配置日志具体级别 (设置过滤级别)
// 自定义日志级别
@Test
public void testLogConfig()throws Exception{
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.JULTest");
// 关闭系统默认配置
logger.setUseParentHandlers(false);
// 自定义配置日志级别
// 创建ConsoleHandler 控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
// 将Layout关联到Appender
consoleHandler.setFormatter(simpleFormatter);
//将Appender关联到Logger
logger.addHandler(consoleHandler);
// 配置日志具体级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info"); // 默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
3.1.5 Logger之间的父子关系
JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层 RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。
// Logger对象父子关系
@Test
public void testLogParent()throws Exception{
//logger2是logger1的爸爸
Logger logger1 = Logger.getLogger("com.itheima");
Logger logger2 = Logger.getLogger("com");
// 测试
System.out.println(logger1.getParent() == logger2);
// 所有日志记录器的顶级父元素 LogManager$RootLogger,name ""
System.out.println("logger2 Parent:"+logger2.getParent() + ",name:" + logger2.getParent().getName());
// 关闭默认配置
logger2.setUseParentHandlers(false);
// 设置logger2(爸爸)日志级别
// 自定义配置日志级别
// 创建ConsolHhandler 控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger2.addHandler(consoleHandler);
// 配置日志具体级别 只设置的logger2(爸爸)的级别,但logger1(儿子)也可以使用
logger2.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
logger1.severe("severe");
logger1.warning("warning");
logger1.info("info");
logger1.config("config");
logger1.fine("fine");
logger1.finer("finer");
logger1.finest("finest");
}
true
logger2 Parent:java.util.logging.LogManager$RootLogger@1ae369b7,name:
11月 16, 2022 2:17:45 下午 com.itheima.JULTest testLogParent
严重: severe
11月 16, 2022 2:17:45 下午 com.itheima.JULTest testLogParent
警告: warning
11月 16, 2022 2:17:45 下午 com.itheima.JULTest testLogParent
信息: info
11月 16, 2022 2:17:45 下午 com.itheima.JULTest testLogParent
配置: config
11月 16, 2022 2:17:45 下午 com.itheima.JULTest testLogParent
详细: fine
11月 16, 2022 2:17:45 下午 com.itheima.JULTest testLogParent
较详细: finer
11月 16, 2022 2:17:45 下午 com.itheima.JULTest testLogParent
非常详细: finest
进程已结束,退出代码0
3.1.6 总结
jdk自带,不用依赖,直接使用,缺点,功能略少,效率略低
3.2 log4j
Log4j是Apache下的一款开源的日志框架,通过在项目中使用 Log4J,我们可以控制日志信息输出到控 制台、文件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以 更灵活的控制日志的输出过程。方便项目的调试。
3.2.1添加依赖,简单实现
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;
@Test
public void testQuick()throws Exception{
// 开启 log4j 内置日志记录 默认关闭
LogLog.setInternalDebugging(true);
// 获取日志记录器对象
Logger logger = Logger.getLogger(Log4jTest.class);
// Logger logger1=Logger.getLogger("com.mahang.Log4jTest"); 支持两种方式
// 日志记录输出
logger.info("hello log4j");
// 日志级别
logger.fatal("fatal"); //严重错误,一般会造成系统崩溃并终止运行
logger.error("error"); // 错误信息,不会影响系统运行
logger.warn("warn"); // 警告信息,可能会发生问题
logger.info("info"); // 运行信息,数据连接、网络连接、IO 操作等等
logger.debug("debug"); // 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
logger.trace("trace"); // 追踪信息,记录程序所有的流程信息
}
3.2.2 日志级别
日志级别从高到低分 为:
fatal 指出每个严重的错误事件将会导致应用程序的退出。
error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
warn 表明会出现潜在的错误情形。
info 一般和在粗粒度级别上,强调应用程序的运行全程。
debug 一般用于细粒度级别上,对调试应用程序非常有帮助。
trace 是程序追踪,可以用于输出程序运行中的变量,显示执行的流程。
还有两个特殊的级别:
OFF,可用来关闭日志记录。 ALL,启用所有消息的日志记录。
注:一般只使用4个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUG
3.2.3 Log4j组件(相关概念)
Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。(与上面的Jul基本相似,准确的说,是JUL与Log4j相似)
其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件 等);Layout 控制日志信息的输出格式。
3.2.3.1 Loggers
日志记录器,负责收集处理日志记录,实例的命名就是类“XX”的full quailied name(类的全限定名), Logger的名字大小写敏感,其命名有继承机制(上面提到的父子关系):
例如:name为org.apache.commons的logger会继承 name为org.apache的logger。
Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接 或者间接地继承自root。rootlogger可以用Logger.getRootLogger()方法获取。
但是,自log4j 1.2版以来, Logger 类已经取代了 Category 类。对于熟悉早期版本的log4j的人来说, Logger 类可以被视为 Category 类的别名。
3.2.3.2 Appenders
Appender 用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地 有以下几种:
输出端类型 | 作用 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件中 |
DailyRollingFileAppender | 将日志输出到一个日志文件,并且每天输出到一个新的文件 |
RollingFileAppender | 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大 小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件 |
JDBCAppender | 把日志信息保存到数据库中 |
3.2.3.3 Layouts
布局器 Layouts用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。Log4j常用 的Layouts:
格式化器类型 | |
---|---|
HTMLLayout | 格式化日志输出为HTML表格形式 |
SimpleLayout | 简单的日志输出格式化,打印的日志格式为(info - message) |
PatternLayout | 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式 |
3.2.4 Layout的格式
在 log4j.properties 配置文件中,我们定义了日志输出级别与输出端,在输出端中分别配置日志的输出 格式。
# %m 输出代码中指定的日志信息
# %p 输出优先级,及 DEBUG、INFO 等
# %n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
# %r 输出自应用启动到输出该 log 信息耗费的毫秒数
# %c 输出打印语句所属的类的全名
# %t 输出产生该日志的线程全名
# %d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
# %l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
# %F 输出日志消息产生时所在的文件名称
# %L 输出代码中的行号
# %% 输出一个 "%" 字符
# 可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:
# %5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
# %-5c 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
# %.5c 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
# %20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉
例如:
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式 layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.console.layout.conversionPattern = [%10p]%r ==== %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
效果:
[ INFO]0 ==== com.itheima.Log4jTest.testQuick(Log4jTest.java:22) 2022-11-16 15:02:46.768 hello log4j
[ INFO]0 ==== com.itheima.Log4jTest.testQuick(Log4jTest.java:22) 2022-11-16 15:02:46.768 hello log4j
[ FATAL]4 ==== com.itheima.Log4jTest.testQuick(Log4jTest.java:28) 2022-11-16 15:02:46.772 fatal
[ FATAL]4 ==== com.itheima.Log4jTest.testQuick(Log4jTest.java:28) 2022-11-16 15:02:46.772 fatal
3.2.5 Appender的输出
注:log4j.appender.后面的名字是自定义的,随便起,见名知意即可
每一个Appender都是独立的,可以共同使用,只需在log4j.rootLogger上添加即可。
控制台
# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别=trace,使用的 apeender 为console(apeender可以添加多个)
log4j.rootLogger = trace,console,file
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式 layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.console.layout.conversionPattern = [%10p]%r ==== %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
文件
# 日志文件输出的 appender 对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式 layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.file.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.file.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8
按照时间规则拆分的 appender 对象
# 按照时间规则拆分的 appender 对象
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式 layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.dailyFile.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.dailyFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.dailyFile.encoding = UTF-8
# 指定日期拆分规则
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss
按照文件大小拆分的 appender 对象
# 按照文件大小拆分的 appender 对象
# 日志文件输出的 appender 对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式 layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.rollingFile.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.rollingFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日志文件内容的大小
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex = 10
数据库
#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=root
#插入数据
log4j.appender.logDB.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
在sql中建表
xxxxxxxxxx CREATE TABLE `log` (`log_id` int(11) NOT NULL AUTO_INCREMENT,`project_name` varchar(255) DEFAULT NULL COMMENT '目项名',`create_date` 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`));
3.2.6 自定义Logger
# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别=trace,使用的 apeender 为console(apeender可以添加多个)
log4j.rootLogger = trace,console
# 自定义 logger 对象设置 同样,log4j.logger.后的名字是自己写的
log4j.logger.com.itheima = info,console
#不指定apeender,使用父类的apeender
log4j.logger.org.apache = error
// 获取日志记录器对象 Log4jTest.class是com.itheima的子包
Logger logger = Logger.getLogger(Log4jTest.class);
3.3 jcl
全称为Jakarta Commons Logging,是Apache提供的一个通用日志API。
它是为 "所有的Java日志实现"(因为设计问题,只支持Log4j和jul)提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常常弱 (SimpleLog)。所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具: Log4j, Jdk 自带的日志(JUL)
JCL 有两个基本的抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)。
顺序:log4j,jul,simpleLog
添加依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
简单实现
//导入的包
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class JCLTest {
@Test
public void testQuick()throws Exception{
// 获取 log日志记录器对象
Log log = LogFactory.getLog(JCLTest.class);
// 日志记录输出
log.info("hello jcl");
}
}
设计的不太行,大家基本都在用SLF4J
3.4 SLF4J(推荐)
简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范 的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。 当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架 会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。
SLF4J是目前市面上最流行的日志门面。现在的项目中,基本上都是使用SLF4J作为我们的日志系统。
SLF4J日志门面主要提供两大功能:日志框架的绑定,日志框架的桥接
3.4.1 添加依赖,简单实现
注:添加slf4j 日志门面(slf4j-api.jar),后,需要添加具体的实现,如果不添加,将会无操作,如下图
添加依赖
<!-- slf4j 日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!-- slf4j 内置的简单实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
简单实现
import org.junit.Test;
//导入的包
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jTest {
public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
// 快速入门
@Test
public void test01()throws Exception{
// 日志输出
LOGGER.error("error");
LOGGER.warn("wring");
LOGGER.info("info"); // 默认级别
LOGGER.debug("debug");
LOGGER.trace("trace");
// 使用占位符输出日志信息
// 使用占位符输出,提高性能
String name = "itheima";
Integer age = 14;
LOGGER.info("用户:{},{}",name,age);
// 将系统的异常信息输出
try {
int i = 1/0;
} catch (Exception e) {
// e.printStackTrace();
LOGGER.error("出现异常:",e);
}
}
}
为什么要使用SLF4J作为日志门面
-
使用SLF4J框架,可以在部署时迁移到所需的日志记录框架。
-
SLF4J提供了对所有流行的日志框架的绑定,例如log4j,JUL,Simple logging和NOP。因此可以 在部署时切换到任何这些流行的框架。
-
无论使用哪种绑定,SLF4J都支持参数化日志记录消息。由于SLF4J将应用程序和日志记录框架分离, 因此可以轻松编写独立于日志记录框架的应用程序。而无需担心用于编写应用程序的日志记录框架。
-
SLF4J提供了一个简单的Java工具,称为迁移器。使用此工具,可以迁移现有项目,这些项目使用日志 框架(如Jakarta Commons Logging(JCL)或log4j或Java.util.logging(JUL))到SLF4J。
3.4.2 绑定日志的实现(Binding)
SLF4J支持各种日志框架。SLF4J发行版附带了几个称为“SLF4J绑定”的jar文件,每个绑定对应 一个受支持的框架。
使用slf4j的日志绑定流程:
-
添加slf4j-api的依赖
-
使用slf4j的API在项目中进行统一的日志记录
-
. 绑定具体的日志实现框架
-
绑定已经实现了slf4j的日志框架,直接添加对应依赖
-
绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
-
-
slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)
通过maven引入常见的日志实现框架:
绑定分为两种情况,比slf4j出现的晚的,默认遵循slf4j,有logback、slf4j-simple。nop 日志开关
<!-- slf4j 日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!-- <!– slf4j 内置的简单实现–>-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-simple</artifactId>-->
<!-- <version>1.7.21</version>-->
<!-- </dependency>-->
<!-- logback 日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--nop 日志开关 添加后,日志功能将会关闭-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
</dependency>
比slf4j出现的早的,是不遵循slf4,有log4、jul。需要添加适配器
<!-- slf4j 日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--绑定 log4j 日志实现,需要导入适配器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--绑定 jul 日志实现,需要导入适配器 日志实现就不用导入了,jul是jdk内置的
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
-->
要切换日志框架,只需替换类路径上的slf4j绑定。例如,要从java.util.logging切换到log4j,只需将 slf4j-jdk14-1.7.27.jar替换为slf4j-log4j12-1.7.27.jar即可。
3.4.3 桥接旧的日志框架(Bridging)
桥接用来解决:项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现,具体方式
1.先去除之前老的日志框架的依赖
2.添加SLF4J提供的桥接组件
3.为项目添加SLF4J的具体实现
可以在不改动源代码的基础上,完成日志的升级
桥接原理:
注释掉原有的日志实现,添加桥接器,由桥接器去调用Slf4j门面,门面再去调用具体的实现
迁移的方式:如果我们要使用SLF4J的桥接器,替换原有的日志框架,那么我们需要做的第一件事情,就是删除掉原
有项目中的日志框架的依赖。然后替换成SLF4J提供的桥接器。
<!-- 1.去除老的日志实现 log4j 日志实现-->
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<!-- slf4j 日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!-- 2.配置 log4j 的桥接器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!-- 3. logback 日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
注意问题:
log4j-over-slf4j.jar和slf4j-log4j12.jar不能同时出现,会导致无限循环问题,如下图所示,
APP原本调用LOG4J,现在调用桥接器,桥接器去调用日志门面,日志门面去调用适配器,适配器要去适配log4j,又进入了桥接器,导致了死循环。
3.5 logback(推荐)
Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。
Logback主要分为三个模块:
logback-core:其它两个模块的基础模块
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能
后续的日志代码都是通过SLF4J日志门面搭建日志系统,所以在代码是没有区别,主要是通过修改配置 文件和pom.xml依赖
3.5.1 添加依赖及简单实现
依赖
<!--slf4j 日志门面-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--logback 日志实现 classic包中包含了core-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
实现
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackTest {
public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
// 快速入门
@Test
public void testQuick()throws Exception{
for (int i = 0; i < 10000; i++) {
// 日志输出
LOGGER.error("error");
LOGGER.warn("wring");
LOGGER.info("info");
LOGGER.debug("debug");// 默认级别
LOGGER.trace("trace");
}
}
}
3.5.2 logback配置
logback会依次读取以下类型配置文件:
logback.groovy
logback-test.xml
logback.xml
如果均不存在会采用默认配置
logback组件之间的关系(与前面所介绍的大同小异):
Logger:日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也 可以定义日志类型、级别。
Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。
Layout:负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封 装在encoder中。
配置信息
以====为分隔符,便于观察
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--==========================property集中管理==================================================-->
<!--
配置集中管理属性
我们可以直接改属性的 value 值
格式:${name}
-->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property>
<!--
日志输出格式:
%-5level 级别从左显示5个字符宽度
%d{yyyy-MM-dd HH:mm:ss.SSS}日期
%c类的完整名称
%M为method
%L为行号
%thread线程名称
%m或者%msg为日志消息
%n换行
-->
<!--定义日志文件保存路径属性-->
<property name="log_dir" value="/logs"></property>
<!--========================各种appender=========================================-->
<!--
Appender: 设置日志信息的去向,常用的有以下几个
ch.qos.logback.core.ConsoleAppender (控制台)
ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺
寸的时候产生一个新文件)
ch.qos.logback.core.FileAppender (文件)
-->
<!--控制台日志输出的 appender-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--控制输出流对象 默认 System.out 改为 System.err 可以显示为红色-->
<target>System.err</target>
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- ============================================================================-->
<!--日志文件输出的 appender-->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!--日志文件保存路径-->
<file>${log_dir}/logback.log</file>
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- =========================================================================-->
<!--html 格式日志文件输出 appender-->
<appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
<!--日志文件保存路径-->
<file>${log_dir}/logback.html</file>
<!--html 消息格式配置-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern>
</layout>
</encoder>
</appender>
<!--========================================================================-->
<!--日志拆分和归档压缩的 appender 对象-->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件保存路径-->
<file>${log_dir}/roll_logback.log</file>
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!--指定拆分规则-->
<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-->
<appender-ref ref="rollFile"/>
</appender>
<!--==========================logger 配置 想让那个appender生效,添加进logger即可==============================================-->
<!--root logger 配置-->
<!--
<logger>元素,但是它是根logger。默认debug
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL和 OFF,
<root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。
-->
<root level="ALL">
<appender-ref ref="console"/>
<appender-ref ref="async"/>
</root>
<!--自定义 looger 对象
additivity="false" 自定义 logger 对象是否继承 rootLogger
-->
<!--
用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
<loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和OFF,
如果未设置此属性,那么当前logger将会继承上级的级别。
additivity:是否向上级loger传递打印信息。默认是true。(对象是否继承 rootLogger)
<logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
-->
<logger name="com.itheima" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
</configuration>
官方提供的log4j.properties转换成logback.xml
https://logback.qos.ch/translator/
注:第一次使用,需要进行hithub身份认证
3.5.3logback-access(用的不多)
logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使 用logback-access模块来替换tomcat的访问日志。
具体步骤:
1.将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下
2.修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:
<Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
-
logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml, logback-access.xml文件如下:
<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>
4.官方提供了详细的配置: https://logback.qos.ch/access.html#configuration
3.6 log4j2(推荐)
Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带 来了一些重大的提升,主要有:
异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异 常处理机制。
性能提升, log4j2相较于log4j 和logback都具有很明显的性能提升,后面会有官方测试的数据。
自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产 上可以动态的修改日志的级别而不需要重启应用。
无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集 导致的jvm gc。
官网:
3.6.1 log4j2 作为门面
目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,因为Log4j2的日志实现功能非常强 大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。
添加依赖
<!--log4j2日志门面-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<!--log4j2 日志实现-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
简单实现
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Log4j2Test {
// 定义日志记录器对象 注意,此处用的是LogManager.getLogger
public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
// 快速入门
@Test
public void testQuick()throws Exception{
// 日志消息输出
LOGGER.fatal("fatal");
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("inf");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
3.6.2 slf4j作为门面,log4j作为实现(推荐)
依赖
执行流程:slf4j日志门面--->log4j-slf4j适配器--->log4j2日志门面--->log4j2 日志实现
<!--使用slf4j 作为日志门面-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--使用 log4j2 的适配器进行绑定-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.9.1</version>
</dependency>
<!--log4j2日志门面-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<!--log4j2 日志实现-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
配置信息
<?xml version="1.0" encoding="UTF-8"?>
<!--
status="warn" 日志框架本身的输出日志级别
monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒 实现热更新
-->
<Configuration status="debug" monitorInterval="5">
<!--
集中配置属性进行管理
使用时通过:${name}
-->
<properties>
<property name="LOG_HOME">/logs</property>
</properties>
<!--日志处理-->
<Appenders>
<!--控制台输出 appender-->
<Console name="Console" target="SYSTEM_ERR"><!--SYSTEM_ERR输出类型,颜色不一样罢了,还有 SYSTEM_OUT-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
</Console>
<!--日志文件输出 appender-->
<File name="file" fileName="${LOG_HOME}/myfile.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</File>
<!--使用随机读写流的日志文件输出 appender,性能提高-->
<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</RandomAccessFile>
<!--按照一定规则拆分的日志文件的 appender-->
<RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
filePattern="/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log"><!--filePattern拆分后的文件命名 ;以天为单位($${date:yyyy-MM-dd}),作为文件夹,以分钟为文件生成日志(myrollog-%d{yyyy-MM-dd-HH-mm}),到达指定大小后,在进行拆分,排序(-%i)-->
<!--日志级别过滤器 放行 拦截-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<!--日志消息格式-->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
<!-- 设置具体差分规则-->
<Policies>
<!--在系统启动时,触发拆分规则,生产一个新的日志文件-->
<OnStartupTriggeringPolicy />
<!--按照文件大小拆分,10MB -->
<SizeBasedTriggeringPolicy size="10 MB" />
<!--按照时间节点拆分,规则根据filePattern定义的-->
<TimeBasedTriggeringPolicy />
</Policies>
<!--在同一个目录下,文件的个数限定为 30 个,超过进行覆盖-->
<DefaultRolloverStrategy max="30" />
</RollingFile>
</Appenders>
<!--logger 定义-->
<Loggers>
<!--使用 rootLogger 配置 日志级别 level="trace"-->
<Root level="trace">
<!--指定日志使用的处理器-->
<AppenderRef ref="Console" />
<!--使用异步 appender-->
<AppenderRef ref="Async" />
</Root>
</Loggers>
</Configuration>
3.6.3 Log4j2异步日志
异步日志
log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益
什么是同步日志
什么是异步日志
Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender(异步Appender),一个是通过AsyncLogger(异步Logger),分别对应 前面我们说的Appender组件和Logger组件。
Log4j2快,主要快到了异步Logger,异步Appender与logback性能差不多
注意:配置异步日志需要添加依赖
<!--异步日志依赖-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
AsyncAppender方式(不推荐)
<Appenders>
<Async name="Async">
<AppenderRef ref="file"/>
</Async>
</Appenders>
与logback的异步方式基本相似,在<AppenderRef ref="file"/>标签中添加 appender,随后将自己添加到logger中。
AsyncLogger方式(重点)
AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的 更快。你可以有两种选择:全局异步和混合异步。
全局异步
所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个 log4j2.component.properties 配置
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合异步
可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加 灵活。
<!--logger 定义-->
<Loggers>
<!--自定义异步 logger 对象
includeLocation="false" 关闭日志记录的行号信息 行号信息开启后,对性能有影响
additivity="false" 不再继承 rootlogger 对象
-->
<AsyncLogger name="com.itheima" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
</AsyncLogger>
<!--使用 rootLogger 配置 日志级别 level="trace"-->
<Root level="trace">
<!--指定日志使用的处理器-->
<AppenderRef ref="Console" />
<!--使用异步 appender-->
<AppenderRef ref="Async" />
</Root>
</Loggers>
如上配置: com.itheima 日志是异步的,root日志是同步的。
注意的问题
如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。性能会和 AsyncAppender一致,降至最低。(木桶效应)
设置includeLocation=false ,否则打印位置信息会急剧降低异步日志的性能,比同步日志还要 慢。
3.6.4 无垃圾记录
垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停。
许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串, 字符数组,字节数组等。这会对垃圾收集器造成压力并增加GC暂停发生的频率。
从版本2.6开始,默认情况下Log4j以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临 时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal字段。
Log4j 2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换 为字节时重用缓冲区来实现。
3.7 SpringBoot中的日志使用(推荐)
springboot 默认就是使用SLF4J作为日志门面,logback作为日志实现来记录日志。
使用springboot中的日志要导入依赖
<dependency>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
不过,springboot中的日志包含在spring-boot-starter-web中,所以不需要重复导入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
依赖关系图:
总结
springboot 底层默认使用了SLF4J作为日志门面,使用logback作为日志实现。
使用别的日志,最终也会通过slf4j调用logback(默认情况,更改默认的方法,在下文)
简单实现
注,更推荐使用@Slf4j来代替 声明日志记录器对象的过程
package com.itheima.springboot_log;
import org.apache.logging.log4j.LogManager;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootLogApplicationTests {
// 声明日志记录器对象
public static final Logger LOGGER = LoggerFactory.getLogger(SpringbootLogApplicationTests.class);
@Test
public void contextLoads() {
// 打印日志信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info"); // 默认日志级别
LOGGER.debug("debug");
LOGGER.trace("trace");
// 使用 lo4j2 使用桥接器切换为 slf4j 门面和 logback 日志实现
org.apache.logging.log4j.Logger logger = LogManager.getLogger(SpringbootLogApplicationTests.class);
logger.info("log4j2 info");
}
}
指定配置文件
给类路径下放上每个日志框架自己的配置文件;SpringBoot就不使用默认配置的了。
日志框架 | 配置文件 |
---|---|
logback | logback-spring.xml;logback.xml |
log4j2 | log4j2-spring.xml;log42.xml |
jul | logging.properties |
logback-spring.xml与logback.xml的区别(其他的也一样)
logback-spring.xm:默认支持被springboot框架解析,可以更方便的自定义一些内容,例如消息格式
因为2者的加载顺序是不一样的。logback.xml--->application.properties--->logback-spring.xml.
logback.xml加载早于application.properties,所以如果在logback.xml使用了变量时,而恰好这个变量是写在application.properties时,那么就会获取不到,只要改成logback-spring.xml就可以解决。
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<springProfile name="dev">
<pattern>${pattern}</pattern>
</springProfile>
<springProfile name="pro">
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] xxxxxxxx %m %n</pattern>
</springProfile>
</encoder>
# 指定项目使用的具体环境
spring.profiles.active=pro
将日志切换为log4j2
总共分为两步:
1.排除掉默认的启动器,移除logback
2.,添加log4j的启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--1.排除 logback 日志实现-->
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--2.使用 log4j2 的日志启动器 日志门面slf4j,日志实现log4j-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
log4j2 的日志启动器中包含了 日志门面slf4j
4.日志建议
门面约束
使⽤⻔⾯,⽽不是具体实现
使⽤ 日志门面 可以⽅便的切换具体的⽇志实现。⽽且,如果依赖多个项⽬,使⽤了不同的日志门面,还可以⽅便的通过 Adapter 转接到同⼀个实现上。如果依赖项⽬直接使⽤了多个不同的⽇志 实现,会⾮常糟糕。
⽇志⻔⾯,⼀般现在推荐使⽤ Log4j-API 或者 SLF4j(主要),不推荐继续使⽤ JCL。
单⼀原则
只添加⼀个⽇志实现
项⽬中应该只使⽤⼀个具体的 日志实现,如果在依赖的项⽬中,使⽤的 日志门面 不⽀持当前 ⽇志实现 ,就添加合适的桥接器。
jul性能⼀般,log4j性能也有问题⽽且不再维护,建议使⽤ Logback 或者Log4j2。
依赖约束
⽇志实现的坐标 应该设置为optional并使⽤runtime scope
例如
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
设为optional,依赖不会传递,这样如果是个lib项⽬,然后别的项⽬使⽤了这个lib,不会被引 ⼊不想要的日志实现依赖;
Scope设置为runtime,是为了防⽌开发⼈员在项⽬中直接使⽤ 日志实现 中的类,强制 约束开发⼈员使⽤ 门面接⼝。
避免传递
尽量⽤exclusion排除依赖的第三⽅库中的⽇志坐标
第三⽅库的开发者却未必会把具体的⽇志实现或者桥接器的依赖设置为optional, 然后你的项⽬就会被迫传递引⼊这些依赖,⽽这些⽇志实现未必是你想要的。
⽐如他依赖了Log4j,你 想使⽤Logback,这时就很尴尬。另外,如果不同的第三⽅依赖使⽤了不同的桥接器和Log实现,极有 可能会形成环。
这种情况下,推荐的处理⽅法,是使⽤exclude来排除所有的这些Log实现和桥接器的依赖,只保留 第三⽅库⾥⾯对Log Facade的依赖。
实例:依赖jstorm会引⼊Logback和log4j-over-slf4j,如果你在⾃⼰的项⽬中使⽤Log4j或其他Log 实现的话,就需要加上exclusion:
<dependency>
<groupId>com.alibaba.jstorm</groupId>
<artifactId>jstorm-core</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
注意写法
避免为不会输出的log买单
尽量使用
logger.debug("this is json msg: {}", message);
而非
logger.debug("this is debug: " + message);
的形式,减少开销
减少分析
输出的⽇志中尽量不要使⽤⾏号,函数名等信息
原因是,为了获取语句所在的函数名,或者⾏号,log库的实现都是获取当前的stacktrace,然后分析取出这些信息,⽽获取stacktrace的代价是很昂贵的。如果有很多的⽇志输出,就会占⽤⼤量的 CPU。在没有特殊需要的情况下,建议不要在⽇志中输出这些这些字段。
5 性能比较
性能比较的信息均来自log4j2官网Log4j – Performance
结论
推荐使用slf4j+log4j2
异步日志记录-峰值吞吐量比较
异步日志记录对于处理突发事件非常有用应用程序线程完成最小量的工作以捕获日志事件中的所有所需信息,然后将该日志事件放到队列中以供后台线程稍后处理。只要队列的大小足够大,应用程序线程就应该能够在日志记录调用上花费很少的时间,并很快返回到业务逻辑。
Log4j2随着线程数的增加而扩展得更好:具有更多线程的应用程序可以记录更多的日志。其他日志库受到锁争用的影响,当更多线程在日志中时,总吞吐量保持不变或下降。这意味着使用其他日志库,每个单独的线程将能够记录更少的日志。
请记住,这peak 吞吐量:Log4j2的异步记录器( lock-free data structure无锁数据结构)在一定程度上给予了更好的吞吐量,但是一旦队列满了,附加器线程就需要等待,直到队列中的槽变得可用,并且吞吐量将下降到最好的基础附加器的最大持续吞吐量。
异步日志记录响应时间
该图形显示了响应时间延迟行为在每秒64,000条消息的中等总工作负载下,4个线程并发记录。在此负载和此硬件/OS/JVM配置下,锁争用和上下文切换所起的作用较小而暂停主要是由次要的垃圾收集引起的。垃圾收集暂停的持续时间和频率可以有很大的不同:当测试Log4j 1.2.17时异步附加器 ,当Log4j 2 Async Appender测试只看到这并不一定意味着一个比另一个更好。
通常,无垃圾的异步日志记录器具有最佳的响应时间行为 在我们测试的所有配置中。
异步日志记录参数化消息
与其他日志框架相比,Log4j2的异步记录器表现良好,但请注意,消息格式化成本随着参数数量的增加而急剧增加。在这方面,Log4j 2仍有工作要做:我们希望保持成本更稳定。
JUL(java.util.logging)没有内置的异步处理程序。 MemoryHandler](MemoryHandler (Java Platform SE 8 )) ( 内存行程程式 )是最接近的,因此我们将其包括在此处。 内存处理程序not 做一个安全的快照,当前参数状态(它只保留对原始参数对象的引用),因此它在单线程时速度非常快。 但是,当更多的应用程序线程并发地进行日志记录时,锁争用的成本将超过这一收益。
使用调用方位置信息的异步日志记录
下图显示了在记录时捕获调用者位置信息对性能的影响 。我们的测试显示,捕获调用方位置对所有日志库都有类似的影响,并将异步日志记录速度降低30-100倍。
同步文件日志记录-持续吞吐量比较
当更多线程同时进行日志记录时,Log4j2的持续吞吐量略有下降,但它的细粒度锁定带来了回报,吞吐量保持相对较高。在多线程应用程序中,其他日志框架的吞吐量显著下降:Log4j 1.2的单线程容量为其1/4,Logback的单线程能力为其1/10,而随着线程的增加,JUL的单线程吞吐量从1/4稳步下降到1/10。
同步文件日志记录-响应时间比较
同步文件日志记录的响应时间随工作负载和 线程数。以下是每秒32,000个事件的工作负载的示例, 其中2个线程每个每秒记录16,000个事件。
使用哪个appender
MemoryMappedFile appender(内存映射文件追加程序)或RandomAccessFile appender(随机访问文件追加程序)的性能更好。