实际背景
客户有客户端多台,每个客户端有自己的唯一编号。输出的日志要根据每个客户端的编号生成,例如10001_demo.log,10002_demo.log
方法
1.网上给出的第一种方法是:
在log4j的配置文件中log4j.appender.file.File=${log.dir}/${log.file}中,使用${}形式定义变量,在后台使用
System.setProperty("log.dir","/home/..."),来设置变量值。但我测试了多次,这样是不起作用的,可能是我自己配置的问题。
2.第二种方法,亲测可用:
1 Logger log = Logger.getLogger(ZhzhcxCtl.class);//获取log对象
2 FileAppender fileAppender = (FileAppender) Logger.getRootLogger().getAppender("file");//获取FileAppender对象
3 fileAppender.setFile("/home/log/gcds.log");//重新设置输出日志的路径和文件名
4 fileAppender.activateOptions();//使设置的FileAppender起作用
5 log.info("index.........");
但是这样的话,每次调用log.info()方法之前都要添加第2--4行这几段代码,这样写代码冗余比较大。或许这时可以想到用代理的方法,但是如果代理了,这时log.info()出现的地方就不是原来的那个类里面了,而是现在这个代理的类了。这样出现的问题就是log4j.properties配置文件中定义的输出格式%c打印的类的名字永远都是代理的那个类了,就永远看不到到底是哪个类调用的log.info方法。所以这种方法也是不可行的。
3.第三种方法:重写FileAppender的subAppend方法。
通过看log4j的FileAppender源码,我们可以发现,FileAppender是继承WriterAppender类的,而WriterAppender有一个subAppend方法,我们看一下subAppend方法源码:
protected void subAppend(LoggingEvent event) { this.qw.write(this.layout.format(event)); if (this.layout.ignoresThrowable()) { String[] s = event.getThrowableStrRep(); if (s != null) { int len = s.length; for (int i = 0; i < len; i++) { this.qw.write(s[i]); this.qw.write(Layout.LINE_SEP); } } } if (shouldFlush(event)) { this.qw.flush(); } }
对,没看错,这个方法就是往日志文件里写东西的。因为FileAppender是继承WriterAppender类的,所以subAppend也是FileAppender的。这时我们只要在subAppend之前,吧文件名改成我们想要的,就可以了。
FileAppender有2个setFile方法,是用来设置输出日志文件的路径的。我们可以subAppend之前调用一次setFile就可以了。我们知道,每一次http请求,到后台都是对应一个线程。如果要做到每个终端对应一个日志的话,我们就要每一个线程都要带一个终端号,然后把输出的文件名改成其终端对应的。我们可以这样想一下,线程A调用log.info()-->调用FileAppender的对象FA--->FA.subAppend写日志。我们要改成:线程A调用log.info()-->调用FileAppender的对象FA--->FA.setFile(线程A对应终端的日志路径)-->FA.subAppend写日志。而现在的问题是我们用什么来存放线程A对应终端的日志路径,如何根据每个线程来找其对应的日志路径。这里我么可以想象一个对象,他里面有无数个线程,这个线程对应的路径,这里ThreadLocal了解一下。当然现在每个终端的日志都存放到一个文件里,如果再加上一个每个终端的日志都存放到一个文件里,第二天这个文件名字要改成前一天的,要如何做呢?