Java日志体系(二) log4j 配置文件详解 缓存问题

  • DailyRollingFileAppender:每天产生一个日志磁盘文件;

  • RollingFileAppender:日志磁盘文件大小达到指定尺寸时产生一个新的文件;

Layout:日志格式化器,负责发布不同风格的日志信息


每个appender和一个Layout相对应,appende负责把日志信息输出到指定的地点,而Layout则负责把日志信息按照格式化的要求展示出来;其中,log4j有以下几种Layout可供选择:

  • HTMLLayout:以html表格形式布局展示;

  • PatternLayout:自定义指定格式展示;

  • SimpleLayout:包含日志信息的级别和信息字符串;

  • TTCCLayout:包含日志产生的时间、线程、类别等等信息;

示例

创建maven项目,学习log4j。

pom.xml添加依赖


log4j

log4j

1.2.17

复制代码

测试类 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" ?>

log4j:configuration

</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© {

//此处加锁了:

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);

}

}

}

复制代码

例子1:当ImmediateFlush=true时,测试结果(单位毫秒):

931 631 372 371 374 371 371 383 376 439 376 383 372 416 393 368 368 366 376 376 394 384 373 396 371 380 368 382 373 369 373 379 374 370 381 367 371 379 372 385 381 379 375 398 409 415 392 371 403 406

复制代码

例子2:当ImmediateFlush=false时,测试结果(单位毫秒):

845 693 344 338 353 340 372 373 345 337 332 341 345 352 346 332 336 333 379 359 333 330 356 338 333 341 346 331 341 337 339 329 341 339 339 334 341 328 331 329 328 330 329 336 334 332 332 331 333 330

复制代码

例子3:当ImmediateFlush=false、BufferedIO=true、BufferSize=8192时,测试结果(单位毫秒):

731 853 356 332 336 334 334 334 334 331 332 344 331 330 332 332 330 331 340 334 329 333 331 335 334 334 332 331 336 335 331 354 334 333 334 354 331 333 334 332 333 331 347 332 333 330 332 330 333 331

复制代码

例子4:使用AsyncAppender异步处理,测试结果(单位毫秒):

292 178 146 177 216 278 215 147 136 102 92 96 97 93 93 95 93 94 92 93 97 93 94 93 95 94 96 107 94 91 93 94 99 98 96 95 95 98 102 95 93 92 91 107 155 137 110 98 93 93

复制代码

通过以上4个例子,我们可以看出,性能最差的是ImmediateFlush=true的时候,而性能最好的就是开启日志异步AsyncAppender处理的时候;

log4j钩子程序


log4j的缓存,通过测试结果来看,在开启缓存的情况下,log4j的性能得到了大幅度提升。既然缓存的优势这么明显,为什么log4j不默认开启缓存呢?

缓存的存在,有利有弊。利,提升系统响应性能;弊,当系统因为异常而崩溃,又或者jvm被强行关闭,从而导致缓存中的数据丢失,日志不存在,无法及时确定异常原因。我想,这个才是log4j并没有默认开启缓存的原因!

日志的存在,一方面为了记录系统请求的信息;另一方面,帮助develpoer及时发现、排除错误原因。如果连日志的完整性都不能保留,那么日志存在的意义又是什么?所以,log4j并没有将缓存设置为默认开启,只是提供了一个选项;

那么,我们如何使鱼和熊掌可以兼得呢?在log4j提供的api中暂时无法实现此需求,不过jvm向我们提供了一个方法,可以帮助我们实现,这就是jvm关闭钩子程序;

在jvm中注册一个钩子程序,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。

那么,在我们的日志中,如何实现钩子程序呢?请看下面的实现:

测试代码:

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值