(轉)日交易額百億級交易系統的超輕量日誌實現
逛園子的時候偶然發現了《日交易額百億級交易系統的超輕量日誌實現》,感覺博主的思路很強,可惜是一個JAVA版本,於是我將它翻譯為C#。
開發環境VS2015+.net framework4. 原文地址,http://www.cnblogs.com/cyfonly/p/6139049.html
因為JAVA和C#語言的近似性,很多直接內容直接從原文COPY的,博主勿怪。。
使用方式:(直接Copy原文)
/獲取單例 FLogger logger = FLogger.getInstance(); //簡便api,只需指定內容 logger.info("Here is your message..."); //指定日誌級別和內容,文件名自動映射 logger.writeLog(Constant.INFO, "Here is your customized level message..."); //指定日誌輸出文件名、日誌級別和內容 logger.writeLog("error", Constant.ERROR, "Here is your customized log file and level message...");
配置項如下:(直接Copy原文)
########## 公共環境配置 ########## # 字符集 CHARSET_NAME= UTF-8 ########## 日誌信息配置 ########## # 日誌級別 0:調試信息 1:普通信息 2:警告信息 3:錯誤信息 4:嚴重錯誤信息 LOG_LEVEL = 0,1,2,3,4 # 日誌文件存放路徑 LOG_PATH =/log (此處跟原文不同哦) # 日誌寫入文件的間隔時間(默認為1000毫秒) WRITE_LOG_INV_TIME = 1000 # 單個日誌文件的大小(默認為10M) SINGLE_LOG_FILE_SIZE = 10485760 # 單個日誌文件緩存的大小(默認為10KB) SINGLE_LOG_CACHE_SIZE = 10240
打印結果
info.log [INFO] 2016-12-06 21:07:32:840 [main] Here is your message... warn.log [WARN] 2016-12-06 21:07:32:842 [main] Here is your customized level message... error.log [ERROR] 2016-12-06 21:07:32:842 [main] Here is your customized log file and level message... 從上面可以看到,你可以很清楚的分辨出日誌的級別、時間和內容等信息。到這其實很明了了,日誌由以下幾個元素組成: [日誌級別] 精確到毫秒的時間 [當前線程名] 日誌內容
源碼解析
雙緩沖隊列
FLogger 在內部采用雙緩沖隊列,那何為雙緩沖隊列呢?它的作用又是什麽呢?
FLogger 為每個日誌文件維護了一個內部對象 LogFileItem ,定義如下:
public class LogFileItem { /** 不包括路徑,不帶擴展名的日誌文件名稱 如:MsgInner */ public String logFileName = ""; /** 包括路徑的完整日誌名稱 */ public String fullLogFileName = ""; /** 當前日誌文件大小 */ public long currLogSize = 0; /** 當前正在使用的日誌緩存 */ public char currLogBuff = ‘A‘; /** 日誌緩存列表A */ public List<StringBuilder> alLogBufA = new List<StringBuilder>(); /** 日誌緩存列表B */ public List<StringBuilder> alLogBufB = new List<StringBuilder>(); /** 下次日誌輸出到文件時間 */ public long nextWriteTime = 1000; /** 上次寫入時的日期 */ public String lastPCDate = ""; /** 當前已緩存大小 */ public long currCacheSize = 10240; }
在每次寫日誌時,日誌內容作為一個 StringBuffer 添加到當前正在使用的 ArrayList<StringBuffer> 中,另一個則空閑。當內存中的日誌輸出到磁盤文件時,會將當前使用的 ArrayList<StringBuffer> 與空閑的 ArrayList<StringBuffer> 進行角色交換,交換後之前空閑的 ArrayList<StringBuffer> 將接收日誌內容,而之前擁有日誌內容的 ArrayList<StringBuffer> 則用來輸出日誌到磁盤文件。這樣就可以避免每次刷盤時影響日誌內容的接收(即所謂的 stop-the-world 效應)及多線程問題。
日誌接收代碼: 此處用lock代替JAVA的synchronized
lock (lfi) { if (lfi.currLogBuff == ‘A‘) { lfi.alLogBufA.Add(logMsg); } else { lfi.alLogBufB.Add(logMsg); } lfi.currCacheSize +=System.Text.Encoding.UTF8.GetBytes(logMsg.ToString()).Length; }
日誌刷盤代碼:
List<StringBuilder> alWrtLog = null; lock (lfi) { if (lfi.currLogBuff == ‘A‘) { alWrtLog = lfi.alLogBufA; lfi.currLogBuff = ‘B‘; } else { alWrtLog = lfi.alLogBufB; lfi.currLogBuff = ‘A‘; } lfi.currCacheSize = 0; } //創建日誌文件 createLogFile(lfi); //輸出日誌 int iWriteSize = writeToFile(lfi.fullLogFileName, alWrtLog); lfi.currLogSize += iWriteSize;
刷盤機制: 暫不支持退出強制觸發
刷盤時間間隔觸發 配置項如下: # 日誌寫入文件的間隔時間(默認為1000毫秒) WRITE_LOG_INV_TIME = 1000 當距上次刷盤時間超過間隔時間,將執行內存日誌刷盤。 內存緩沖大小觸發 配置項如下: # 單個日誌文件緩存的大小(默認為10KB) SINGLE_LOG_CACHE_SIZE = 10240 當內存緩沖隊列的大小超過配置大小時,將執行內存日誌刷盤。
多 RollingFile 機制
//創建日誌文件 private void createLogFile(LogFileItem lfi) { //當前系統日期 String currPCDate = TimeUtil.getPCDate(‘-‘); //判斷日誌root路徑是否存在,不存在則先創建 if (Directory.Exists(ConstantCLS.CFG_LOG_PATH)) { if (Directory.Exists(ConstantCLS.CFG_LOG_PATH)) { Directory.CreateDirectory(ConstantCLS.CFG_LOG_PATH); } } //如果超過單個文件大小,則拆分文件 if (lfi.fullLogFileName != null && lfi.fullLogFileName.Length > 0 && lfi.currLogSize >= LogManager.SINGLE_LOG_FILE_SIZE) { if (File.Exists(lfi.fullLogFileName)) { String newFileName = ConstantCLS.CFG_LOG_PATH + "/" + lfi.lastPCDate + "/" + lfi.logFileName + "_" + TimeUtil.getPCDate() + "_" + TimeUtil.getCurrTime() + ".log"; try { File.Move(lfi.fullLogFileName, newFileName); MessageBox("日誌已自動備份為 " + newFileName + "成功!"); lfi.fullLogFileName = ""; lfi.currLogSize = 0; } catch (Exception ex) { MessageBox("日誌已自動備份為 " + newFileName + "失敗!"+ex.ToString()); } } } //創建文件 if (lfi.fullLogFileName == null || lfi.fullLogFileName.Length <= 0 || !lfi.lastPCDate.Equals(currPCDate)) { String sDir = ConstantCLS.CFG_LOG_PATH + "/" + currPCDate; if (!Directory.Exists(sDir)) { DirectoryInfo dirInfo = Directory.CreateDirectory(sDir); } lfi.fullLogFileName = sDir + "/" + lfi.logFileName + ".log"; lfi.lastPCDate = currPCDate; if (File.Exists(lfi.fullLogFileName)) { FileInfo fi = new FileInfo(lfi.fullLogFileName); lfi.currLogSize = fi.Length; } else { File.Create(lfi.fullLogFileName); lfi.currLogSize = 0; } } }
熱加載
FLogger 支持熱加載,FLogger 內部並沒有采用事件驅動方式(即新增、修改和刪除配置文件時產生相關事件通知 FLogger 實時熱加載),而是以固定頻率的方式進行熱加載,具體實現就是每執行完100次刷盤後才進行熱加載(頻率可調),關鍵代碼如下:
public void run() { int i = 0; while (bIsRun) { try { //輸出到文件 flush(false); //重新獲取日誌級別 if (i++ % 100 == 0) { ConstantCLS.CFG_LOG_LEVEL = CommUtil.getConfigByString("LOG_LEVEL", "0,1,2,3,4"); i = 1; } } catch (Exception ex) { MessageBox("開啟日誌服務錯誤..." + ex.ToString()); } } }
想當然遇到的坑:
loadxml:在C#中加載的是string類型的XML文件,加載文件需要使用load
streamwrite:在C#中writeLine為Append方式時,傳入的參數是string而不是filestream
synchronized:JAVA關鍵字,C#中科用lock代替
e.printstacktrace:JAVA在命令行打印信息,C#中可用Console.WriteLine(System.Environment.StackTrace);
修改後源碼下載
(轉)日交易額百億級交易系統的超輕量日誌實現