什么是日志
日志的作用就是在测试、生产环境没有 Debug 调试工具时开发、测试人员定位问题的手段。日志打得好,就能根据日志的轨迹快速定位并解决线上问题,反之,日志输出不好不能定位到问题不说反而会影响系统的性能。 优秀的项目都是能根据日志定位问题的,而不是在线调试,或者半天找不到有用的日志而抓狂…
发展史
log4j: 1996年早期,欧洲安全电子市场项目组E.U.SEMPER需要一个(Tracing API)来准中程序。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j,并被转换成C/C++/C#/Perl/Python等不同的语言版本,后来Log4j成为Apache基金会项目中的一员,期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议sun引入Log4j到java的标准库中,但Sun拒绝了。
JUL: 原来Sun也有自己的盘算,不就一个日志嘛,我自己也搞一个,2002年2月JDK1.4发布,Sun推出了自己的日志标准库JUL(Java Util Logging),其实是照着Log4j抄的,而且还没抄好,还是在JDK1.5以后性能和可用性才有所提升
JCL: 同年,Apache推出了Jakarta Commons Logging,Jakarta在这里指的是一个组织,而不是印度的首都雅加达,Jakarta,一个早期的Apache开源项目,用于管理各个Java子项目,诸如Tomcat, Ant, Maven, Struts, JMeter, Velocity, JMeter, Commons等。2011年12月,在所有子项目都被迁移为独立项目后,Jakarta名称就不再使用了。JCL诞生的初衷是因为Java自身的一些包用了JUL,而Log4j用户使用的有很多,那么JCL就是提供一套API来实现不同Logger之间的切换。JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是Log4j,也可以是Java Util Logging。最后更新时间是2014.7.9
SLF4J: 2006年,Ceki Gülcü(log4j1.x的作者)不适应Apache的工作方式,离开了Apache。然后先后创建了SLF4J(日志门面接口,类似于Commons Logging)和Logback(SLF4J的实现)两个项目,并回瑞典创建了QOS公司,SLF4J(Simple Logging Facade For Java)简单日志门面,和JCL功能类似,但JCL有一个致命的缺点就是算法复杂,出现问题难以排除,而SLF4J的诞生就是为了解决JCL的缺点。QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。
Logback: Ceki巨佬觉得市场上的日志标准库都是间接实现Slf4j接口,也就是说每次都需要配合桥接包,因此在2006年,Ceki巨佬基于Slf4j接口撸出了Logback日志标准库,做为Slf4j接口的默认实现,Logback也十分给力,在功能完整度和性能上超越了所有已有的日志标准库。
Log4j2: Apache在2012年重写了log4j1.x,并成立了log4j2项目,2014年发布了log4j2的通用版本,Log4j2有着和Logback相同的功能,但又有自己单用的功能,比如:插件式结构、配置文件优化、异步日志等。Log4j2是Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些固有问题。
常用日志框架
System.out和System.err
2001年以前,Java是没有日志库的,打印日志全凭System.out和System.err,我人都傻了,十分离谱
缺点下:
- 产生大量的IO操作 同时在生产环境中 无法合理的控制是否需要输出
- 输出的内容不能保存到文件
- 只打印在控制台,打印完就过去了,也就是说除非你一直盯着程序跑
- 无法定制化,且日志粒度不够细
Log4j
背景
此时名为Ceki的巨佬站出来,说你这个不好用,我这个好用,接着在2001年掏出了Log4j,用起来也确实比System系列香,Log4j一度成为业内日志标准
介绍
Log4j是Apache下的一款开源的日志框架,通过在项目中使用 Log4J,我们可以控制日志信息输出到控 制台、文件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以 更灵活的控制日志的输出过程。方便项目的调试
官方网站: logging.apache.org/log4j/1.2/
使用
入门实验
- 添加依赖
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.代码测试
public class Log4jTest {
@Test
public void testQuick() throws Exception { // 初始化系统配置,不需要配置文件数据
// 初始化系统配置,不需要配置文件数等
BasicConfigurator.configure();
// 创建日志记录器对象
Logger logger = Logger.getLogger(Log4jTest.class);
// 日志记录输出
logger.info("hello log4j");
// 日志级别
logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
logger.error("error"); // 错误信息,但不会影响系统运行
logger.warn("warn"); // 警告信息,可能会发生问题
logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
logger.debug("debug");// 调试信息,一般在开发阶段使用,记录程序的变量、参
logger.trace("trace");// 追踪信息,记录程序的所有流程信息
}
}
日志级别
- 每个Logger都被了一个日志级别(log level),用来控制日志信息的输出。日志级别从高到低分 为:
fatal 指出每个严重的错误事件将会导致应用程序的退出
error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
warn 表明会出现潜在的错误情形。
info 一般和在粗粒度级别上,强调应用程序的运行全程。
debug 一般用于细粒度级别上,对调试应用程序非常有帮助。
trace 是程序追踪,可以用于输出程序运行中的变量,显示执行的流程。
- 还有两个特殊的级别:
OFF,可用来关闭日志记录。
ALL,启用所有消息的日志记录。
注:一般只使用4个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUG
Log4j组件
Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件 等);Layout 控制日志信息的输出格式
Loggers (日志记录器)
日志记录器,负责收集处理日志记录,实例的命名就是类“XX”的full quailied name(类的全限定名), Logger的名字大小写敏感,其命名有继承机制:例如:name为org.apache.commons的logger会继承 name为org.apache的logger
Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接 或者间接地继承自root。root logger可以用Logger.getRootLogger()方法获取
配置根Logger,其语法为:
log4j.rootLogger = [ level ] , appenderName, appenderName, ...
例子:
log4j.rootLogger=info, stdout, log, errorlog
log4j.Logger=search,Test
Appenders(输出端)
Appender 用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地 有以下几种:
输出端类型 | 作用 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件中 |
DailyRollingFileAppender | 将日志输出到一个日志文件,并且每天输出到一个新的文件 |
RollingFileAppender | 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件 |
JDBCAppender | 把日志信息保存到数据库中 |
配置日志信息输出目的地Appender,其语法为:
log4j.appender.appenderName = fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1 = value1
log4j.appender.appenderName.option = valueN
例子:
log4j.rootLogger=info, stdout, log, errorlog
log4j.Logger=search,Test
###Console ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d{ABSOLUTE} [ %t ] [ %p ]:%L - %m%n
### Log ###
log4j.appender.log = org.apache.log4j.DailyRollingFileAppender
log4j.appender.log.File = log/log.log
log4j.appender.log.Append = true
log4j.appender.log.Threshold = INFO
log4j.appender.log.DatePattern='.'yyyy-MM-dd
log4j.appender.log.layout = org.apache.log4j.PatternLayout
log4j.appender.log.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t ] %m%n
详细配置可参考:wiki.jikexueyuan.com/project/log…
Layout(日志格式化器)
布局器 Layouts用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。Log4j常用的Layouts
格式化器类型 | 作用 |
---|---|
HTMLLayout | 格式化日志输出为HTML表格形式 |
SimpleLayout | 简单的日志输出格式化,打印的日志格式为(info - message) |
PatternLayout | 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式 |
在 log4j.properties 配置文件中,我们定义了日志输出级别与输出端,在输出端中分别配置日志的输出 格式:
* log4j 采用类似 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下:
%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字符,就从左边交远销出的字符截掉
自定义Logger
# RootLogger配置
log4j.rootLogger = trace,console
# 自定义Logger com.lm 包下的logger类型
log4j.logger.com.lm = info,file
log4j.logger.org.lm = error
@Test
public void testCustomLogger() throws Exception {
// 自定义 com.lm
Logger logger1 = Logger.getLogger(Log4jTest.class);
logger1.info("info");
// 自定义 org.lm
Logger logger2 = Logger.getLogger(Logger.class);
}
JUL(java util logging)
背景
JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用
介绍
- 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,将输出内容进 行排版
使用
入门实验
不需要特殊引入什么jar,jdk 原生支持
public class JULTest {
//快速入门
@Test
public void testQuick(){
//1.获取日志记录器对象
Logger logger = Logger.getLogger("com.lm.JULTest");
//2.日志记录输出
logger.info("hello jul");
//通用方法进行日志记录
logger.log(Level.INFO,"info msg");
//通过占位符方式输出变量值
String name = "lm";
Integer age = 13;
logger.log(Level.INFO,"用户信息:{0},{1}",new Object[]{name,age});
}
}
日志级别
java.util.logging.Level中定义了日志的级别:
SEVERE(最高值)
WARNING
INFO (默认级别)
CONFIG
FINE
FINER
FINEST(最低值)
还有两个特殊的级别:
OFF,可用来关闭日志记录。
ALL,启用所有消息的日志记录。
虽然我们测试了7个日志级别但是默认只实现info以上的级别
@Test
public void testLogLevel(){
//1.获取日志对象
Logger logger = Logger.getLogger("com.com.lm.JULTest");
//2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
结果:
severe
warning
info
自定义日志级别控制
@Test
public void testLogConfig() throws IOException {
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.com.lm.JULTest");
// 关闭系统默认配置
logger.setUseParentHandlers(false);
// 自定义配置日志级别
// 创建ConsolHandler
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
// 设置日志具体级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 场景FileHandler 文件输出
FileHandler fileHandler = new FileHandler("F:/logs/jul.log");
// 进行关联
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);
// 2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
结果:
所有日志都打印
Logger之间的父子关系
JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层 RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联
// Logger对象父子关系
@Test
public void testLogParent() throws Exception{
Logger logger1 = Logger.getLogger("com.lm.App");
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);
// 设置loggger2的默认级别
// 创建ConsolHandler对象
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger2.addHandler(consoleHandler);
// 设置日志具体级别
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");
}
结果:logger1继承了logger2的配置
true
logger2 Parent:java.util.logging.LogManager$RootLogger@6f94fa3e,name:
五月 24, 2021 12:36:51 下午 com.lm.App main
严重: severe
五月 24, 2021 12:36:51 下午 com.lm.App main
警告: warning
五月 24, 2021 12:36:51 下午 com.lm.App main
信息: info
五月 24, 2021 12:36:51 下午 com.lm.App main
配置: config
五月 24, 2021 12:36:51 下午 com.lm.App main
详细: fine
五月 24, 2021 12:36:51 下午 com.lm.App main
较详细: finer
五月 24, 2021 12:36:51 下午 com.lm.App main
非常详细: finest
日志的配置文件
默认配置文件路径$JAVAHOME\jre\lib\logging.properties
@Test
public void testProperties() throws IOException {
// 读取自定义配置文件
InputStream in = JULDemo01.class.getClassLoader().getResourceAsStream("logging.properties");
// 获取日志管理器对象
LogManager logManager = LogManager.getLogManager();
// 通过日志管理器加载配置文件
logManager.readConfiguration(in);
Logger logger = Logger.getLogger("com.lm.JULDemo01");
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
配置文件:
# RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler
handlers= java.util.logging.ConsoleHandler
# RootLogger 顶级父元素默认的日志级别为ALL
.level= INFO
## 自定义Logger
com.itheima.handlers = java.util.logging.FileHandler
# 自定义Logger日志等级
com.itheima.level = INFO
# 忽略父日志设置
com.itheima.useParentHandlers = false
# 向控制台输出 handler 对象
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = INFO
# 指定 handler 对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定 handler 对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
## 文件处理器
# 输出日志级别
java.util.logging.FileHandler.level=INFO
# 向日志文件输出的 handler 对象
# 指定日志文件路径 F:/logs/java%u.log
java.util.logging.FileHandler.pattern = F:/logs/java%u.log
# 指定日志文件内容大小(50000字节)
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1
# 指定 hanlder 对象日志消息格式对象
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定以追加方式添加日志内容
java.util.logging.FileHandler.append=true
# 指定日志消息格式
java.util.logging.SimpleFormatter.format = %1$tc %2$s%n%4$s: %5$s%6$s%n
日志原理解析
- 初始化LogManager
- LogManager加载logging.properties配置
- 添加Logger到LogManager
- 从单例LogManager获取Logger
- 设置级别Level,并指定日志记录LogRecord
- Filter提供了日志级别之外更细粒度的控制
- Handler是用来处理日志输出位置
- Formatter是用来格式化LogRecord的
JCL(Jakarta Commons Logging)
介绍
Jakarta Commons Logging (JCL)提供的是一个日志(Log)接口(interface),同时兼顾轻量级和不依赖于具体的日志实现工具。 它提供给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。用户被假定已熟悉某种日志实现工具的更高级别的细节。JCL提供的接口,对其它一些日志工具,包括Log4J, Avalon LogKit, and JDK 1.4等,进行了简单的包装,此接口更接近于Log4J和LogKit的实现
于是JUL刚出来不久,2002年8月Apache推出了JCL(Jakarta Commons Logging),也就是日志抽象层,支持运行时动态加载日志组件的实现,当然也提供一个默认实现Simple Log(在ClassLoader中进行查找,如果能找到Log4j则默认使用log4j实现,如果没有则使用JUL 实现,再没有则使用JCL内部提供的Simple Log实现)
为什么要使用日志门面?
- 面向接口开发,不再依赖具体的实现类。减少代码的耦合
- 项目通过导入不同的日志实现类,可以灵活的切换日志框架
- 统一API,方便开发者学习和使用
- 统一配置便于项目日志的管理
使用
入门实验
1、增加commons-logging依赖
<!--引入common-logging-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2、使用commons-logging得LogFactory获取日志实例(使用JUL)
public class App {
public static void main(String[] args) {
Log logger1 = LogFactory.getLog(App.class);
logger1.info("lm.test");
}
}
结果:
五月 24, 2021 1:05:34 下午 com.lm.App main
信息: lm.test
3、增加log4j 依赖
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
4、使用log4j 打印(日志样式可以通过log4j.properties改变)
public class App {
public static void main(String[] args) {
Log logger1 = LogFactory.getLog(App.class);
logger1.info("lm.test");
}
}
结果:
log4j:WARN No appenders could be found for logger (com.lm.App).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
原理分析
- 通过LogFactory动态加载Log实现类
- 日志门面支持的日志实现数组
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"};
- 获取具体的日志实现
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
result = this.createLogFromClass(classesToDiscover[i], logCategory,true);
}
SLF4J( Simple Logging Facade for Java)
背景
SLF4J: 2006年,Ceki Gülcü(log4j1.x的作者)不适应Apache的工作方式,离开了Apache。然后先后创建了SLF4J(日志门面接口,类似于Commons Logging)和Logback(SLF4J的实现)两个项目,并回瑞典创建了QOS公司,SLF4J(Simple Logging Facade For Java)简单日志门面,和JCL功能类似,但JCL有一个致命的缺点就是算法复杂,出现问题难以排除,而SLF4J的诞生就是为了解决JCL的缺点。QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)
官方网站: www.slf4j.org/
由于Slf4j出来的较晚,光有一个接口,没有实现的日志库也很蛋疼,如JUL和Log4j都是没有实现Slf4j,就算开发者想用Slf4j也用不了
这时候巨佬Ceki发话了,Sum和Apache这两个组织不来实现我的接口没关系,我来实现就好了,只有魔法才能打败魔法。后面巨佬Ceki提供了一系列的桥接包来帮助Slf4j接口与其他日志库建立关系,这种方式称为桥接设计模式。
介绍
SLF4J(The Simple Logging Facade for Java) 是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志框架,如log4j/logback/log4j2等
JCL与SLF4J实现机制对比
使用
简单入门
1、maven添加依赖
<!--slf4j core 使用slf4j必須添加-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<!--slf4j 自带的简单日志实现、如果有其他log实现可以不用 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.28</version>
</dependency>
2、测试
public class App {
public final static Logger LOGGER = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
// 声明日志对象
LOGGER.info("lm.test");
LOGGER.warn("lm.test");
}
}
结果:
[main] INFO com.lm.App - lm.test
[main] WARN com.lm.App - lm.test
slf4j+log4j 使用
1、增加依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、测试
public class App {
public final static Logger LOGGER = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
// 初始化系统配置,不需要配置文件数等
BasicConfigurator.configure();
// 声明日志对象
LOGGER.info("lm.test");
LOGGER.warn("lm.test");
}
}
结果:
1 [main] INFO com.lm.App - lm.test
3 [main] WARN com.lm.App - lm.test
SLF4J 日志绑定
门面框架与日志框架进行集成的时,需要通过桥接包进行适配--注意只能存在一个log实现方案
使用slf4j的日志绑定流程
1. 添加slf4j-api的依赖
2. 使用slf4j的API在项目中进行统一的日志记录
3. 绑定具体的日志实现框架
1. 绑定已经实现了slf4j的日志框架,直接添加对应依赖
2. 绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
4. slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)
集成方案
桥接旧的日志框架(Bridging)
那如果一个项目中使用了多种具体的日志框架,想要统一是配到slf4j,slf4j再使用一个日志框架进行输出.如之前使用的log4j,现在想使用slf4j 做门面,又不像改api的import代码引入时
绑定的适配jar 不能与桥接久框架的适配jar 同时存在,不然可能死循环.如 jcl-over-slf4j.jar和 slf4j-jcl.jar不能同时部署、log4j-over-slf4j.jar和slf4j-log4j12.jar不能同时出现
Logback
背景
Ceki巨佬觉得市场上的日志标准库都是间接实现Slf4j接口,也就是说每次都需要配合桥接包,因此在2006年,Ceki巨佬基于Slf4j接口撸出了Logback日志标准库,做为Slf4j接口的默认实现,Logback也十分给力,在功能完整度和性能上超越了所有已有的日志标准库
目前Java日志体系关系图如下:
介绍
Slf4j是The Simple Logging Facade for Java的简称,是一个简单日志门面抽象框架,它本身只提供了日志Facade API和一个简单的日志类实现,一般常配合Log4j,LogBack,java.util.logging使用。Slf4j作为应用层的Log接入时,程序可以根据实际应用场景动态调整底层的日志实现框架(Log4j/LogBack/JdkLog…)。LogBack和Log4j都是开源日记工具库,LogBack是Log4j的改良版本,比Log4j拥有更多的特性,同时也带来很大性能提升。
LogBack被分为3个组件,logback-core, logback-classic 和 logback-access。
logback-core提供了LogBack的核心功能,是另外两个组件的基础。
logback-classic则实现了Slf4j的API,所以当想配合Slf4j使用时,需要将logback-classic加入classpath。
logback-access是为了集成Servlet环境而准备的,可提供HTTP-access的日志接口。
logback配置
logback 配置文件执行顺序
(1) 尝试在classpath下查找文件logback-test.xml
(2) 如果文件不存在,则查找文件logback.xml
(3) 如果两个文件都不存在,则用BasicConfigurator自动对自己进行配置,把记录输出到控制台
配置文件引入方式
rpc服务:classpath下有配置文件就行
web服务:
<!--logback-->
<context-param>
<param-name>logbackConfigLocation</param-name>
<param-value>/WEB-INF/conf/logback.xml</param-value>
</context-param>
<listener>
<listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
</listener>
logback配置结构
简单例子:
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="glmapper-name" value="glmapper-demo" />
<contextName>${glmapper-name}</contextName>
<appender>
//xxxx
</appender>
<logger>
//xxxx
</logger>
<root>
//xxxx
</root>
</configuration>
根节点<configuration>包含的属性
scan
当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod
设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug
当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 其他配置省略-->
</configuration>
设置变量 <property>
property
<property>用来定义变量值的标签,<property> 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="APP_Name" value="myAppName" />
<contextName>${APP_Name}</contextName>
<!-- 其他配置省略-->
</configuration>
获取时间戳字符串<timestamp>
timestamp
两个属性 key:标识此 <timestamp> 的名字;datePattern:设置将当前时间(解析配置文件的时间)转换为字符串的模式,遵循java.txt.SimpleDateFormat的格式
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<contextName>${bySecond}</contextName>
<!-- 其他配置省略-->
</configuration>
配置logger
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
name:
用来指定受此loger约束的某一个包或者具体的某一个类
level:
用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。 如果未设置此属性,那么当前loger将会继承上级的级别。
addtivity:
是否向上级logger传递打印信息。默认是true
<appender-ref>
<logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
例子:
<appender name="watcherScribe" class="com.scribe.logback.AsyncScribeAppender">
<!-- 这是scribe的发送flume的配置 -->
<sizeOfInMemoryStoreForward>2000</sizeOfInMemoryStoreForward>
</appender>
<logger name="metrics_banma_watcher" additivity="false">
<level value="INFO"/>
<appender-ref ref="watcherScribe"/>
</logger>
配置root
也是元素,但是它是根logger。只有一个level属性,应为已经被命名为"root"
<root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个loger
<!-- Root Logger -->
<root>
<level value="INFO" />
<appender-ref ref="banmaLogModule" />
<appender-ref ref="errorlog" />
<appender-ref ref="banmaErrorLogModule" />
</root>
appender 配置
负责写日志的组件、有两个必要属性name和class。name指定appender名称,class指定appender的全限定名
例子:
<appender name="GLMAPPER-LOGGERONE" 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}/glmapper-spring-boot/glmapper-loggerone.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-loggerone.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>
filter 子标签
上面说的。可以为appender 添加一个或多个过滤器,可以用任意条件对日志进行过滤。appender 有多个过滤器时,按照配置顺序执行
ThresholdFilter
临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
LevelFilter
级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath(用于配置符合过滤条件的操作) 和 onMismatch(用于配置不符合过滤条件的操作)接收或拒绝日志。
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 将过滤器的日志级别配置为INFO,所有INFO级别的日志交给appender处理,非INFO级别的日志,被过滤掉。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger{30} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
Appender
ConsoleAppender:
把日志添加到控制台,有以下子节点:
- <encoder>:对日志进行格式化。(具体参数稍后讲解 )
- <target>:字符串 System.out 或者 System.err ,默认 System.out ;
例如:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
FileAppender:
把日志添加到文件,有以下子节点:
- <file>:被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。
- <append>:如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。
- <encoder>:对记录事件进行格式化。(具体参数稍后讲解 )
- <prudent>:如果是 true,日志会被安全的写入文件,即使其他的FileAppender也在向此文件做写入操作,效率低,默认是 false。
例子:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>testFile.log</file>
<append>true</append>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
RollingFileAppender:
滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件。有以下子节点:
- <file>:被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。
- <append>:如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。
- <encoder>:对记录事件进行格式化。(具体参数稍后讲解 )
- <rollingPolicy>:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。
- <triggeringPolicy >: 告知 RollingFileAppender 合适激活滚动。
- <prudent>:当为true时,不支持FixedWindowRollingPolicy。支持TimeBasedRollingPolicy,但是有两个限制,1不支持也不允许文件压缩,2不能设置file属性,必须留空
rollingPolicy:
<appender name="jmonitorappender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>/var/sankuai/logs/${app.key}.jmonitor.log.%d{yyyy-MM-dd}</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>5</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
TimeBasedRollingPolicy:
最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动,这个下面又包括了两个属性:
- FileNamePattern:必要节点,包含文件名及“%d”转换符, “%d”可以包含一个java.text.SimpleDateFormat指定的时间格式,如:%d{yyyy-MM}。如果直接使用 %d,默认格式是 yyyy-MM-dd。RollingFileAppender 的file字节点可有可无,通过设置file,可以为活动文件和归档文件指定不同位置,当前日志总是记录到file指定的文件(活动文件),活动文件的名字不会改变;如果没设置file,活动文件的名字会根据fileNamePattern 的值,每隔一段时间改变一次。“/”或者“\”会被当做目录分隔符
- maxHistory:可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,且是6,则只保存最近6个月的文件,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除。
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名:按天回滚 daily -->
<FileNamePattern>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
FixedWindowRollingPolicy:
根据固定窗口算法重命名文件的滚动策略
- <minIndex>:窗口索引最小值
- <maxIndex>:窗口索引最大值,当用户指定的窗口过大时,会自动将窗口设置为12。
- <fileNamePattern >:必须包含“%i”例如,假设最小值和最大值分别为1和2,命名模式为 mylog%i.log,会产生归档文件mylog1.log和mylog2.log。还可以指定文件压缩选项,例如,mylog%i.log.gz 或者 没有log%i.log.zip
按日期和大小拆分日志例子:
<!-- 按日期和大小拆分日志 -->
<configuration>
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>mylog.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<!-- 每个文件最大100MB,保留60天的历史意义,一共最大20gb -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="ROLLING" />
</root>
</configuration>
Log4j2
自从Logback出来后,可以说Slf4j+Logback组合如日中天,很冲击JCL+Log4j组合,Apache眼看有被Logback反超的势头
于2012年,Apache直接推出新项目Log4j2(不兼容Log4j),Log4j2全面借鉴Slf4j+Logback(十分明显的抄袭嫌疑)
因为Log4j2不仅仅具有Logback的所有特性,还做了分离设计,分为log4j-api和log4j-core,log4j-api是日志接口,log4j-core是日志标准库,并且Apache也为Log4j2提供了各种桥接包
到目前为止Java日志体系被划分为两大阵营,分别是Apache阵营和Ceki阵营,如下图所示
介绍
Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带 来了一些重大的提升
- 1、性能提升, log4j2相较于log4j 和logback都具有很明显的性能提升,后面会有官方测试的数据。
- 2、自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产 上可以动态的修改日志的级别而不需要重启应用。
- 3、 无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集 导致的jvm gc
- 4、异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异 常处理机制。
记录日志的过程,大体上可以分成三个步骤:
1、在程序中对原始日志信息进行采集
2、对采集下来的日志信息进行格式化
3、将格式化好的日志信息写入目的地
Log4j2 的架构也自然是按照这个来的,Log4j2 中有三个重要的组件分别实现了这些功能,分别是 Logger 、Layout 和Appender,日志数据流向图如下:
使用
slf4j+log4j2 入门配置
1、增加配置
<!-- Log4j2 门面API--> <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>
<!--使用slf4j作为日志的门面,使用log4j2来记录日志 --> <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--为slf4j绑定日志实现 log4j2的适配器 --> <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
2、加入log4j2 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
3、测试代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class App {
public final static Logger LOGGER = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
// 声明日志对象
LOGGER.debug("lm.test");
LOGGER.info("lm.test");
LOGGER.warn("lm.test");
}
}
结果:
14:23:38.229 [main] INFO com.lm.App - lm.test
14:23:38.233 [main] WARN com.lm.App - lm.test
Logger logger = LogManager.getLogger(loggerName);
logger.info(msg);
对于获取logger和调用info操作的通用流程我直接用流程图:
web项目配置
web项目需要加入web.xml
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>/WEB-INF/conf/log4j2.xml</param-value>
</context-param>
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
Log4j2配置
Log4j2配置方式
Log4j2 有两种配置方式:
- 通过 ConfigurationFactory 使用编程的方式进行配置
- 通过配置文件配置,支持 XML 、JSON 、YAML 和 properties 等文件类型
通过编程实现的方式如下:
@Plugin(name = "CustomerConfigurationFactory", category = ConfigurationFactory.CATEGORY)
public class CustomerConfigurationFactory extends ConfigurationFactory {
protected String[] getSupportedTypes() {
return new String[0];
}
public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource configurationSource) {
ConfigurationBuilder<BuiltConfiguration> builder = newConfigurationBuilder();
builder.setStatusLevel(Level.INFO);
return builder.build();
}
}
在 Log4j2 中,默认实现了四种 ConfigurationFactory,分别用来加载 JSON YAML properties XML 格式的配置文件、log4j2默认会在classpath目录下寻找log4j.json、log4j.jsn、log4j2.xml等名称的文件,如果都没有找到,则会按默认配置输出,也就是输出到控制台,并且默认级别是 Level.ERROR
假如同时存在 log4j2.properties log4j2.yaml log4j2.json log4j2.xml 四个配置文件,加载的优先级为 properties > yaml > json > xml
Log4j2配置文件详解
Configuration配置
Confirguration 是配置文件根元素,每个配置文件有且仅有一个。如果不使用配置文件使用默认配置,以下配置与默认配置等价:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
每一个配置都至少包含一个 Appender 和 Logger。status 属性用于配置 Log4j2 内部的的日志级别
日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出
Log4j2 可以自动检测配置文件的变化,monitorInterval 属性可以配置自动检测配置文件的时间,这个属性最小的值为 5,代表 5 秒检测一次
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30">
....
</Configuration>
Logger配置
Logger 元素用于配置日志记录器。Logger 有两种形式,Logger 和 Root,两个区别在于 Root 没有 name 属性并且不支持 additivity 属性
假如我们要获取一个特定类的的某个级别的日志。调高日志的级别无法完成这样的需求。一种可行的方式是在 Loggers 中添加一个 Logger,配置方式如下:
<Loggers>
<Logger name="cn.rayjun.Log4j2HelloWorld" level="trace">
<AppenderRef ref="Console"/>
</Logger>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
这样配置,除了 cn.rayjun.Log4j2HelloWorld 类会打印出 trace 级别的日志,其他就只会打印出 error 级别的日志,打印结果如下:
19:59:06.957 [main] TRACE cn.rayjun.Log4j2HelloWorld - Enter
19:59:06.957 [main] TRACE cn.rayjun.Log4j2HelloWorld - Enter
19:59:06.959 [main] ERROR cn.rayjun.Log4j2HelloWorld - Hello world
19:59:06.959 [main] ERROR cn.rayjun.Log4j2HelloWorld - Hello world
但是上面打印的日志有个问题,信息都被打印了两遍,为了解决这个问题,需要添加 additivity 参数并且置为 false,如下配置:
<Loggers>
<Logger name="cn.rayjun.Log4j2HelloWorld" level="trace" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
Logger的additivity设置为false,那么这个日志不会再继续向父Logger进行传递
异步logger
log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,我们来看看如何使用 log4j2的异步日志
默认情况下 Logger 都是同步的,但是也有异步的实现、Root 的异步版本是 AsyncRoot,Logger 的异步版本是 AsyncLogger,异步 Logger 依赖外部队列 LMAX Disruptor。使用异步 Logger 需要加上外部依赖:
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应我们说的Appender组件和Logger组件
AsyncAppender方式
异步依赖arrayblockingqueue队列
AsyncAppender的核心部件是一个阻塞队列,logger将数据通过append方法放入到阻塞队列中,随后后台线程从队列中取出数据然后进行后续的操作
使用case:
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<File name="MyFile" fileName="logs/app.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</File>
<Async name="Async">
<AppenderRef ref="MyFile"/>
</Async>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>
AsyncLogger方式
Root 的异步版本是 AsyncRoot,Logger 的异步版本是 AsyncLogger,异步 Logger 依赖外部队列 LMAX Disruptor。
AsyncLogger调用Disruptor,然后直接返回。但是Disruptor 比较吃内存、可能会导致cpu 飙升、谨慎使用
例子:
<Configuration status="WARN" monitorInterval="30">
<Appenders>
<RollingRandomAccessFile name="applicationAppender" fileName="./log/application.log"
filePattern="./log/$${date:yyyy-MM}/common-%d{yyyy-MM-dd}.log.gz"
append="false">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- AsyncLogger配置 -->
<AsyncLogger name="log4j2" >
<AppenderRef ref="applicationAppender"/>
</AsyncLogger>
<Root level="info">
<AppenderRef ref="applicationAppender"/>
</Root>
</Loggers>
</Configuration>
注意
- 如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。性能会和 AsyncAppender一致,降至最低
- 需要设置includeLocation=false ,如果要打印位置信息会急剧降低异步日志的性能,比同步日志还要 慢
Filter配置
ThresholdFilter
Filters决定日志事件能否被输出。过滤条件有三个值:ACCEPT(接受),DENY(拒绝),NEUTRAL(中立)
输出warn级别一下的日志
<Filters>
<!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是debug\info\warn级别的日志输出-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
只输出error级别以上的日志
<Filters>
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
参考:www.docs4dev.com/docs/zh/log…
Appender配置
Appender 负责将日志分发到相应的目的地。也就是说 Appender 决定日志以何种方式展示,上面使用到的就是 ConsoleAppender,这个 Appender 会将日志直接打印到控制台。同时还支持将日志输出到文件、数据库、消息队列
<Appenders>
<!--这个输出控制台的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 这个都知道是输出日志的格式 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--添加过滤器ThresholdFilter,可以有选择的输出某个级别以上的类别 onMatch="ACCEPT" onMismatch="DENY"意思是匹配就接受,否则直接拒绝 -->
<File name="ERROR_FILE" fileName="./logs/error.log">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<File name="WARN_FILE" fileName="./logs/warn.log">
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<RollingFile name="INFO_RollingFile" fileName="./logs/info.log" filePattern="logs/$${date:yyyy-MM}/info-%d{MM-dd-yyyy}-%i.log.gz">
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="2MB"/>
</RollingFile>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<!--TimeBasedTriggeringPolicy 会与 filePattern 的配置相匹配,如果 filePattern 是 {yyyy-MM-dd HH-mm} ,最小的时间粒度是分钟,那么就会每隔一分钟生成一个文件,如果改成 {yy-MM-dd HH},最小时间粒度是小时,那么就会每隔一个小时生成一个文件-->
<TimeBasedTriggeringPolicy />
<!--SizeBasedTriggeringPolicy 表示设定日志的大小,上面的配置是指日志大小到 250M 后开始生成新的日志文件-->
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
Layouts配置
Appender 会使用 Layout 来对日志进行格式化。Lo4j1 和 Logback 中的 Layout 会把日志转成 String,而在 Log4j2 中使用的则是 byte 数组,这是从性能的角度进行的优化
Layout 配置支持多种方式。用的最多的方式就是 PatternLayout,就是通过正则表达式来格式化日志,应用的也最多,基本配置如下
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
%d 表示时间,默认情况下表示打印完整时间戳 2012-11-02 14:34:02,123,可以调整 %d 后面的参数来调整输出的时间格式
%p 表示输出日志的等级,可以使用 %highlight{%p} 来高亮显示日志级别
%c 用来输出类名,默认输出的是完整的包名和类名,%c{1.} 输出包名的首字母和完整类名
%t 表示线程名称
%m 表示日志内容,%M 表示方法名称
%n 表示换行符
%L 表示打印日志的代码行数
完整简单demo实现
假设现在有一个日志需求:日志以文件的形式进行保存,需要保存完整的日志信息,包括日志对应的类代码行数等,并且需要根据时间和日志文件的大小生成新的日志文件
<Configuration status="warn">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%C.%M] [%m]%n"/>
</Console>
<RollingRandomAccessFile name="${service_name}-log"
fileName="~/${service_name}/${service_name}.log"
filePattern="~/${service_name}/${service_name}.log.%d{yyyyMMddHH}"
append="true" immediateFlush="false">
<PatternLayout
pattern="[%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%C.%M] [%m]%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<AsyncRoot level="${log_level}" includeLocation="true">
<AppenderRef ref="Console"/>
<AppenderRef ref="${service_name}-log" />
</AsyncRoot>
</Loggers>
</Configuration>
Logger 上如果加上了 includeLocation 后,日志性能会下降的很厉害,如果日志的位置信息不是必要的,就不需要加上