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

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

文章目錄

一 第三方程式不應僅限於會用

  關於日誌檔案備份的問題,我跟很多人都溝通過,但是結果很遺憾,絕大部分人對於Log4j的理解僅限於常規的使用,網路上能搜尋到的資源絕大部分也只是關於配置的介紹。

  其實這樣的現象並不少見,略微延展起來就變成了老生常談的一個問題:對於開源框架的使用,會用即可?我記得剛畢業那會就跟同學爭論過,時至今日我依然認為,會用是最起碼的要求,而理解其實現的原理是必要的,因為我們無法保證在使用任何第三方程式時,不會出現問題或者特殊的需求,如果我們對於其三方程式的理解僅限於使用上,那麼這些問題一旦出現——腦殼疼。

  當下的需求就是一個例子,如果不清楚Log4j是如何進行檔案備份的,那麼何談實現更為複雜的需求?這裡我不再重複網路上滿篇的底層實現邏輯,有興趣的朋友自行百度,簡而言之一句話:檔案備份邏輯在Appender的rollOver()方法裡。

二 同時按日期和檔案大小備份

  這是一個很常規的需求,然而Log4j對於這樣的需求似乎很難支援,要麼選擇RollingFileAppender按檔案大小進行備份,要麼選擇DailyRollingFileAppender按天進行檔案備份。

  前幾部分我簡單的介紹了按程式碼方式配置Log4j的思路,最後的一部分,讓我們來實現這個需求,通過複寫來實現日誌檔案同時按日期和檔案大小進行備份。

  實現思路是自己實現一個Appender,並重寫rollOver()方法,考慮到按檔案大小進行備份的實現,RollingFileAppender已經做到了,那麼我們對RollingFileAppender進行一個簡單的功能擴充套件即可。

