论坛首页 Java企业应用论坛

log4j 动态日志文件名 的线程安全问题

浏览 14735 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-03-14   最后修改:2012-03-14
假设一个场景,有一个webservive,java开发的。
有很多客户端频繁调用这个webservice服务,webservice方法的参数中有一项为clientId
日志要求是,希望在webservice服务程序对每一个clientId能够单独文件记录日志(clientId 数量不算太多,但是数量和名称不固定)
我目前的实现方式如下:
		Appender appender = LogManager.getLoggerRepository().getRootLogger().getAppender("R");
		((FileAppender)appender).setFile(DIR+File.seperator+clientId+".txt");
		((FileAppender)appender).activateOptions();
		log.info("client :" + clientId +" in-------<");
//业务代码 省略
        log.info("client :" +clientId+" out------->");


测试了一下多个client并发的情况,日志文件没有按要求记录。问题应该是appender是非线程安全的。

我目前想到的办法是  log.info 里从threadLocal读出filename 再设置日志文件名称 ,再设置文件名,不知道这样可不可行。
   发表时间:2012-03-14  
自定义一个SelfFileAppender,重写activateOptions方法,设置自己的文件命名逻辑,
指定使用此appender
log4j.appender.R=SelfFileAppender

不管考虑线程的事,log4j会管理
0 请登录后投票
   发表时间:2012-03-14  
tianzizhi 写道
自定义一个SelfFileAppender,重写activateOptions方法,设置自己的文件命名逻辑,
指定使用此appender
log4j.appender.R=SelfFileAppender

不管考虑线程的事,log4j会管理


activateOptions不会被自动调用的
0 请登录后投票
   发表时间:2012-03-14  
你Appender是同一个,你替换Appender全世界当然都变了。重点在Appender的doAppend(LoggingEvent),输出目的地不同,应该在这里动手

方法一:只是理论,没实践过
1 Logger每次名字不一样,比如都叫"client." + clientId,点很重要。
2 自定义一个Appender,实现文件的输出。但是每次输出流不是一开始就打开,而是根据doAppend(LoggingEvent)的参数名称来(即1中的client.后部分),Writer/OutputStream可以考虑存入map,以加快速度

方法二:最简便
1 Logger还是每次名字不一样,附带clientId信息
考虑用JDBCAppender,表名或者某个字段,基于clientId。可以参考JDBCAppender,不过这个有点简陋。稍微复杂一点的需要覆盖getLogStatement(LoggingEvent)或者自己写一个


0 请登录后投票
   发表时间:2012-03-14  
前面之所以说logger的name中,用“.”是因为:
1 配置,你只要设置client就可以覆盖所有n个logger
2 patternLayout也能%c{1}直接得到clientId
0 请登录后投票
   发表时间:2012-03-15  
captmjc 写道
你Appender是同一个,你替换Appender全世界当然都变了。重点在Appender的doAppend(LoggingEvent),输出目的地不同,应该在这里动手

方法一:只是理论,没实践过
1 Logger每次名字不一样,比如都叫"client." + clientId,点很重要。
2 自定义一个Appender,实现文件的输出。但是每次输出流不是一开始就打开,而是根据doAppend(LoggingEvent)的参数名称来(即1中的client.后部分),Writer/OutputStream可以考虑存入map,以加快速度

方法二:最简便
1 Logger还是每次名字不一样,附带clientId信息
考虑用JDBCAppender,表名或者某个字段,基于clientId。可以参考JDBCAppender,不过这个有点简陋。稍微复杂一点的需要覆盖getLogStatement(LoggingEvent)或者自己写一个



方法一对原有写日志的程序需要改动所以我觉得不是很合适
方法二试过效率实在太低了,影响业务程序
0 请登录后投票
   发表时间:2012-03-15  
> 方法一对原有写日志的程序需要改动所以我觉得不是很合适
反正你原来的代码,:
System.setOut(....);
System.out.println(.....);
我觉得,使用同一个Logger,然后根据不同的msg,输出到不特定的有点麻烦,改动log4j的地方太多,IMHO反而更不合适。

> 方法二试过效率实在太低了,影响业务程序
不知道你并发量,JDBCAppender确实很弱,甚至还有bug。但是合理tuning或者自己重写一个DB类的Appender,理论上还是可行的。

还有一种方法,扩展一个File/WriterAppender,路径通过注入clientId自动获取。Override其中的doAppend方法,判断LoggingEvent中的message是否固定格式,比如你这里的"client: ${clientId} "开头,如果是,则super.doAppend(event)否则return.

然后写个AppenderFactory,根据clientId获得不同的实例。
log动作开始前,
Appender appender = factory.getAppender(clientId);
if (!log.isAttached(appender)) {
  log.addAppender(appender);
}
然后再写日志。

由于org.apache.log4j.helpers.AppenderAttachableImpl.isAttached(Appender)中间使用的是==比较,因此factory.getAppender(clientId)需要用map保留原有实例。
0 请登录后投票
   发表时间:2012-03-15  
打字漏了:反正你原来的代码,IMHO,和下面代码无异:
System.setOut(....);
System.out.println(.....);

根本不是相关代码线程安全不安全的问题,而是你错误使用,所以,补充前面第一条的回复,改动日志输出的方式,才是治本。
0 请登录后投票
   发表时间:2012-03-15  
前段时间,我也做了一个多线程并发的程序,也要写日志,翻新log4j打出来的日志,也无法保证日志的连贯性,多个线程的日志会互相交叉。

后来想了一个办法。

日志代码,我们自己在每个线程自己的程序开头,用StringBuffer拼接好。到运行结束的地方,把日志用log4j记录下来,这样就能保证 整块日志的是同一个功能的日志了。

这样讲,明白了吗?
0 请登录后投票
   发表时间:2012-03-15  
youjianbo_han_87 写道
前段时间,我也做了一个多线程并发的程序,也要写日志,翻新log4j打出来的日志,也无法保证日志的连贯性,多个线程的日志会互相交叉。

后来想了一个办法。

日志代码,我们自己在每个线程自己的程序开头,用StringBuffer拼接好。到运行结束的地方,把日志用log4j记录下来,这样就能保证 整块日志的是同一个功能的日志了。

这样讲,明白了吗?

不要意思,没有看到是文件名的问题。回答的牛头不对马嘴。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics