Java 日志体系 (二) log4j 配置文件详解 缓存问题,mybatis 基本工作原理
核心对象: 框架的强制对象和框架的使用。core Objects
支持对象: 框架和支持体核心对象,可选的对象执行另外重要的任务。Support Objects
支持对象
Level 对象:级别对象定义的任何记录信息的粒度和优先级。有记录的七个级别在 API 中定义:OFF, DEBUG, INFO, ERROR, WARN, FATAL 和 ALL
Filter 对象:过滤对象用于分析日志信息及是否应记录或不用这些信息做出进一步的决定。一个 appender 对象可以有与之关联的几个 Filter 对象。如果日志记录信息传递给特定 Appender 对象,都和特定 Appender 相关的 Filter 对象批准的日志信息,然后才能发布到所连接的目的地。
对象渲染器:ObjectRenderer 对象是一个指定提供传递到日志框架的不同对象的字符串表示。这个对象所使用的布局对象来准备最后的日志信息。
日志管理:日志管理对象管理的日志框架。它负责从一个系统级的配置文件或配置类读取初始配置参数。
核心对象
Logger、Appender、Layout
Log4j 中有三个主要组成部分
===============
Logger:日志对象,负责捕捉日志记录信息
Logger 对象是用来取代 System.out 或者 System.err 的日志输出器,负责日志信息的输出;其中,log4j 日志框架提供了 info、error、debug 等 API 供 Developer 使用;
与 commons-logging 相同,log4j 也有日志等级的概念;每一个 logger 对象都会分配一个等级,未被分配等级的 logger 则继承根 logger 的级别,进行日志的输出;每个日志对象方法的请求也有一个等级,如果方法请求的等于大于当前 logger 对象的等级,则该请求会被处理输出,否则该请求被忽略;
log4j 在 Level 类中定义了 7 个等级,以此递增
ALL:打开所有日志;
DEBUG:适用于代码调试期间;
INFO:适用于代码运行期间;
WARN:适用于代码会有潜在错误事件;
ERROR:适用于代码存在错误事件;
FATAL:适用于严重错误事件;
OFF:关闭所有日志;
Appender:日志输出目的地,负责把格式好的日志信息输出到指定地方,可以是控制台、磁盘文件等
每个日志对象,都有一个对应的 appender,每个 appender 代表着一个日志输出目的地;其中,log4j 有以下几种 appender 可供选择:
ConsoleAppender:控制台;
FileAppender:磁盘文件;
DailyRollingFileAppender:每天产生一个日志磁盘文件;
RollingFileAppender:日志磁盘文件大小达到指定尺寸时产生一个新的文件;
Layout:日志格式化器,负责发布不同风格的日志信息
每个 appender 和一个 Layout 相对应,appende 负责把日志信息输出到指定的地点,而 Layout 则负责把日志信息按照格式化的要求展示出来;其中,log4j 有以下几种 Layout 可供选择:
HTMLLayout:以 html 表格形式布局展示;
PatternLayout:自定义指定格式展示;
SimpleLayout:包含日志信息的级别和信息字符串;
TTCCLayout:包含日志产生的时间、线程、类别等等信息;
示例
--
创建 maven 项目,学习 log4j。
pom.xml 添加依赖
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
复制代码
测试类 Log4jTest
public class Log4jTest {
Logger logger = Logger.getLogger(Log4jTest.class);
@Test
public void test() {
logger.fatal("Fatal Message!");
logger.error("Error Message!");
logger.warn("Warn Message!");
logger.info("Info Message!");
logger.debug("Debug Message!");
logger.trace("Trace Message!");
}
}
复制代码
没有配置文件怎么样,看一下执行结果
log4j:WARN No appenders could be found for logger (cn.lingyiwin.logs.Log4jTest).
警告无法为记录器找到附加器
log4j:WARN Please initialize the log4j system properly.
警告:请正确初始化 log4j 系统
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
警告见http://logging.apache.org/log4j/1.2/faq.html#noconfig获取更多信息
Process finished with exit code 0
复制代码
配置 log4j.properties 或者 log4j.xml 文件到 resources
log4j.properties
log4j.rootLogger = INFO, FILE, CONSOLE
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=D:/logs/log.out
log4j.appender.FILE.ImmediateFlush=true
log4j.appender.FILE.Threshold = DEBUG
log4j.appender.FILE.Append=true
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.conversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.ImmediateFlush=true
log4j.appender.CONSOLE.Threshold = DEBUG
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.encoding=UTF-8
log4j.appender.CONSOLE.layout.conversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
复制代码
log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out"/>
<param name="immediateFlush" value="true"/>
<param name="threshold" value="DEBUG"/>
<param name="append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d - %c -%-4r [%t] %-5p %x - %m
%n" />
</layout>
</appender>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="D:/logs/log.out" />
<param name="ImmediateFlush" value="true"/>
<param name="Threshold" value="DEBUG"/>
<param name="Append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
<category name="cn.ling.logs" additivity="false">
<level value="ERROR"></level>
<appender-ref ref="CONSOLE" />
</category>
<root>
<priority value="ERROR" />
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>
复制代码
==如果是非 maven 项目,参考www.cnblogs.com/fps2tao/p/1…
log4j 配置文件详解
===========
Logger 日志对象,负责捕捉日志记录信息
配置根 Logger,其语法为:
log4j.rootLogger = [ level ] , appenderName, appenderName, …
复制代码
level 指的是根 logger 对象的日志等级,ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF
Log4j 建议只使用 4 个级别,从高到低分别为 ERROR > WARN > INFO > DEBUG;
appenderName 指的是根 logger 对象的日志信息输出目的地,在此可以指定多个输出目的地;
日志输出级别 INFO
输出目的地 FILE CONSOLE
log4j.rootLogger = INFO, FILE, CONSOLE
复制代码
Appender 日志输出目的地,负责把格式好的日志信息输出到指定地方,可以是控制台、磁盘文件等
配置日志信息输出目的地 Appender,其语法为:
log4j.appender.appenderName = className
复制代码
appenderName 指的是日志信息输出目的地的名称,可自定义,需要与根 Logger 中的 appenderName 一致;
className 指的是日志输出目的地处理类,必须为全限定类名;
将日志信息输出到对应的磁盘文件中
log4j.appender.FILE = org.apache.log4j.FileAppender
指定日志输出的最低级别,默认为 DEBUG;如果日志请求的级别低于此级别,则不会输出此请求日志信息
log4j.appender.FILE.Threshold = DEBUG
将日志输出到 D 盘的 logs/log.out 文件中
log4j.appender.FILE.File=D:/logs/log.out
设置日志输出的编码
log4j.appender.FILE.Encoding=UTF-8
将新增日志追加到文件中,默认为 true 为不覆盖,false 为覆盖
log4j.appender.FILE.Append=false
请求的日志消息被立即输出,默认为 true
log4j.appender.FILE.ImmediateFlush=true
请求的日志消息不会立即输出,存储到缓存当中,当缓存满了后才输出到磁盘文件中,默认为 false,此时 ImmediateFlush 应当设置为 false
log4j.appender.FILE.BufferedIO=true
缓存大小,默认为 8k
log4j.appender.FILE.BufferSize= 8192
将日志信息输出到控制台中
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
将日志信息使用 System.out.println 输出到控制台
log4j.appender.CONSOLE.Target=System.out
请求的日志消息被立即输出,默认为 true(同上)
log4j.appender.CONSOLE.ImmediateFlush=true
指定日志输出的最低级别,默认为 DEBUG;如果日志请求的级别低于此级别,则不会输出此请求日志信息(同上)
log4j.appender.CONSOLE.Threshold = DEBUG
设置日志输出的编码(同上)
log4j.appender.CONSOLE.encoding=UTF-8
将输出的日志信息,每天产生一个日志文件,与上面 FileAppender 不同
log4j.appender.DRFILE = org.apache.log4j.DailyRollingFileAppender()
将日志输出到 D 盘的 logs/log.out 文件中(同上)
log4j.appender.DRFILE.File=D:/logs/log.out
请求的日志消息被立即输出,默认为 true(同上)
log4j.appender.DRFILE.ImmediateFlush=true
指定日志输出的最低级别,默认为 DEBUG;如果日志请求的级别低于此级别,则不会输出此请求日志信息(同上)
log4j.appender.DRFILE.Threshold = DEBUG
将新增日志追加到文件中,默认为 true 为不覆盖,false 为覆盖 (同上)
log4j.appender.DRFILE.Append=true
标识每天产生一个新的日志文件,当然也可以指定按月、周、时、分
log4j.appender.DRFILE.DatePattern='.'yyyy-MM-dd-HH-mm
具体格式如下:
1)'.'yyyy-MM: 每月
2)'.'yyyy-ww: 每周
3)'.'yyyy-MM-dd: 每天
4)'.'yyyy-MM-dd-a: 每天两次
5)'.'yyyy-MM-dd-HH: 每小时
6)'.'yyyy-MM-dd-HH-mm: 每分钟
在日志文件达到指定的大小后,再产生新的文件继续记录日志
log4j.appender.RFILE = org.apache.log4j.RollingFileAppender
将日志输出到 D 盘的 logs/log.out 文件中(同上)
log4j.appender.RFILE.File=D:/logs/log.out
指定日志输出的最低级别,默认为 DEBUG;如果日志请求的级别低于此级别,则不会输出此请求日志信息(同上)
log4j.appender.RFILE.Threshold = DEBUG
设置日志输出的编码(同上)
log4j.appender.RFILE.encoding=UTF-8
将新增日志追加到文件中,默认为 true 为不覆盖,false 为覆盖 (同上)
log4j.appender.RFILE.Append=false
请求的日志消息被立即输出,默认为 true(同上)
log4j.appender.RFILE.ImmediateFlush=true
指定日志文件切割大小,默认 10MB,单位 KB/MB/GB;当日志文件达到指定大小后,将当前日志文件内容剪切到新的日志文件中,新的文件默认以“原文件名+.1”、“原文件名+.2”的形式命名
log4j.appender.RFILE.MaxFileSize=100KB
#产生的切割文件最大数量,如果第二个文件超过了指定大小,那么第一个文件将会被删除
log4j.appender.RFILE.MaxBackupIndex=2
复制代码
Layout
配置日志信息的格式 Layout,其语法为:
log4j.appender.appenderName.layout = className
复制代码
ppenderName 就是上面所讲的 Appender 的名称,Appender 必须与 Layout 相互绑定;
className 则是处理日志格式的类,也必须是全限定类名;
以 html 表格形式布局
log4j.appender.FILE.layout = org.apache.log4j.HTMLLayout
输出 java 文件名称和行号,默认值 false
log4j.appender.FILE.layout.LocationInfo = true
简单风格布局,只包含日志信息和级别
log4j.appender.FILE.layout = org.apache.log4j.SimpleLayout
#自定义风格布局,可以包含时间,日志级别,日志类
log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
#指定怎样格式化的消息
log4j.appender.FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n
具体的格式化说明:
%p:输出日志信息的优先级,即 DEBUG,INFO,WARN,ERROR,FATAL。
%d:输出日志时间点的日期或时间,默认格式为 ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}。
%r:输出自应用程序启动到输出该 log 信息耗费的毫秒数。
%t:输出产生该日志事件的线程名。
%l:输出日志事件的发生位置,相当于 %c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数。例如:test.Log4jTest.main(Log4jTest.java:10)。
%c:输出日志信息所属的日志对象,也就是 getLogger()中的内容。
%C:输出日志信息所属的类目;
%logger:log4j 中没有此格式;
%M:输出产生日志信息的方法名。
%F:输出日志消息产生时所在的文件名称。
%L::输出代码中的行号。
%m::输出代码中指定的具体日志信息。
%n:输出一个回车换行符,Windows 平台为"rn",Unix 平台为"n"。
%x:输出和当前线程相关联的 NDC(嵌套诊断环境),尤其用到像 java servlets 这样的多客户多线程的应用中。
%%:输出一个"%"字符。
复制代码
性能优化
====
由于频繁的 IO 和磁盘的读写,应用的性能也随之降低。并且,java 的 IO 是阻塞式,加锁后导致也同样降低性能。因此对于日志的调优,就成了必备功课。
首先,抛开频繁的 IO 和磁盘读写不谈,就单纯讨论 log4j 的性能而言,在高并发的情况下,log4j 的锁也会导致应用的性能下降,究其原因,还是看以下的代码:
Category 类的 callAppenders 方法:
//日志对象唤起日志输出目的地 Appender:进行日志打印
public void callAppenders(LoggingEvent event) {
int writes = 0;
//遍历日志对象集合
for(Category c = this; c != null; c=c.parent) {
//Category 是 Logger 的父类,此处的 c 就是请求的日志对象本身:
synchronized(c) {
//此处加锁了:
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
复制代码
通过以上代码,我们可以发现,为了获得对应日志对象的 Appender,会在每次获取之前都加上 synchronized 同步锁。无论多少个线程进行请求,到此处都需要进行获取锁的操作,才可以进行日志的打印。这也就是说,线程越多,并发越大,此处的锁的竞争越激烈,进而导致系统性能的降低。
其次,我们再回过头来看下 IO 和磁盘读写的问题。在实际的生产环境下,系统所产生的日志信息需要保存在磁盘文件中,以便日后进行系统分析,或者系统问题的查找。
之前,我们说过 Java 的 IO 是阻塞式的,下面就来看下实际的代码:JDK1.7 中的 sun.nio.cs.StreamEncoder 类:
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
implWrite(cbuf, off, len);
}
}
复制代码
可以看到,在 java-IO 流最终输出阶段,也同样加了 synchronized 同步锁。这也就是我们所说的 java 阻塞式 IO。
log4j 性能测试
FileAppender 类主要功能就是将日志信输出到磁盘文件中。其中,有 ImmediateFlush、BufferedIO、BufferSize 这三个属性尤为值得关注;
当 ImmediateFlush=true 时候,表示每一条打印日志请求都会被立即输出,也就是立刻同步到磁盘中去。在高并发下,系统性能受到很大的影响,IO 和磁盘读写数大大提升。
当 ImmediateFlush=false 时候,与上面正好相反,表示每一条打印日志请求不会被立即输出,会使用 java.io.OutputStreamWriter 的缓存,缓存大小为 1024 字节。
当 ImmediateFlush=false、BufferedIO=true、BufferSize=8192 时候,表示使用 java.io.BufferedWriter 缓存,缓存大小为默认 8192 字节,每一条打印请求不会立即输出,当缓存达到 8192 字节后才会落盘操作。这样一来,大大减少了 IO 和磁盘读写操作,提升了系统的性能。
测试代码:
public class log4jDemo {
Logger log = Logger.getLogger(log4jDemo.class);
@Test
public void test() throws InterruptedException {
for(int x=0;x<20;x++) {
long start = System.currentTimeMillis();
for (int y = 0; y < 50; y++) {
log.info("Info Message!");
}
long time = System.currentTimeMillis() - start;
System.out.println(time);
}
}
}
复制代码
评论