package com.bubbling;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.apache.log4j.RollingFileAppender;
import org.apache.log4j.helpers.CountingQuietWriter; import org.apache.log4j.spi.LoggingEvent; /** * 派生自RollingFileAppender,所有具備按檔案大小備份的條件; 通過重寫其rollOver()方法,補充按日期備份的邏輯,即可實現。 * * @author 胡楠 * */ public class MyRollingFileAppender extends RollingFileAppender { /** * 按日期備份時的檔名包含的日期樣式 */ public static final String strDatePattern = "yyyyMMdd"; /** * 當前的日期,一旦日期發生變化,則觸發對應的備份邏輯 */ private String strDate = new SimpleDateFormat(strDatePattern).format(Calendar.getInstance().getTime()); /* * 注意:別對這個方法理解有問題,這裡僅僅是發生了檔案備份後的處理邏輯,不是判斷是否進行備份的邏輯 */ public void rollOver() { File target; File file; // 如果設定了最大檔案備份數量(每日的喲),那麼執行以下邏輯 if (maxBackupIndex > 0) { file = new File(getBackupFileName(maxBackupIndex)); if (file.exists()) { file.delete(); } // 這裡的邏輯略繞,從當前已備份到的最大序號開始,倒著遍歷,看不懂的跟蹤除錯下就好 for (int i = maxBackupIndex - 1; i >= 1; i--) { file = new File(getBackupFileName(i)); if (file.exists()) { target = new File(getBackupFileName(i + 1)); file.renameTo(target); } } target = new File(getBackupFileName(1)); this.closeFile(); file = new File(fileName); file.renameTo(target); } // 如果沒有設定過最大檔案備份數量,則給他21個億 else { for (int i = 1; i < Integer.MAX_VALUE; i++) { target = new File(getBackupFileName(i)); if (!target.exists()) { this.closeFile(); file = new File(fileName); file.renameTo(target); break; } } } // 以上僅處理了按檔案大小備份,下面處理日期發生變化後的處理 try { String strFile = this.getFile(); if (!(strDate.equals(strFile.substring(strFile.indexOf("-") + 1, strFile.lastIndexOf(".log"))))) { StringBuilder builder = new StringBuilder(strFile.substring(0, strFile.lastIndexOf("-") + 1)); builder.append(strDate); builder.append(".log"); this.fileName = builder.toString(); } this.setFile(fileName, false, bufferedIO, bufferSize); } catch (IOException e) { e.printStackTrace(); } } /* * 注意:這裡才是判斷是否備份的邏輯,依然重寫,使日期變更也觸發rollOver()方法 */ protected void subAppend(LoggingEvent event) { String strCurrentDate = new SimpleDateFormat(strDatePattern).format(Calendar.getInstance().getTime()); // 滿足以下所有條件才進行檔案備份 if (fileName != null && ((CountingQuietWriter) qw).getCount() >= maxFileSize || !strDate.equals(strCurrentDate)) { // 如果是因為日期變動導致的備份,需要調整當前時間 if (!strDate.equals(strCurrentDate)) { strDate = strCurrentDate; } this.rollOver(); } super.subAppend(event); } /** * 此方法用於自行改寫備份檔名,我喜歡***yyyyMMdd-00001.log這樣的格式,看著舒服 * * @param maxBackupIndex * @return */ private String getBackupFileName(int maxBackupIndex) { StringBuilder builder = new StringBuilder(); builder.append(fileName.substring(0, fileName.indexOf(".log"))); builder.append("-"); builder.append(String.format("%05d", maxBackupIndex)); builder.append(".log"); return builder.toString(); } }

三 調整ThreadLogger使用複寫的Appender

  實現了自定義的Appender之後,我們把ThreadLogger中建立Logger物件的方法重構下,不再使用原RollingFileAppender物件,而且因為加入了檔案大小和檔案備份數量等引數,LogUtil也需要調整。

public class ThreadLogger
{
	……
	private String filePath = "";
	private LogTarget logTarget = LogTarget.File;
	private LogLevel logLevel = LogLevel.Debug;
	// 補充新的屬性,檔案大小
	private String maxFileSize = "1MB";
	// 補充新的屬性,檔案備份數量
	private int maxBackupIndex = 10;
	……
	private Logger getFileLogger()
	{
		// 初始化一個RollingFileAppender物件,此處調整為自定義的Appender
		MyRollingFileAppender appender = new MyRollingFileAppender();
		// 設定檔案備份數量
		appender.setMaxBackupIndex(maxBackupIndex);
		// 設定檔案大小
		appender.setMaxFileSize(maxFileSize);
		// 設定Appender物件名,若不設定則執行時報錯
		appender.setName(filePath);
		……
	}
	……
}

四 簡單測試下

  最後簡單驗證下,因為沒有仔細的完善所有執行邏輯,僅僅為了舉個例子,所以執行結果大致符合預期就算是大功告成,各位看官有興趣可以自行對邏輯進行處理。

package com.bubbling;

import com.bubbling.LogUtil.LogLevel;

public class LogUtilTest
{
	public static void main(String[] args)
	{
		long id = Thread.currentThread().getId();
		LogUtil.setFilePath("D:\\測試\\" + id + ".log");
		LogUtil.setLogLevel(LogLevel.Debug);

		for (int i = 0; i < 5000000; i++)
		{
			LogUtil.debug("測試執行緒:" + id + "debug輸出");
			LogUtil.info("測試執行緒:" + id + "info輸出");
			LogUtil.warn("測試執行緒:" + id + "warn輸出");
			LogUtil.error("測試執行緒:" + id + "error輸出");
		}
	}
}

在這裡插入圖片描述

五 結語

  從一開始的需求,到逐步的實現,這其中並沒有什麼特別高深莫測的東西,我始終主張能能最簡單的實現,來滿足當下的需求即可,直到對效能有了更高的要求,再去仔細分析優化空間。

  但這並不是說下手寫程式碼的時候不需要考慮效能問題,嚴謹的邏輯(一般就好)是每一個程式設計師的基礎,查閱原始碼、文件及跟蹤除錯是基本功。

  不要總是想著複製貼上,如果你對技能提升有渴望,請多花些心思去思考設計方案,而不是急著拷貝別人的程式碼。

  程式設計師應該多花時間在設計上,實現上消耗的時間應該是最少的,程式碼的編寫也不是一步到位的,隨著實現的深入,逐步重構,完善程式碼層級結構,設計模式也就漸漸顯現出來,合理的設計也對後期的維護提供了堅實的基礎。雖說道理如此簡單,但是真的能沉下心來這樣做的,怕是十不足一。