【Linux】日志模块实现详解

📢博客主页:https://blog.csdn.net/2301_779549673

📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson

📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

📢本文由 JohnKi 原创,首发于 CSDN🙉

📢未来很长,值得我们全力奔赴更美好的生活✨

文章目录


📢前言

日志系统是软件开发中不可或缺的组成部分,它记录了程序的运行状态、错误信息和调试细节。本文将结合一段C++实现的日志模块代码,深入讲解日志系统的核心设计思想、技术实现细节及实际应用场景。通过阅读本文,笔者者将带你掌握如何构建一个灵活、高效且可扩展的日志模块。


🏳️‍🌈一、为什么要设计日志系统

什么是设计模式

IT行业这么火,涌入的人很多,俗话说林子大了啥鸟都有,大佬和菜鸡们两极分化的越来越严重,为了让菜鸡们不太拖大佬的后腿,于是大佬们针对一些经典的常见的场景,给定了一些对应的解决方案,这个就是 设计模式

日志认识

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

日志格式以下几个指标是必须得有的

  • 时间戳
  • 日志等级
  • 日志内容

以下几个指标是可选的

  • 文件名行号
  • 进程,线程相关id信息等

日志有现成的解决方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采用自定义日志的方式。

这里我们采用设计模式-策略模式来进行日志的设计,具体策略模式介绍,详情看代码和课程。
我们想要的日志格式如下:

