1. 程式人生 > >(轉)日交易額百億級交易系統的超輕量日誌實現

(轉)日交易額百億級交易系統的超輕量日誌實現

加載文件 all 觸發 lock Coding world ole span pub

逛園子的時候偶然發現了《日交易額百億級交易系統的超輕量日誌實現》,感覺博主的思路很強,可惜是一個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

打印結果

:(直接Copy原文)

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);

修改後源碼下載

(轉)日交易額百億級交易系統的超輕量日誌實現