首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

高效能 C++ 日誌系統設計:緩衝、分級、輪轉、執行緒安全完整實作

     編輯:關於VC++

商用級日誌系統四大核心需求

需求挑戰解決方案
高效能頻繁磁碟 I/O 拖慢主執行緒記憶體緩衝區批次寫入 + 專用寫入執行緒定時刷新
執行期分級動態調整除錯資訊上線後需關閉,問題排查時需開啟共享記憶體儲存分級旗標,寫入執行緒輪詢熱更新
磁碟空間安全長期運行日誌無限增長佔滿磁碟多檔案循環覆寫 (Round-robin),單檔大小上限
Unicode/中文支援文字模式寫入中文亂碼二進位模式 + UTF-16 LE BOM (FF FE) 標記

核心架構:CSuperLog 單例模式

class CSuperLog
{
public:
    enum Level { DEBUG=0, RUN=1, ERROR=2 };
    static int WriteLog(const CString& msg, Level lv = RUN, bool forceFlush = false);
    static void SetLogLevel(Level lv); // 對外調整介面 (實際寫共享記憶體)

private:
    CSuperLog(); // 建構子啟動寫入執行緒
    ~CSuperLog(); // 析構通知執行緒退出
    void WriteLogToFile(); // 實際寫檔
    CString GetCurTimeStr(); // 取得時間戳記
    enum Status OpenLogFile(); // 輪轉邏輯

    // 緩衝區
    CString m_strWriteStrInfo; // 文字日誌緩衝
    BYTE*   m_pBinBuffer;      // 二進位日誌緩衝 (略)
    // 同步
    CRITICAL_SECTION m_csWriteLog;
    // 執行緒
    HANDLE m_hThread; bool m_bRun;
    // 檔案輪轉
    CStdioFile* m_pFile; int m_iCurLogFileSeq;
    // 共享記憶體
    HANDLE m_hMapLogFile; LPTSTR m_psMapAddr;
};
// 全域單例
CSuperLog g_Log;

關鍵實作細節

1. 非同步批次寫入 + 定時強制刷新

// 寫入介面:只追加到緩衝區,極快返回
int CSuperLog::WriteLog(const CString& strLog, Level enLevel, bool bForce)
{
    if (enLevel < m_iLogLevel) return -1; // 分級過濾

    EnterCriticalSection(&m_csWriteLog);
    if (!strLog.IsEmpty()) {
        m_strWriteStrInfo += GetCurTimeStr();
        if (enLevel == ERROR) m_strWriteStrInfo += _T("Error! ");
        m_strWriteStrInfo += strLog + _T("\r\n");
    }
    // 達到閾值或強制刷新
    if (bForce || m_strWriteStrInfo.GetLength() > MAX_STR_LOG_INFO_LEN)
        WriteLogToFile(); // 內部會 LeaveCriticalSection
    else
        LeaveCriticalSection(&m_csWriteLog);
    return 0;
}

// 專用執行緒:每 300ms 檢查,每 3 秒 (10 次) 強制刷新
unsigned __stdcall CSuperLog::LogProcStart(void*)
{
    int nCount = 1;
    while (m_bRun) {
        Sleep(300);
        if (++nCount % 10 == 0)
            WriteLog(_T(""), ERROR, true); // 空訊息觸發強制刷新
    }
    return 0;
}

2. 共享記憶體實現跨進程/外部工具動態調整分級

// 初始化建立共享記憶體
m_hMapLogFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, _T("SuperLogShareMem"));
m_psMapAddr = (LPTSTR)MapViewOfFile(m_hMapLogFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
_tcscpy_s(m_psMapAddr, 1024, g_pszLogLevel[m_iLogLevel]); // 寫入預設等級

// 寫入執行緒輪詢檢查
if (_tcscmp(m_psMapAddr, g_pszLogLevel[m_iLogLevel]) != 0) {
    m_iLogLevel = ParseLevelFromString(m_psMapAddr); // 熱更新
}

外部工具只需開啟同名共享記憶體寫入 DEBUG/RUN/ERROR 即可動態調整,無需重啟主程式。

3. 三檔案循環覆寫 (Round-Robin) 防止磁碟爆滿

Status CSuperLog::OpenLogFile()
{
    EnterCriticalSection(&m_csWriteLog);
    for (int i = 0; i < MAX_LOG_FILE_COUNT; ++i) { // 最多嘗試 3 次
        if (!m_pFile) { m_pFile = new CStdioFile; /* ... */ }

        BOOL bRet = m_pFile->Open(
            g_pszLogFileName[(m_iCurLogFileSeq++) % MAX_LOG_FILE_COUNT],
            CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone | CFile::modeNoTruncate);
        if (!bRet) { /* 錯誤處理 */ continue; }

        WriteUnicodeHeadToFile(m_pFile); // 空檔寫入 BOM

        if (m_pFile->GetLength() > MAX_LOG_FILE_LEN) { // 檔案過大
            m_pFile->Close();
            // 判斷是否已輪轉一圈:若是,下一個檔案用 modeCreate 截斷;否則用 modeNoTruncate 保留
            // ...
            continue; // 迴圈嘗試下一個檔案
        }
        break; // 找到可用檔案
    }
    m_pFile->SeekToEnd();
    LeaveCriticalSection(&m_csWriteLog);
    return RUN;
}

4. UTF-16 LE BOM 解決中文亂碼

int CSuperLog::WriteUnicodeHeadToFile(CFile* pFile)
{
    if (pFile->GetLength() == 0) {
        pFile->Write("\xFF\xFE", 2); // UTF-16 LE BOM
        if (m_enStatus == RUN) pFile->WriteString(WELCOME_LOG_INFO);
        pFile->Flush();
    }
    return 0;
}
// 開檔必須指定 CFile::typeBinary

5. 靜態介面宏簡化呼叫端

#define WRITE_LOG   CSuperLog::WriteLog
#define LOG_DEBUG   CSuperLog::DEBUG
#define LOG_RUN     CSuperLog::RUN
#define LOG_ERROR   CSuperLog::ERROR

// 使用範例
#include "SuperLog.h"
WRITE_LOG(_T("簡訊發送失敗,重試一次。"), LOG_ERROR);

已知問題與改進方向

  • 析構順序陷阱:GetCurTimeStr 曾用 static CString 返回參考,導致全域物件析構時存取已銷毀物件 Crash。已改為全域變數 g_strTime 規避。
  • 缺少 printf 風格格式化介面 (WriteLogFmt) 及二進位資料區塊寫入介面。
  • 共享記憶體無名稱空間隔離,多實例衝突風險。
  • 建議現代化:使用 std::ofstream、std::mutex、std::condition_variable、spdlog 等成熟庫替代。

結語

此實作展示了商用級日誌系統的核心骨架:生產者-消費者模式分離關鍵路徑、共享記憶體實現熱配置、檔案輪轉保障資源安全、BOM 解決編碼問題。雖細節有待打磨,但架構思路具備通用參考價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved