寫了幾年的Java,最弱的部份是Log,現在看來還是差不多。最近在改寫Framework時才對Log4J的用法稍有體會,不然也是渾渾噩噩看別人怎麼配置,自己也跟著配置。
2006年曾設計Logger Proxy委託天津同事開發,其目的有二:一是預設按class full name產生log檔,二是避開像JBoss本身的log4j.properties影響。這樣做效益有好有壞,壞就壞在Ap Server會記一份,自己又用Proxy記了一份,而且不只如此,移到不同的AP Server或Web Server,因WAR或EAR的目錄結構不同,連帶的Proxy跟著改寫,無法通用。
後來在Google找有無可依不同的class name為檔名產出不同的log檔,是有接近的solution,但不完全滿足其需求。也間接了解了log4j.properties真正的配置方式:
log4j.rootLogger = INFO, stdout
log4j.category.com.opensymphony=DEBUG
log4j.category.org.apache.struts2=INFO, rolling
原來Log4J是由Logger、Appender和Layout決定。後兩者設定方式還算清楚,而Logger其實可以模擬做到依不同class或package產出不同的log file。上例的log4j.rootLogger、log4j.category.package.class就是一種Logger,rootLogger是super Logger可以記錄所有class的Log,而log4j.category.後面是接package甚至是class full name。
等於(=)後面以逗號分隔,第一個參數是Level,第二個以後的參數均是Appender,以log4j.category.com.opensymphony為例,雖然rootLogger的Level是INFO,但com.opensymphony這package內所有的class和子class均是DEBUG Level,除非還有更detail的package另外設Level,則以最接近的package為優先。第三例則表示還可參照rolling這個appender。
但為何不能滿足在執行期動態決定產出的log file name,看FileAppender如下:
log4j.appender.rolling=org.apache.log4j.RollingFileAppender
log4j.appender.rolling.File=c:/logs/test
log4j.appender.rolling.MaxFileSize=100KB
log4j.appender.rolling.MaxBackupIndex=1
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.rolling.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%F:%L] : %m%n
上例的粗體字就事先把file給產出,若是目錄就會報錯,爾後在Google找到解法是去繼承RollingFileAppender。
(log4j.properties設定)
log4j.appender.dynarolling=com.xyz.util.DynaRollingAppender
log4j.appender.dynarolling.File=C:/logs/test
log4j.appender.dynarolling.MaxFileSize=100KB
log4j.appender.dynarolling.MaxBackupIndex=1
log4j.appender.dynarolling.layout=org.apache.log4j.PatternLayout
log4j.appender.dynarolling.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%F:%L] : %m%n
(source code部份)
public class DynaRollingAppender extends RollingFileAppender {
private static String oldFileName = null;
private static String oldPath = null;
public void generateLogFile(LocationInfo info) {
if (oldPath == null) {
oldPath = this.fileName.substring(0, this.fileName.lastIndexOf("test"));
}
String fileName1 = oldPath;
fileName1 += info.getClassName() + ".log";
this.logger1.info(fileName1 + "; " + this.fileName);
oldFileName = fileName1;
this.setFile(fileName1);
this.activateOptions();
}
@Override
protected void subAppend(LoggingEvent event) {
String className = event.getLocationInformation().getClassName();
if (oldFileName != className) {
this.generateLogFile(event.getLocationInformation());
}
super.subAppend(event);
}
}
上例中,事先overide subAppend,換掉setFile內容,但是initial時在換掉之前就做了一次setFile,所以File參數必須是檔案,目的是取得其目錄,然後再換成class full name + ".log"格式做setFile,再activateOptions才會生效,而最後一行super.subAppend則將內容寫入新的log file。
從程式就看到LoggingEvent含有LocationInfo類別,正是log4j使用reflection取得call它的class name, file name和line number等資訊。但這樣做有沒有效能的issue未知,之前第一次去吉隆坡出差,就曾因Logger Proxy不穩掛過。