1. 程式人生 > >程式碼方式配置Log4j並實現執行緒級日誌管理 第二部分

程式碼方式配置Log4j並實現執行緒級日誌管理 第二部分

文章目錄

一 設計類結構

  第一部分說了兩件事兒:

  1. 如何根據配置檔案分析有用的資訊以便對底層結構進行挖掘
  2. 如何檢視原始碼的類結構以便調研需求完成後續開發設計

  在瞭解我們的終極目標之後,不忙寫程式碼,之前的工作僅僅是調研,為的是確認需求能否實現,以及實現過程中可能觸及的技術風險。接下來我們需要根據需求及之前所做的調研,對我們即將要開發的功能做一個初步設計,將實現的框架搭建起來。

  我習慣先定義一個類,比如說LogUtil.java:

package com.bubbling;

/**
 * 1.實現程式碼方式配置Log4j <br>
 * 2.實現執行緒級日誌物件管理 <br>
 * 3.實現日誌的非同步輸出模式 <br>
 * 4.實現按日誌檔案大小及日期進行檔案備份
 * 
 * @author 胡楠
 *
 */
public class LogUtil
{
	
}

  這個類將作為一個日誌工具類使用,對外提供相關方法供呼叫者進行日誌屬性配置及日誌內容輸出。

二 成員設計

  接下來是成員設計,成員設計可以說是最重要的,它直接影響類的整體設計思路。先說日誌配置中有哪些常見的、使用率高的、必要的屬性:

  1. 檔名
  2. 檔案儲存路徑
  3. 單一檔案最大儲存容量
  4. 備份檔案最大保留數量
  5. 是否開啟非同步模式
  6. 日誌輸出目的地:檔案/控制檯/其他

  因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給大家看看我是怎麼知道這些屬性設定方法的:

Appender屬性設定方法概覽

  如果需要檢視更詳細的說明,跟到原始碼的方法定義處,看看人家設計師當初是怎麼對方法進行設計的即可,這依然是通用的、常規的操作(看原始碼很牛逼?瞎說八道)。

  最後再總結下,目前尚未完成的設計:

  1. 執行緒級日誌物件控制
  2. 按處理執行緒動態設定日誌輸出界別
  3. 日誌的非同步輸出模式

  這些內容會在後續的部落格裡逐步介紹。可能會有人覺得我很傻逼,寫的羅裡吧嗦,但請理解我的初衷,我只是想分享我當時做設計的時候,是如何一步步完成需求的,或許對於高手而言不值一提,但我更希望這裡的一些思路能夠對某些朋友有所幫助。