一个简单的日志类Logger

实现一个 C++ 简单日志类,具备以下特性:

  1. 日志文件命名采用文件名前缀加上日期的格式,方便管理与识别。
  2. 对单个日志文件大小进行限制,当文件大小达到 20MB 时,自动开启新的日志文件。
  3. 具备过期文件清理机制,自动删除保留时间超过 365 天的日志文件,节省存储空间。
  4. 该日志类是线程安全的,能够在多线程环境下稳定运行,避免日志记录冲突。
  5. 支持使用 format 格式进行日志记录,方便灵活输出不同格式的日志信息。
  6. 自动创建子目录log

Logger.h 文件内容:

#ifndef LOGGER_H

#define LOGGER_H

// 引入 ATL 字符串处理头文件,提供 CString 类
#include
// 引入 Windows API 核心头文件
#include

// Logger 类:实现日志记录功能
class Logger
{
private:
    // 单个日志文件最大
    size_t MAX_FILE_SIZE;

    // 日志文件保留天数
    int LOG_RETENTION_DAYS ;

    // 文件名前缀
    CString prefix;

    // 日志目录路径
    CString logDirectory;

    // 当前日志文件路径
    CString currentLogFile;

    // 日志文件指针
    FILE* pLogFile;

    // 临界区,保证多线程安全
    CRITICAL_SECTION cs;

    // 获取当前日期和时间字符串
    CString getCurrentDateTime();

    // 生成日志文件名
    CString getLogFileName();

    // 创建日志目录
    void createLogDirectory();

    // 打开新的日志文件
    void openNewLogFile();

    // 检查日志文件大小
    void checkFileSize();

    // 删除过期日志文件
    void deleteOldLogs();

public:
    // 构造函数,初始化对象  文件名前缀,单个文件大小(MB),文件保留天数
    Logger(CString pre = "log", int mb = 20, int max_day = 365);

    // 析构函数,释放资源
    ~Logger();

    // 支持可变参数的日志记录函数
    void log(const char* format, ...);

    // 记录 CString 类型日志
    void log(CString data);
};

#endif // LOGGER_H

Logger.cpp文件内容:

#include "stdafx.h"
#include "Logger.h"
#include
#include
#include
#include
#include
#include

// 链接 Kernel32 库
#pragma comment(lib, "Kernel32.lib")

