C++编程:一个简单的日志头文件loghelper.hpp

0. 引言

使用C++构建一个简单的线程安全的日志头文件(head-only)的loghelper.hpp,适用于简单快速的调试程序,又不想引入比较复杂的日志模块。

1. 完整代码

我们将日志系统封装在一个Logger类中,采用单例模式确保全局只有一个Logger实例。该类负责日志级别的管理、日志文件的处理以及日志的输出。

// loghelper.hpp
#ifndef LOGGER_H_
#define LOGGER_H_

#include <cstdint>
#include <sys/stat.h>
#include <cstring>
#include <iostream>
#include <string>
#include <utility>
#include <unistd.h>
#include <memory>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <mutex>

// Log levels enumeration
enum class LogLevel : std::int32_t
{
    TRACE    = 0,
    DEBUG    = 1,
    INFO     = 2,
    WARN     = 3,
    ERROR    = 4,
    CRITICAL = 5,
    OFF      = 6
};

// RAII class to automatically close file upon destruction
class FileCloser
{
public:
    explicit FileCloser(FILE* file) : file_(file) {}
    ~FileCloser()
    {
        if (file_ != nullptr)
        {
            fclose(file_);
        }
    }
    FILE* get() const { return file_; }

private:
    FILE* file_;
};

// Logger class implementing the Singleton pattern
class Logger
{
public:
    // Retrieves the single instance of Logger
    static Logger& Instance()
    {
        static Logger instance;
        return instance;
    }

    // Initializes the logger with the log file path and log level
    void Init(const std::string& logFile, LogLevel level = LogLevel::INFO)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        logFilePath_ = logFile;
        logLevel_ = level;

        if (access(logFilePath_.c_str(), F_OK) != 0)
        {
            fprintf(stderr, "Error: Log file does not exist: %s. Creating a new one.\n", logFilePath_.c_str());
            FILE* logFilePtr = fopen(logFilePath_.c_str(), "w");
            if (logFilePtr != nullptr)
            {
                fclose(logFilePtr);
                fprintf(stdout, "Log file created: %s\n", logFilePath_.c_str());
            }
            else
            {
                fprintf(stderr, "Error: Unable to create log file: %s\n", logFilePath_.c_str());
                logFilePath_.clear();
            }
        }
        else
        {
            fprintf(stdout, "Log file exists: %s\n", logFilePath_.c_str());
        }
    }

    // Logs a message with the given level, function name, and line number
    template <typename... Args>
    void Log(
        LogLevel      level,
        const char*   src_func,
        int32_t       src_line,
        const char*   fmt_str,
        Args&&...     fmt_args)
    {
        if (level < logLevel_ || logLevel_ == LogLevel::OFF)
        {
            return;
        }

        char tmp_buf[kLogBufferSize];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-security"
        int32_t sp_res = std::snprintf(tmp_buf, sizeof(tmp_buf), fmt_str, std::forward<Args>(fmt_args)...);
#pragma GCC diagnostic pop
        if (sp_res < 0)
        {
            return;
        }

        std::stringstream ss;
        ss << "[" << LogLevelToString(level) << "] ";
        ss << "[" << src_func << ":" << src_line << "] ";
        ss << tmp_buf << "\n";
        std::string logEntry = ss.str();

        std::lock_guard<std::mutex> lock(mutex_);
        // Output to terminal
        std::cout << logEntry;

        // Output to file
        if (!logFilePath_.empty())
        {
            // Check if file size exceeds the maximum limit
            uint64_t fileSize = GetFileSize(logFilePath_.c_str());
            if (fileSize != static_cast<uint64_t>(-1) && fileSize >= kMaxFileSize)
            {
                ForceRenameLogFile();
            }

            // Open the log file in append mode
            FILE* logFile = fopen(logFilePath_.c_str(), "a");
            FileCloser closer(logFile);
            if (logFile != nullptr)
            {
                fprintf(logFile, "%s", logEntry.c_str());
            }
            else
            {
                fprintf(stderr, "Error: Unable to open log file: %s\n", logFilePath_.c_str());
            }
        }
    }

    // Disable copy constructor and assignment operator
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

