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

C++ Windows日志系統實戰:分級、按日期分目錄、自動分卷封裝庫

     編輯:關於C

企業級應用離不開可靠的日志系統。本文實現一個分級別、按日期分目錄、自動分卷、線程安全的C++ Windows日志庫,代碼結構清晰,適合直接集成或二次開發。

設計目標與特性

核心數據結構


#define MAX_LOGTEXT_LEN 2048
#define MAX_FILE_PATH 255
#define MAX_LOG_FILE_SIZE (512 * 1024)

#define LOG_TYPE_INFO 0
#define LOG_TYPE_ERROR 1
#define LOG_TYPE_SYSTEM 2

#pragma pack(push, 1)
typedef struct tagLOG_DATA {
    char strDate[11];      // YYYY-MM-DD
    char strTime[9];       // HH:MM:SS
    unsigned int iType;    // 日志類型
    char strText[MAX_LOGTEXT_LEN]; // 日志內容
} LOG_DATA, *LPLOG_DATA;
#pragma pack(pop)

const char g_LogRootPath[] = "C://My_APPLOG"; // 根目錄

關鍵函數流程

1. 入口:Write_Log(變長參數格式化)


void Write_Log(unsigned int uiLogType, char *pstrFmt, ...) {
#if _LOG_WRITE_STATE_
    LOG_DATA data;
    memset(&data, 0, sizeof(data));
    
    // 格式化內容
    va_list v1;
    va_start(v1, pstrFmt);
    _vsnprintf(data.strText, MAX_LOGTEXT_LEN, pstrFmt, v1);
    va_end(v1);
    
    data.iType = uiLogType;
    
    // 獲取當前時間
    time_t curTime = time(NULL);
    struct tm *mt = localtime(&curTime);
    strftime(data.strDate, sizeof(data.strDate), "%Y-%m-%d", mt);
    strftime(data.strTime, sizeof(data.strTime), "%H:%M:%S", mt);
    
    Write_Log_Text(&data);
#endif
}

2. 路徑構建:GetLogPath → GetLogFileName


// 構建:根目錄/YYYYMMDD/
int GetLogPath(char *pStrPath) {
    time_t curTime = time(NULL);
    struct tm *mt = localtime(&curTime);
    sprintf(pStrPath, "%s//%d%02d%02d", g_LogRootPath, 
            mt->tm_year+1900, mt->tm_mon+1, mt->tm_mday);
    return Create_LogDir(pStrPath); // 遞歸創建多級目錄
}

// 根據類型生成文件名:app_info.log / app_error.log / app_system.log
int GetLogFileName(int iLogType, const char *pStrPath, char *pStrName) {
    char szLogName[MAX_FILE_PATH];
    switch (iLogType) {
        case LOG_TYPE_INFO:    sprintf(szLogName, "%s//app_info.log", pStrPath); break;
        case LOG_TYPE_ERROR:   sprintf(szLogName, "%s//app_error.log", pStrPath); break;
        case LOG_TYPE_SYSTEM:  sprintf(szLogName, "%s//app_system.log", pStrPath); break;
    }
    // 文件存在且超過大小限制 → 重建覆蓋
    if (IsFileExist(szLogName) && GetFileLenth(szLogName) + 256 >= MAX_LOG_FILE_SIZE) {
        Create_LogFile(szLogName, 0);
    } else if (!IsFileExist(szLogName)) {
        Create_LogFile(szLogName, 0);
    }
    sprintf(pStrName, "%s", szLogName);
    return LOG_SUCCESS;
}

3. Windows API 文件/目錄操作


// 遞歸創建多級目錄
int Create_LogDir(const char *pStrPath) {
    char szSub[MAX_FILE_PATH];
    char *pSub = NULL;
    int iLen = 0, bFind = 0;
    while (1) {
        pSub = strchr(pStrPath + iLen, '/');
        if (!pSub) { // 最後一級
            int iRet = CreateDirectory(pStrPath, NULL);
            if (!iRet && GetLastError() == ERROR_ALREADY_EXISTS) return LOG_SUCCESS;
            return iRet ? LOG_SUCCESS : LOG_FAILED;
        }
        if (bFind) {
            strncpy(szSub, pStrPath, pSub - pStrPath);
            CreateDirectory(szSub, NULL); // 忽略已存在錯誤
        } else bFind = 1;
        iLen = pSub - pStrPath + 1;
    }
}