// 获取当前日期和时间字符串
CString Logger::getCurrentDateTime()
{
    SYSTEMTIME st;
    GetLocalTime(&st);
    CString timestamp;
    // 格式化时间
    timestamp.Format("%04d-%02d-%02d %02d:%02d:%02d.%03d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    return timestamp;
}

// 生成日志文件名
CString Logger::getLogFileName()
{
    SYSTEMTIME st;
    GetLocalTime(&st);
    CString timestamp;
    // 格式化时间
    timestamp.Format("%04d-%02d-%02d-%02d-%02d-%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);

    CString fileName;
    fileName.Format("%s%s.txt", prefix, timestamp);
    return fileName;
}

// 创建日志目录
void Logger::createLogDirectory()
{
    if(_mkdir(logDirectory) != 0 && errno != EEXIST)
    {
        std::cerr << "Failed to create log directory: " << logDirectory << std::endl;
    }
}

// 打开新的日志文件
void Logger::openNewLogFile()
{
    if(pLogFile)
    {
        fclose(pLogFile);
    }
    currentLogFile = logDirectory + "\\" + getLogFileName();
    pLogFile = fopen(currentLogFile, "a");
    if(!pLogFile)
    {
        std::cerr << "Failed to open log file: " << currentLogFile << std::endl;
    }
}

// 检查日志文件大小
void Logger::checkFileSize()
{
    if(pLogFile)
    {
        fseek(pLogFile, 0, SEEK_END);
        long fileSize = ftell(pLogFile);
        if(fileSize >= static_cast(MAX_FILE_SIZE))
        {
            openNewLogFile();
        }
        fseek(pLogFile, 0, SEEK_SET);
    }
}
//删除超过天数的日志文件
void Logger::deleteOldLogs()
{
    // WIN32_FIND_DATA 结构体用于存储文件信息
    WIN32_FIND_DATA findData;

    // 构造搜索模式字符串,查找日志目录下所有 文件
    CString searchPattern = logDirectory + "\\*.*";

    // 使用 FindFirstFile 查找第一个匹配的文件
    HANDLE hFind = FindFirstFile(searchPattern, &findData);

    // 检查是否成功找到文件
    if(hFind != INVALID_HANDLE_VALUE)
    {
        do
        {
            // 构造文件的完整路径
            CString filePath = logDirectory + "\\" + findData.cFileName;

            // FILETIME 结构体用于存储文件的创建时间
            FILETIME ftCreation;

            // 打开文件以获取其创建时间
            HANDLE hFile = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

            // 检查文件是否成功打开
            if(hFile != INVALID_HANDLE_VALUE)
            {
                // 获取文件的创建时间
                GetFileTime(hFile, &ftCreation, NULL, NULL);

                // 关闭文件句柄
                CloseHandle(hFile);

                // SYSTEMTIME 结构体用于存储转换后的系统时间
                SYSTEMTIME stCreation;

                // 将 FILETIME 转换为 SYSTEMTIME
                FileTimeToSystemTime(&ftCreation, &stCreation);

                // 获取当前系统时间
                SYSTEMTIME now;
                GetLocalTime(&now);

                // 定义 tm 结构体用于存储当前时间和文件创建时间
                struct tm tmNow, tmCreation;

                // 初始化当前时间结构体
                tmNow.tm_sec = now.wSecond;       // 秒
                tmNow.tm_min = now.wMinute;       // 分钟
                tmNow.tm_hour = now.wHour;        // 小时
                tmNow.tm_mday = now.wDay;         // 日
                tmNow.tm_mon = now.wMonth - 1;    // 月(tm_mon 的范围是 0-11)
                tmNow.tm_year = now.wYear - 1900; // 年(tm_year 是从 1900 年开始的偏移量)
                tmNow.tm_isdst = 0;               // 不考虑夏令时

                // 初始化文件创建时间结构体
                tmCreation.tm_sec = stCreation.wSecond;       // 秒
                tmCreation.tm_min = stCreation.wMinute;       // 分钟
                tmCreation.tm_hour = stCreation.wHour;        // 小时
                tmCreation.tm_mday = stCreation.wDay;         // 日
                tmCreation.tm_mon = stCreation.wMonth - 1;    // 月(tm_mon 的范围是 0-11)
                tmCreation.tm_year = stCreation.wYear - 1900; // 年(tm_year 是从 1900 年开始的偏移量)
                tmCreation.tm_isdst = 0;                      // 不考虑夏令时

                // 将 tm 结构体转换为 time_t 类型的时间戳
                time_t tNow = mktime(&tmNow);          // 当前时间的时间戳
                time_t tCreation = mktime(&tmCreation); // 文件创建时间的时间戳

                // 计算时间差(以天为单位)
                double diff = difftime(tNow, tCreation) / (60 * 60 * 24);

                // 如果文件创建时间超过 LOG_RETENTION_DAYS 天,则删除文件
                if(diff > LOG_RETENTION_DAYS)
                {
                    DeleteFile(filePath);
                }
            }
        }
        while(FindNextFile(hFind, &findData) != 0);    // 查找下一个匹配的文件

        // 关闭查找句柄
        FindClose(hFind);
    }
}

// 构造函数,初始化日志系统
Logger::Logger(CString pre, int mb , int max_day )
{
    prefix = pre;
    MAX_FILE_SIZE = mb * 1024 * 1024;
    LOG_RETENTION_DAYS = max_day;
    InitializeCriticalSection(&cs);
    char exePath[MAX_PATH];
    GetModuleFileName(NULL, exePath, MAX_PATH);
    CString exeDir = exePath;
    int pos = exeDir.ReverseFind('\\');
    if(pos != -1)
    {
        exeDir = exeDir.Left(pos);
    }
    logDirectory = exeDir + "\\log";
    createLogDirectory();
    openNewLogFile();
    deleteOldLogs();
}

// 析构函数,释放资源
Logger::~Logger()
{
    if(pLogFile)
    {
        fclose(pLogFile);
    }
    DeleteCriticalSection(&cs);
}

// 可变参数日志记录函数,用于将格式化的日志信息写入日志文件
// 参数 format: 格式化字符串,类似于 printf 函数的第一个参数,用于指定日志信息的格式
// ...: 可变参数,根据 format 字符串的要求传入相应的参数
void Logger::log(const char* format, ...)
{
    // 进入临界区,确保多线程环境下对日志文件的操作是线程安全的
    EnterCriticalSection(&cs);

    // 检查当前日志文件的大小 如果文件大小超过预设的最大文件大小,会创建一个新的日志文件
    checkFileSize();

    // 检查日志文件指针是否有效
    if (pLogFile)
    {
        // 定义一个 va_list 类型的变量 args,用于处理可变参数
        va_list args;
        // 初始化 va_list 变量 args,使其指向可变参数列表的起始位置
        va_start(args, format);

        // 使用 _vscprintf 函数计算格式化后的字符串所需的缓冲区大小
        int len = _vscprintf(format, args);

        // 检查计算得到的字符串长度是否大于 0
        if (len > 0)
        {
            // 创建一个 std::vector 类型的缓冲区,大小为 len + 1
            // 额外的 1 个字节用于存储字符串结束符 '\0'
            std::vector buffer(len + 1);

            // 使用 vsprintf_s 函数将可变参数按照 format 字符串的格式写入缓冲区
            // vsprintf_s 是安全版本的函数,会检查缓冲区大小,避免缓冲区溢出
            // 如果写入成功,返回写入的字符数(不包括字符串结束符),该值应该大于等于 0
            if (vsprintf_s(buffer.data(), buffer.size(), format, args) >= 0)
            {
                // 将缓冲区中的字符数组转换为 CString 类型的日志消息
                CString logMessage(buffer.data());

                // 获取当前的日期和时间
                CString timestamp = getCurrentDateTime();

                // 将时间戳、日志消息和换行符拼接成完整的日志记录
                CString fullLog = timestamp + "  " + logMessage + "\n";

                // 使用 fprintf 函数将完整的日志记录写入日志文件
                fprintf(pLogFile, "%s", fullLog);

                // 调用 fflush 函数强制将缓冲区中的数据写入文件
                fflush(pLogFile);
            }
        }

        // 结束可变参数的处理,释放 va_list 变量占用的资源
        va_end(args);
    }

    // 离开临界区,允许其他线程进入并操作日志文件
    LeaveCriticalSection(&cs);
}

// 记录 CString 类型日志
void Logger::log(CString data)
{    
    EnterCriticalSection(&cs);
    checkFileSize();

    if(pLogFile)
    {
        CString timestamp = getCurrentDateTime();
        CString fullLog = timestamp + "  " + data + "\n";
        fprintf(pLogFile, "%s", fullLog);
        fflush(pLogFile);
    }
    LeaveCriticalSection(&cs);
}

用法举例:

Logger logger("log",20,365);

logger.log("ANSI文本转换为UTFB,并设置到控件出错");

logger.log("temp width :%d 中心点的温度:%.1f 延时:%d", tempInfo->width, g_dTemp[y1 * Width + x1], page->m_iTempDelay);

你可能感兴趣的:(单片机,嵌入式硬件,c++,学习,笔记,开发语言)