程式碼方式配置Log4j並實現執行緒級日誌管理 第二部分
文章目錄
一 設計類結構
第一部分說了兩件事兒:
- 如何根據配置檔案分析有用的資訊以便對底層結構進行挖掘
- 如何檢視原始碼的類結構以便調研需求完成後續開發設計
在瞭解我們的終極目標之後,不忙寫程式碼,之前的工作僅僅是調研,為的是確認需求能否實現,以及實現過程中可能觸及的技術風險。接下來我們需要根據需求及之前所做的調研,對我們即將要開發的功能做一個初步設計,將實現的框架搭建起來。
我習慣先定義一個類,比如說LogUtil.java:
package com.bubbling;
/**
* 1.實現程式碼方式配置Log4j <br>
* 2.實現執行緒級日誌物件管理 <br>
* 3.實現日誌的非同步輸出模式 <br>
* 4.實現按日誌檔案大小及日期進行檔案備份
*
* @author 胡楠
*
*/
public class LogUtil
{
}
這個類將作為一個日誌工具類使用,對外提供相關方法供呼叫者進行日誌屬性配置及日誌內容輸出。
二 成員設計
接下來是成員設計,成員設計可以說是最重要的,它直接影響類的整體設計思路。先說日誌配置中有哪些常見的、使用率高的、必要的屬性:
- 檔名
- 檔案儲存路徑
- 單一檔案最大儲存容量
- 備份檔案最大保留數量
- 是否開啟非同步模式
- 日誌輸出目的地:檔案/控制檯/其他
因LogUtil是一個工具類,其成員設計初步擬定為類級成員,作為可被外部配置的公有資訊,初步的成員設計如下:
public class LogUtil
{
private static String fileName;
private static String filePath;
private static String fileSize;
private static int maxBackupIndex;
private static boolean isAsynchronous;
private static int logTarget;
public static void setFileName(String value)
{
fileName = value;
}
public static String getFileName()
{
return fileName;
}
……
}
因為需要明確日誌輸出的目的地,那麼我們需要對目的地的定義進行規範設計,像這種對同一型別且僅區分內容的屬性設計,通常使用靜態不可變成員,或者列舉型別來定義,這裡我使用列舉型別來定義它,這樣會使得封裝性更強:
public class LogUtil
{
private static String fileName;
private static String filePath;
private static String fileSize;
private static int maxBackupIndex;
private static boolean isAsynchronous;
private static LogTarget logTarget;
public enum LogTarget
{
Console, File, Socket
}
public static void setFileName(String value)
{
fileName = value;
}
public static String getFileName()
{
return fileName;
}
……
}
三 方法設計
再來就是方法設計,它體現了一個類的內在或外向的行為方式,是與外界進行互動的視窗。按需求來說,我們需要一個方法給我們返回一個Logger物件,並使用Logger物件進行日誌輸出,需要明確的是,返回的Logger其屬性配置因該從LogUtil的類級成員處取得,並且由方法引數來確定日誌的輸出目標,不同輸出目標的Logger其屬性配置亦不相同:
public static Logger getLogger(LogTarget target)
{
Logger logger = null;
if (LogTarget.Console == target)
{
logger = getConsoleLogger();
}
else if (LogTarget.File == target)
{
logger = getFileLogger();
}
else if (LogTarget.Socket == target)
{
logger = getSocketLogger();
}
return logger;
}
private static Logger getSocketLogger()
{
// TODO Auto-generated method stub
return null;
}
……
四 實現Logger物件例項化方法
已經定義好了大體框架,接下來就是實現各種get***Logger()方法了,這裡以輸出到檔案使用RollingFileAppender為例,做一個簡單的實現樣例,其他Logger物件的實現大同小異,僅在屬性上有所區別:
public static Logger getLogger(LogTarget target, String loggerName)
{
Logger logger = null;
if (LogTarget.Console == target)
{
logger = getConsoleLogger(loggerName);
}
else if (LogTarget.File == target)
{
logger = getFileLogger(loggerName);
}
else if (LogTarget.Socket == target)
{
logger = getSocketLogger(loggerName);
}
return logger;
}
private static Logger getSocketLogger(String loggerName)
{
// TODO Auto-generated method stub
return null;
}
private static Logger getFileLogger(String loggerName)
{
// 初始化一個RollingFileAppender物件
RollingFileAppender appender = new RollingFileAppender();
// 設定日誌內容追加到檔案內容末尾
appender.setAppend(true);
// 設定日誌檔案的儲存位置
appender.setFile(getFilePath() + File.separator + getFileName());
// 不開啟非同步模式
appender.setBufferedIO(false);
// 僅開啟非同步模式,快取大小才有意義
appender.setBufferSize(0);
// 下面的方法是對上面四個屬性設定的一個封裝
// appender.setFile("", true, false, 0);
// 需要啟用Appender物件的配置,這樣屬性設定才會生效
appender.activateOptions();
// 注意這裡需要是指Logger物件名,後續設計會對此處進行重構,目前以呼叫類的SimpleName作為Logger物件的name屬性值
Logger logger = Logger.getLogger(loggerName);
// 為Logger物件新增Appender成員
logger.addAppender(appender);
// 設定Logger物件不繼承上層節點屬性配置,僅向檔案中輸出內容
logger.setAdditivity(false);
// 為什麼設定日誌輸出級別為Trace,因為後續我們需要通過LogUtil公開的方法對日誌級別進行動態控制,所以此處暫時設定為最低級別
logger.setLevel(Level.TRACE);
return logger;
}
……
此時已經算是成功了一半了,應用程式通過LogUtil的getLogger()方法會得到一個可用的Logger物件,呼叫Logger物件提供的日誌輸出方法即可實現日誌的輸出。
那麼有人問我,我是怎麼知道Logger、Appender有那些屬性需要設定的?答:官方文件/原始碼查閱。這裡我不貼官方文件了,用Eclipse給大家看看我是怎麼知道這些屬性設定方法的:
如果需要檢視更詳細的說明,跟到原始碼的方法定義處,看看人家設計師當初是怎麼對方法進行設計的即可,這依然是通用的、常規的操作(看原始碼很牛逼?瞎說八道)。
最後再總結下,目前尚未完成的設計:
- 執行緒級日誌物件控制
- 按處理執行緒動態設定日誌輸出界別
- 日誌的非同步輸出模式
這些內容會在後續的部落格裡逐步介紹。可能會有人覺得我很傻逼,寫的羅裡吧嗦,但請理解我的初衷,我只是想分享我當時做設計的時候,是如何一步步完成需求的,或許對於高手而言不值一提,但我更希望這裡的一些思路能夠對某些朋友有所幫助。