| 需求 | 挑戰 | 解決方案 |
|---|---|---|
| 高效能 | 頻繁磁碟 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;
// 寫入介面:只追加到緩衝區,極快返回
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;
}
// 初始化建立共享記憶體
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 即可動態調整,無需重啟主程式。
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;
}
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
#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 解決編碼問題。雖細節有待打磨,但架構思路具備通用參考價值。