private:
    Logger() : logLevel_(LogLevel::INFO), logFilePath_("") {}

    // Retrieves the size of the specified file
    uint64_t GetFileSize(const char* filePath)
    {
        struct stat statBuf;
        if (stat(filePath, &statBuf) == 0)
        {
            return statBuf.st_size;
        }
        return static_cast<uint64_t>(-1); // Use unsigned type -1 to indicate error
    }

    // Renames the current log file to create a backup
    void ForceRenameLogFile()
    {
        if (logFilePath_.empty())
        {
            fprintf(stderr, "Error: Log file path is empty.\n");
            return;
        }

        // Construct the backup file path
        std::string backupFilePath = logFilePath_ + ".1";

        // Construct the mv command
        std::string command = "mv -f " + logFilePath_ + " " + backupFilePath;

        fprintf(stdout, "Renaming log file: %s -> %s\n", logFilePath_.c_str(), backupFilePath.c_str());

        // Execute the command using system()
        system(command.c_str());
    }

    // Converts a LogLevel to its string representation
    std::string LogLevelToString(LogLevel level)
    {
        switch (level)
        {
            case LogLevel::TRACE:    return "TRACE";
            case LogLevel::DEBUG:    return "DEBUG";
            case LogLevel::INFO:     return "INFO";
            case LogLevel::WARN:     return "WARN";
            case LogLevel::ERROR:    return "ERROR";
            case LogLevel::CRITICAL: return "CRITICAL";
            case LogLevel::OFF:      return "OFF";
            default:                 return "UNKNOWN";
        }
    }

    static constexpr uint32_t kLogBufferSize = 1024;
    static constexpr uint64_t kMaxFileSize = 10 * 1024 * 1024; // 10MB

    LogLevel logLevel_;
    std::string logFilePath_;
    std::mutex mutex_;
};

// Macro definitions for simplified logging
#ifndef LOG_MACROS_H
#define LOG_MACROS_H

#define LOG_INFO(...) \
    Logger::Instance().Log(LogLevel::INFO, __FUNCTION__, __LINE__, __VA_ARGS__)

#define LOG_DBG(...) \
    Logger::Instance().Log(LogLevel::DEBUG, __FUNCTION__, __LINE__, __VA_ARGS__)

#define LOG_ERR(...) \
    Logger::Instance().Log(LogLevel::ERROR, __FUNCTION__, __LINE__, __VA_ARGS__)

#define LOG_PANIC(...)    \
    do { \
        Logger::Instance().Log(LogLevel::CRITICAL, __FUNCTION__, __LINE__, __VA_ARGS__); \
        exit(1); \
    } while(0)

#endif // LOG_MACROS_H

#endif // LOGGER_H_

2. 使用示例

以下是一个示例程序,展示如何使用上述日志系统:

#include "Logger.h"

int main()
{
    // Initialize the logger with the log file path and desired log level
    Logger::Instance().Init("application.log", LogLevel::DEBUG);

    LOG_INFO("Application started with PID: %d", getpid());
    LOG_DBG("This is a debug message.");
    LOG_ERR("An error occurred: %s", "Sample error");

    // Simulate logging to reach the file size limit
    // Adjust kMaxFileSize in Logger.h for testing purposes if needed
    for(int i = 0; i < 100000; ++i)
    {
        LOG_INFO("Logging line number: %d", i);
    }

    LOG_PANIC("Critical failure: %s", "Unable to continue");

    return 0;
}