bash 复制代码
[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容,⽀持可
变参数
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world
[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world

🏳️‍🌈二、日志系统逻辑框架

日志模块的核心功能

  • 时间戳生成:每条日志必须包含精确的时间信息。
  • 日志等级管理:区分不同重要性的日志(如DEBUG、ERROR)。
  • 日志输出策略:支持控制台输出、文件持久化等多种方式。
  • 线程安全:多线程环境下保证日志写入的原子性。
  • 易用性 :通过宏定义简化调用。
bash 复制代码
+------------------+
|     Logger        |  --> 管理策略、构建日志消息
+------------------+
       |
       | 使用策略模式
       v
+------------------+
|  LogStrategy      |  --> 抽象接口(控制台/文件输出)
+------------------+
       |       |
       |       +--------> ConsoleLogStrategy
       |       +--------> FileLogStrategy
       v
+------------------+
|  LogMessage       |  --> 封装单条日志的完整信息
+------------------+

🏳️‍🌈三、关键技术实现解析

3.1 时间戳生成

下图是 struct_tm 结构的示意,他能帮助我们获取当前时间

关键点 :使用localtime_r替代localtime保证线程安全。
​输出格式:YYYY-MM-DD HH:MM:SS,便于人类阅读和机器解析

bash 复制代码
// 获取一下当前系统的时间
std::string CurrentTime() {
    time_t time_stamp = ::time(nullptr);
    struct tm curr;
    localtime_r(&time_stamp, &curr);

    char buffer[1024];

    snprintf(buffer, sizeof(buffer), "%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 static_cast<std::string>(buffer);
}

3.2 日志等级管理

通过枚举类强制类型安全,避免无效等级

bash 复制代码
    // 日志等级
    enum LogLevel{
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2string(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 "None";
        }
    }

Level2string 中的 2 音译 to

🏳️‍🌈四、日志输出策略

日志输出策略课分为

  • 控制台级
  • 文件级

因此我们可以建立一个总的策略来统一管理

4.1 LogStrategy

bash 复制代码
// 日志输出策略
class LogStrategy {
public:
    virtual ~LogStrategy() = 0;
    virtual void SyncLog(const std::string& msg) = 0;
};

4.2 ConsoleLogStrategy

控制台级的日志,我们只需要输出就行了,注意线程安全

bash 复制代码
// 日志控制台输出策略
class ConsoleLogStrategy : public LogStrategy {
public:
    ConsoleLogStrategy() {}
    ~ConsoleLogStrategy() {}
    void SyncLog(const std::string& message) {
        LockGuard lockguard(_lock);
        std::cout << message << std::endl;
    }

private:
    Mutex _lock;
};

4.3FileLogStrategy

文件级的就相对有些复杂了

  • 首先,我们需要确定这个日志将要追加的位置,可以提前默认
  • 其实,我们要对文件的创建、打开、关闭负责好,及时在错误的情况输出
  • 最后,我们要做好往文件追加的功能实现
bash 复制代码
// 默认日志文件地址和名字
const std::string defaultlogpath = "./log/";
const std::string defaultlogname = "log.txt";
// 日志文件输出策略
class FileLogStrategy : public LogStrategy {
public:
    FileLogStrategy(const std::string& path = defaultlogpath,
                    const std::string& name = defaultlogname)
        : _logpath(path), _logname(name) {
        LockGuard lockGuard(_mutex);

        if (std::filesystem::exists(_logpath))
            return;

        try {
            std::filesystem::create_directories(_logpath);
        }

        catch (std::filesystem::filesystem_error& e) {
            std::cerr << e.what() << "\n";
        }
    }

    ~FileLogStrategy() {}

    void SyncLog(const std::string& message) {
        LockGuard lockguard(_mutex);
        std::string log = _logpath + _logname;

        std::ofstream out(log, std::ios::app);
        if (!out.is_open())
            return;

        out << message << std::endl;
        out.close();
    }

private:
    Mutex _mutex;
    std::string _logpath;
    std::string _logname;
};

🏳️‍🌈五、日志类

5.1 日志消息构建

  • RAII技术:利用析构函数自动提交日志,避免手动提交遗漏。
  • ​流式接口:通过重载operator<<实现链式调用。

日志消息规格

2024-08-04 12:27:03 DEBUG 202938 main.cc 16 + 日志的可变部分(<< "hello world" << 3.14 << a << b;)

因此我们需要包含以下的成员变量

bash 复制代码
std::string _currtime; // 当前日志的时间
LogLevel _level;       // 日志等级
pid_t _pid;            // 进程ID
std::string _filename; // 文件名
int _line;             // 行号
Logger & _logger;      // 日志类
std::string _log_msg;  // 一条完整的日志内容

然后我们可以利用模板实现 可变部分 的添加

bash 复制代码
LoggerMessage(LogLevel level, const std::string& filename, int line,
              Logger& logger)
    : _level(level), _pid(::getpid()), _filename(filename), _line(line),
      _logger(logger) {
    std::stringstream ssbuffer;
    ssbuffer << "[" << CurrentTime() << "]"
             << "[" << Level2string(level) << "]"
             << "[" << _pid << "]"
             << "[" << _filename << "]"
             << "[" << _line << "] - ";
    _log_msg = ssbuffer.str();
}

template <typename T> LoggerMessage& operator<<(const T& info) {
    std::stringstream ss;
    ss << info;
    _log_msg += ss.str();
    return *this;
}

~LoggerMessage() {
    if (_logger._logstrategy) {
        _logger._logstrategy->SyncLog(_log_msg);
    }
}

5.2 日志类封装

我们在这里确认日志的策略模式,然后利用 日志消息类 组织日志,并输出

bash 复制代码
Logger() {
    // 默认采用 控制台级 日志打印
    _logstrategy = std::make_shared<ConsoleLogStrategy>();
}
~Logger() {}

void EnableConsoleLog() {
    _logstrategy = std::make_shared<ConsoleLogStrategy>();
}

void EnableFileLog() { _logstrategy = std::make_shared<FileLogStrategy>(); }

// 就是要拷贝,故意的拷贝
LoggerMessage operator()(LogLevel level, const std::string& filename,
                         int line) {
    return LoggerMessage(level, filename, line, *this);
}

private:
std::shared_ptr<LogStrategy> _logstrategy;

5.3 用户接口

为了方便我们使用,我们可以进行如下操作

bash 复制代码
Logger logger;
#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() log.EnableConsoleLog()
#define ENABLE_FILE_LOG() log.EnableFileLog()

🏳️‍🌈六、整体代码

bash 复制代码
#include <iostream>
#include <sstream>
#include <memory>

#include <filesystem>
#include <fstream>

#include <sys/types.h>
#include <unistd.h>


#include "Mutex.hpp"

namespace LogModule{
    using namespace LockModule;

    // 获取一下当前系统的时间
    std::string CurrentTime(){
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr);

        char buffer[1024];

        snprintf(buffer, sizeof(buffer), "%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 static_cast<std::string>(buffer);
    }

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

    std::string Level2string(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 "None";
        }
    }



    // 日志输出策略
    class LogStrategy{
        public:
            virtual ~LogStrategy() = 0;
            virtual void SyncLog(const std::string& msg) = 0;
    };

    // 日志控制台输出策略
    class ConsoleLogStrategy : public LogStrategy{
        public:
            ConsoleLogStrategy(){}
            ~ConsoleLogStrategy(){}
            void SyncLog(const std::string &message){
                LockGuard lockguard(_lock);
                std::cout << message << std::endl;
            }

        private:
            Mutex _lock;
    };

    // 默认日志文件地址和名字
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";
    // 日志文件输出策略
    class FileLogStrategy : public LogStrategy{
        public:
            FileLogStrategy(const std::string& path = defaultlogpath, const std::string& name = defaultlogname)
                : _logpath(path), _logname(name)
            {
                LockGuard lockGuard(_mutex);        

                if(std::filesystem::exists(_logpath)) return;

                try{
                    std::filesystem::create_directories(_logpath);
                }

                catch(std::filesystem::filesystem_error& e){
                    std::cerr << e.what() << "\n";
                }
            }

            ~FileLogStrategy(){}

            void SyncLog(const std::string& message){
                LockGuard lockguard(_mutex);
                std::string log = _logpath + _logname;

                std::ofstream out(log, std::ios::app);
                if(!out.is_open()) return;

                out << message << std::endl;
                out.close();
            }

        private:
            Mutex _mutex;
            std::string _logpath;
            std::string _logname;
    };

    // 日志类
    class Logger{
        public:
            Logger(){
                // 默认采用 控制台级 日志打印
                _logstrategy = std::make_shared<ConsoleLogStrategy>();
            }
            ~Logger(){}

            void EnableConsoleLog(){
                _logstrategy = std::make_shared<ConsoleLogStrategy>();
            }

            void EnableFileLog(){
                _logstrategy = std::make_shared<FileLogStrategy>();
            }

            // 日志消息类
            // 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
            class LoggerMessage{
                public:
                    LoggerMessage(LogLevel level, const std::string& filename, int line, Logger& logger)
                        : _level(level), _pid(::getpid()), _filename(filename), _line(line), _logger(logger)
                    {
                        std::stringstream ssbuffer;
                        ssbuffer << "[" << CurrentTime() << "]" 
                                 << "[" << Level2string(level) << "]"
                                 << "[" << _pid << "]"
                                 << "[" << _filename << "]"
                                 << "[" << _line << "] - ";
                        _log_msg = ssbuffer.str();
                    }

                    template<typename T>
                    LoggerMessage& operator<<(const T& info){
                        std::stringstream ss;
                        ss << info;
                        _log_msg += ss.str();
                        return *this;
                    }

                    ~LoggerMessage(){
                        if(_logger._logstrategy){
                            _logger._logstrategy->SyncLog(_log_msg);
                        }
                    }

                private:
                    std::string _currtime;  // 当前日志的时间
                    LogLevel _level;        // 日志等级
                    pid_t _pid;             // 进程ID
                    std::string _filename;  // 文件名
                    int _line;              // 行号
                    Logger& _logger;        // 日志类
                    std::string _log_msg;   // 一条完整的日志内容
            };

            // 就是要拷贝,故意的拷贝
            LoggerMessage operator()(LogLevel level, const std::string &filename, int line)
            {
                return LoggerMessage(level, filename, line, *this);
            }

        private:
            std::shared_ptr<LogStrategy> _logstrategy;
    };

    Logger logger;
#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() log.EnableConsoleLog()
#define ENABLE_FILE_LOG() log.EnableFileLog()
}

👥总结

本篇博文对 【Linux】日志模块实现详解 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关推荐
imc.119 分钟前
linux基础IO
linux·运维·服务器
躺不平的理查德10 分钟前
让Ubuntu 的第二块网卡 ens37 永久固定为 192.168.30.111
运维·服务器
bitbrowser1 小时前
Facebook 验证反复回到原页面,应该从哪里排查
运维·服务器·facebook
Land03291 小时前
AI网页元素变化无法自动修复?自带元素自愈的自动化解决方案
运维·人工智能·ai·自动化·rpa
fengyehongWorld1 小时前
Jenkins 安装与简单配置
运维·jenkins
云智慧AIOps社区1 小时前
2026 国产化 ITSM 替代指南:横向测评 ServiceNow、轻帆云、Jira等五款主流IT服务管理平台
运维·人工智能·运维开发·it服务管理·itsm平台
虹科网络安全1 小时前
艾体宝新闻|从 SQL 注入到服务器接管:CVE-2026-57517 暴露 Web 管理面板的供应链与安全编码风险
服务器·前端·sql
Urbano2 小时前
针织卫衣全流程生产工序科普:自动化替代、产能优化与设备选型实战方案
运维·自动化
BelongPanda2 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
AC赳赳老秦2 小时前
招投标公开数据自动化采集实战:基于 OpenClaw 的定时抓取与业务关键词精准推送
运维·服务器·数据库·自动化·测试用例·deepseek·openclaw