專案需求:將所有伺服器的日誌集中儲存至單一檔案伺服器。經調研選擇 Log4j 的 SocketAppender 實現網路日誌傳輸。網路資源多為片段資訊,且與官方範例 examples/lf5/UsingSocketAppenders 大同小異,最終決定自行深入研究實作。
啟動 Log4j 內建的 Socket 伺服器:
java org.apache.log4j.net.SocketServer [監聽埠] [伺服器設定檔] [客戶端設定檔目錄]
第三參數指定客戶端專屬設定檔目錄,初期可用 . 代替。
伺服器設定檔範例 (socketserver.properties):
log4j.rootLogger=DEBUG,A3
log4j.category.org.apache.log4j.net=INFO
# LF5 圖形化監控終端
log4j.appender.A1=org.apache.log4j.lf5.LF5Appender
log4j.appender.A1.MaxNumberOfRecords=700
# 日期滾動檔案
log4j.appender.A4=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A4.file=server.log
log4j.appender.A4.DatePattern='.'yyyyMMdd
log4j.appender.A4.layout=org.apache.log4j.PatternLayout
log4j.appender.A4.layout.ConversionPattern=\n\n[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
# 大小限制滾動檔案
log4j.appender.A3=org.apache.log4j.RollingFileAppender
log4j.appender.A3.file=server2.log
log4j.appender.A3.MaxFileSize=1024KB
log4j.appender.A3.MaxBackupIndex=999
log4j.appender.A3.layout=org.apache.log4j.PatternLayout
log4j.appender.A3.layout.ConversionPattern=\n\n[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
A1 啟動 LF5 監控介面,A3 為大小限制滾動,A4 為日期滾動。預設所有客戶端日誌寫入同一檔案,管理不便。
log4j.rootCategory=,SOCKET
log4j.additivity.org.apache=true
log4j.appender.SOCKET=org.apache.log4j.net.SocketAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=1978
log4j.appender.SOCKET.LocationInfo=true
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.SOCKET.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%t%m%n
# 本地備份
log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A2.file=client.log
log4j.appender.A2.DatePattern='.'yyyy-MM-dd
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
Log4j 1.2.11 版 SocketServer.java 存在缺陷:解析客戶端 IP 時錯誤使用 / 分隔符。實際 InetAddress.toString() 格式為 hostname/IP,需取 / 後段作為 IP。
修正 SocketServer.java 關鍵程式碼:
LoggerRepository configureHierarchy(InetAddress inetAddress) {
String s = inetAddress.toString(); // 格式: hostname/192.168.1.1
int i = s.indexOf("/");
if (i == -1) {
return genericHierarchy();
} else {
// 原始碼錯誤: String key = s.substring(0, i);
// 修正為取 IP 部分:
String key = s.substring(i + 1);
File configFile = new File(dir, key + CONFIG_FILE_EXT);
// ... 後續載入客戶端專屬設定檔
}
}
同步修正 SocketNode.java 第 54 行,增加空值檢查防止串流異常:
InputStream is = socket.getInputStream();
if (is != null) {
ois = new ObjectInputStream(new BufferedInputStream(is));
}
編譯修正後的類別並打包替換原版。
於伺服器端 [客戶端設定檔目錄] 建立以客戶端 IP 命名的 .lcf 檔案,例如 192.168.0.126.lcf:
log4j.rootCategory=,A4
log4j.appender.A4=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A4.file=127.0.0.1.log
log4j.appender.A4.DatePattern='.'yyyyMMdd
log4j.appender.A4.layout=org.apache.log4j.PatternLayout
log4j.appender.A4.layout.ConversionPattern=\n\n[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
客戶端設定無需變更。啟動後:伺服器日誌寫入 server.log,有專屬設定檔的客戶端日誌寫入對應檔案,無設定檔者仍寫入 server.log。
將修正後的 SocketServer.java 與 SocketNode.java 獨立建構為專案,移除 package 宣告即可整合至專案建構流程,避免未來升級 Log4j 版本覆蓋修正。