實際背景
客戶有客戶端多台,每個客戶端有自己的唯一編號。輸出的日志要根據每個客戶端的編號生成,例如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了解一下。當然現在每個終端的日志都存放到一個文件里,如果再加上一個每個終端的日志都存放到一個文件里,第二天這個文件名字要改成前一天的,要如何做呢?