解释:

  1. 初始化日志系统

    Logger::Instance().Init("application.log", LogLevel::DEBUG);
    

    设置日志文件路径为application.log,并将日志级别设置为DEBUG,这意味着DEBUG及以上级别的日志将被记录。

  2. 记录不同级别的日志

    LOG_INFO("Application started with PID: %d", getpid());
    LOG_DBG("This is a debug message.");
    LOG_ERR("An error occurred: %s", "Sample error");
    

    通过宏定义简化日志记录,自动包含函数名和行号信息。

  3. 模拟大量日志记录

    for(int i = 0; i < 100000; ++i)
    {
        LOG_INFO("Logging line number: %d", i);
    }
    

    这段代码用于模拟日志文件达到大小限制(10MB)的情况,触发日志轮转机制。

  4. 记录严重错误并终止程序

    LOG_PANIC("Critical failure: %s", "Unable to continue");
    

    记录一个严重错误后,立即终止程序执行。

3. 代码说明

流程图

上述流程图如下:

Yes
No
Yes
No
Yes
No
Start
Initialize Logger
Log File Exists?
Set Log File Path
Create New Log File
Set Log Level
Log Message
Is Log Level >= Current Level?
Format Log Entry
Discard Log
Output to Terminal
Check Log File Size
Size >= 10MB?
Rotate Log File
Append to Log File
End

日志级别定义

通过枚举类型LogLevel定义不同的日志级别,从TRACECRITICAL,再到OFF,可以灵活控制日志的输出细粒度。

// Log levels enumeration
enum class LogLevel : std::int32_t
{
    TRACE    = 0,
    DEBUG    = 1,
    INFO     = 2,
    WARN     = 3,
    ERROR    = 4,
    CRITICAL = 5,
    OFF      = 6
};

文件管理与RAII

为了确保日志文件在程序结束时能够正确关闭,我们引入了一个RAII(资源获取即初始化)类FileCloser。该类在析构时自动关闭文件,避免资源泄漏。

// RAII class to automatically close file upon destruction
class FileCloser
{
public:
    explicit FileCloser(FILE* file) : file_(file) {}
    ~FileCloser()
    {
        if (file_ != nullptr)
        {
            fclose(file_);
        }
    }
    FILE* get() const { return file_; }

private:
    FILE* file_;
};

线程安全

在多线程环境下,多个线程可能会同时尝试写入日志文件。为了避免数据竞争和日志内容混乱,我们在Logger类中使用std::mutex来保护关键区域,确保同一时间只有一个线程可以执行写入操作。

class Logger
{
public:
    // ... 其他成员函数 ...

private:
    // ... 其他成员变量 ...
    std::mutex mutex_;
};

在需要保护的代码段使用std::lock_guard自动管理锁的获取和释放:

void Init(const std::string& logFile, LogLevel level = LogLevel::INFO)
{
    std::lock_guard<std::mutex> lock(mutex_);
    // 初始化日志文件路径和日志级别
}

宏定义简化日志调用

为了简化日志的调用,我们使用宏定义来封装Logger类的日志函数。这样,开发者只需调用LOG_INFOLOG_DBGLOG_ERR即可记录日志,而无需关心具体的实现细节。

// Macro definitions for simplified logging
#ifndef LOG_MACROS_H
#define LOG_MACROS_H

#define LOG_INFO(...) \
    Logger::Instance().Log(LogLevel::INFO, __FUNCTION__, __LINE__, __VA_ARGS__)

#define LOG_DBG(...) \
    Logger::Instance().Log(LogLevel::DEBUG, __FUNCTION__, __LINE__, __VA_ARGS__)

#define LOG_ERR(...) \
    Logger::Instance().Log(LogLevel::ERROR, __FUNCTION__, __LINE__, __VA_ARGS__)

#define LOG_PANIC(...)    \
    do { \
        Logger::Instance().Log(LogLevel::CRITICAL, __FUNCTION__, __LINE__, __VA_ARGS__); \
        exit(1); \
    } while(0)

#endif // LOG_MACROS_H
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值