C++ RAII流式日志库实现

1-1⽇志与策略模式

什么是设计模式

IT⾏业这么⽕,涌⼊的⼈很多.俗话说林⼦⼤了啥⻦都有.⼤佬和菜鸡们两极分化的越来越严重.为了让菜鸡们不太拖⼤佬的后腿,于是⼤佬们针对⼀些经典的常⻅的场景,给定了⼀些对应的解决⽅案,这个就是设计模式

⽇志认识

计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯具。

⽇志格式以下⼏个指标是必须得有的

• 时间戳

• ⽇志等级

• ⽇志内容

以下⼏个指标是可选的

• ⽂件名⾏号

• 进程,线程相关id信息等

⽇志有现成的解决⽅案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采⽤⾃定义⽇志的⽅式。

这⾥我们采⽤设计模式-策略模式来进⾏⽇志的设计,具体策略模式介绍,详情看代码和课程。

我们想要的⽇志格式如下:

cpp 复制代码
#include<iostream>
#include<string>
#include <fstream>
#include <memory>
#include <ctime>
#include <sstream>
#include <filesystem> // C++17, 需要⾼版本编译器和-std=c++17 
#include <unistd.h>
#include "Lock.hpp"

namespace LogModule
{
   
    //默认路径与日志名称
    const std::string defaultpath = "./log/";
    const std::string defaultname = "log.txt";

    //日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    //日志转换成字符串
    std::string LogLevelToString(LogLevel level)
    {
        switch(level)
        {
            case LogLevel::DEBUG:
            return "DEBUG";
            case LogLevel::INFO:
            return "INFO";
            case LogLevel::WARNING:
            return "WARNING";
            case LogLevel::ERROR:
            return "ERROR";
            case LogLevel::FATAL:
            return "FATAL";
            default:
            return "UNKNOW";
        }
    }

    //根据时间戳,获取可读性较强的时间信息
    std::string GetCurrTime()
    {
        time_t tm = time(nullptr);
        struct tm curr;
        localtime_r(&tm,&curr);

        char timebuffer[64];
        snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
                        curr.tm_year + 1900,
                        curr.tm_mon + 1,
                        curr.tm_mday,
                        curr.tm_hour,
                        curr.tm_min,
                        curr.tm_sec);
        return timebuffer;
    }

    //策略模式,策略接口
    class LogStrategy
    {
        public:
            //触发子类析构 → 打印日志(核心功能)再触发基类默认析构 → 空,安全回收
            virtual ~LogStrategy() = default;//如果基类析构不是虚函数,只会调用基类析构,派生类析构不执行 → 内存泄漏、文件不关闭、日志没刷新!
            virtual void SyncLog(const std::string &message) = 0;//不同模式核心刷新方式不同
    };

    //显示器打印
    class ConsoleLogStrategy:public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lock(_mutex);
            std::cerr<<message<<std::endl;
        }

    private:
        Mutex _mutex;//显示器也是临界资源
    };

    //文件日志策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        //构造函数,建立出指定的目录结构和文件结构
        FileLogStrategy(const std::string& logpath = defaultpath,const std::string& logfilename = defaultname)
        :_logpath(logpath),
         _logfilename(logfilename)
         {
            LockGuard lockguard(_mutex);
            if(std::filesystem::exists(_logpath))
                return;
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch(const std::filesystem::filesystem_error &e)
            {
                std::cerr<<e.what()<<'\n';
            }
         }

         //将一条日志信息写入文件中
         void SyncLog(const std::string &message)override
         {
            LockGuard lockguard(_mutex);
            std::string log = _logpath+_logfilename;
            std::ofstream out(log.c_str(),std::ios::app);//追加模式
            if(!out.is_open())
            {
                return;
            }
            out<<message<<'\n';
            out.close();
         }

    private:
         std::string _logpath;
         std::string _logfilename;
         Mutex _mutex;
    };
    
    // 具体的日志类
class Logger
{
public:
    Logger()
    {
        // 默认使用显示器策略
        UseConsoleStrategy();
    }
    ~Logger() = default;

    void UseConsoleStrategy()
    {
        _strategy = std::make_unique<ConsoleLogStrategy>();
    }
    void UseFileStrategy()
    {
        // 修复:正确的类名 + 正确的 make_unique 写法
        _strategy = std::make_unique<FileLogStrategy>();
    }