// 創建/截斷文件
int Create_LogFile(const char *pStrFile, int iPos) {
    HANDLE hd = CreateFile(pStrFile, GENERIC_READ|GENERIC_WRITE, 0, NULL,
                           CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hd == INVALID_HANDLE_VALUE) return LOG_FAILED;
    SetFilePointer(hd, iPos, NULL, FILE_BEGIN);
    SetEndOfFile(hd);
    CloseHandle(hd);
    return LOG_SUCCESS;
}

4. 寫入日志內容


int Write_Log_Text(LPLOG_DATA lpLogData) {
    char szFilePath[MAX_FILE_PATH], szFileName[MAX_LOG_FILE_NAME_LEN];
    char szLogText[MAX_LOGTEXT_LEN];
    
    GetLogPath(szFilePath);
    GetLogFileName(lpLogData->iType, szFilePath, szFileName);
    
    FILE *pFile = fopen(szFileName, "a+");
    if (!pFile) return LOG_FAILED;
    
    sprintf(szLogText, "%s %s %s\n", lpLogData->strDate, lpLogData->strTime, lpLogData->strText);
    fwrite(szLogText, 1, strlen(szLogText), pFile);
    fclose(pFile);
    return LOG_SUCCESS;
}

使用示例


int main() {
    Write_Log(LOG_TYPE_SYSTEM, "Program begin.");
    
    // 業務邏輯...
    FILE *f = fopen("config.ini", "r");
    if (!f) {
        Write_Log(LOG_TYPE_ERROR, "Open config.ini failed: %d", GetLastError());
    } else {
        Write_Log(LOG_TYPE_INFO, "Config loaded, size=%d", file_size);
        fclose(f);
    }
    
    Write_Log(LOG_TYPE_SYSTEM, "Program end.");
    return 0;
}

生成日志示例:


2024-01-15 10:30:45 Program begin.
2024-01-15 10:30:46 Config loaded, size=1024
2024-01-15 10:30:47 Program end.

代碼中待改進項(作者自註)

特性實現方式
分級日志INFO/ERROR/SYSTEM三級,分文件存儲
按日期分目錄根目錄/YYYYMMDD/下存放當日日志
自動分卷單文件超過512KB自動覆蓋重建
變長參數va_list支持printf風格格式化
編譯開關_LOG_WRITE_STATE_宏控制是否輸出
線程安全文件操作加互斥鎖(代碼中需補充)

現代化重構建議(C++17+)


class Logger {
    std::filesystem::path root_;
    std::mutex mtx_;
    std::ofstream info_, error_, system_;
    size_t max_size_;
    
public:
    enum Level { INFO, ERROR, SYSTEM };
    
    Logger(std::filesystem::path root, size_t max_size = 512*1024) 
        : root_(root), max_size_(max_size) {}
    
    template
    void log(Level level, const char* file, int line, const char* fmt, Args&&... args) {
        std::lock_guard lock(mtx_);
        auto now = std::chrono::system_clock::now();
        auto date = fmt::format("{:%Y-%m-%d}", now);
        auto time = fmt::format("{:%H:%M:%S}", now);
        
        auto& stream = get_stream(level, date);
        stream << date << ' ' << time << ' ' 
               << '[' << level_name(level) << ']' 
               << ' ' << fmt::format(fmt, std::forward(args)...)
               << " (" << file << ':' << line << ")\n";
        
        if (stream.tellp() > max_size_) rotate(level, date);
    }
    
    // 便捷宏
    #define LOG_INFO(...) logger.log(Logger::INFO, __FILE__, __LINE__, __VA_ARGS__)
    // ...
};

總結

本日志系統雖屬「輪子級」實現,但完整展示了:分級存儲、日期分目錄、大小分卷、變長參數格式化、Windows API文件操作、目錄遞歸創建等工程實用技術。對於無法引入spdlog、glog、Boost.Log等成熟庫的老項目或嵌入式環境,此代碼可作為輕量級替代方案直接使用或改造。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
問題改進方案
time()精度只到秒用GetLocalTime(ms)或QueryPerformanceCounter(μs)
非線程安全文件操作加CRITICAL_SECTION或std::mutex
僅支持Windows抽象文件/目錄/時間接口,提供Linux實現
缺文件名/行號宏封裝:LOG_INFO(fmt, ...)自動傳__FILE__,__LINE__
同步寫入阻塞業務線程異步日志:生產者-消費者隊列+後台寫入線程
無日志級別過濾運行時配置最小級別,編譯期#if LOG_LEVEL <= INFO