    // 内部类 RAII 风格
    class LogMessage
    {
    private:
        LogLevel _type;          // 日志等级
        std::string _curr_time;  // 日志时间
        pid_t _pid;              // 进程ID
        std::string _filename;   // 写入文件名
        int _line;               // 文件行号
        Logger& _logger;         // 引用外部logger类,方便使用策略进行刷新
        std::string _loginfo;    // 完整日志信息

    public:
        // RAII风格,构造的时候构建好日志头部信息
        LogMessage(LogLevel type, const std::string& filename, int line, Logger& logger)
            : _type(type),
              _curr_time(GetCurrTime()),
              _pid(getpid()),
              _filename(filename),
              _line(line),
              _logger(logger)
        {
            std::stringstream ssbuffer;
            ssbuffer << "[" << _curr_time << "]"
                     << "[" << LogLevelToString(type) << "]"
                     << "[" << _pid << "]"
                     << "[" << _filename << "]"
                     << "[" << _line << "]"
                     << " - ";
            _loginfo = ssbuffer.str();
        }

        // 重载<<支持c++风格的日志输入,使用模版
        template <typename T>
        LogMessage& operator<<(const T& info)
        {
            std::stringstream ssbuffer;
            ssbuffer << info;
            _loginfo += ssbuffer.str();
            return *this; // 返回当前LogMessage对象,方便下次继续进行<<
        }

        // RAII风格,析构的时候进行日志持久化
        ~LogMessage()
        {
            if (_logger._strategy)
            {
                _logger._strategy->SyncLog(_loginfo);
            }
        }
    };

    // 故意拷贝,形成LogMessage临时对象,后续再<<时,会持续引用
    // 直到完成输入,才会自动析构临时LogMessage,完成日志刷新
    LogMessage operator()(LogLevel type, const std::string& filename, int line)
    {
        return LogMessage(type, filename, line, *this);
    }

private:
    std::unique_ptr<LogStrategy> _strategy;
};



inline Logger logger;

#define Log(type) logger(LogModule::LogLevel::type, __FILE__, __LINE__)

// 提供选择使用何种日志策略的方法
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy()
#define ENABLE_FILE_LOG_STRATEGY()    logger.UseFileStrategy()
}
cpp 复制代码
#include "Log.hpp"
using namespace LogModule;

int main()
{
    // 默认打印到控制台
    Log(INFO) << "服务器启动成功!";
    Log(DEBUG) << "加载配置完成";
    Log(WARNING) << "磁盘空间不足";
    Log(ERROR) << "网络连接失败";
    Log(FATAL) << "程序崩溃";

    // 切换成文件输出
    ENABLE_FILE_LOG_STRATEGY();
    Log(INFO) << "这条日志写入文件";

    return 0;
}

调用operator():创建临时LogMessage对象 A,返回 A 的值(临时对象)

第一次<<:给 A 追加"服务器启动",返回 A 的引用(A 还活着)

第二次<<:给 A 追加"用户登录",返回 A 的引用(A 还活着)

第三次<<:给 A 追加endl,返回 A 的引用(A 还活着)

语句结束(分号):A 的生命周期结束,自动调用~A(),把完整日志写入

A 被销毁,内存完全回收,无泄漏

执行流程:

Log(INFO) → 调用 operator(),返回一个 临时 LogMessage 对象

接着调用 operator<<("服务器启动"),给这个临时对象追加内容

语句结束(遇到;) → 临时对象 作用域结束 → 编译器 自动调用~LogMessage ()

相关推荐
Hical6121 分钟前
实测:C++20 协程 vs Go Gin vs Rust Actix,谁的 Web 性能更强?
c++
wjs202424 分钟前
Go 语言接口
开发语言
草莓熊Lotso32 分钟前
《告别 “会用不会讲”:C++ string 底层原理拆解 + 手撕实现,面试 / 开发都适用》
开发语言·c++·面试
水木流年追梦34 分钟前
【python因果库实战27】逆概率加权模型2
开发语言·python
会编程的土豆39 分钟前
【数据结构与算法】空间复杂度从入门到面试:不仅会算,还要会解释
数据结构·c++·算法·面试·职场和发展
张槊哲1 小时前
C++ 进阶指南:如何丝滑地理解与实践多线程与多进程
开发语言·c++·算法
雪度娃娃1 小时前
Effective Modern C++——型别推导
开发语言·c++
Hello eveybody1 小时前
介绍一下背包DP(C++)
开发语言·c++·动态规划·dp·背包dp
charlie1145141912 小时前
AwesomeQt:最小的Qt6系列迷你版本教程发布!
linux·c++·qt·c
Run_Teenage2 小时前
Linux:线程互斥,线程锁
运维·开发语言·jvm