【Linux】日志模块实现详解

news2025/4/6 12:55:34

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 📢前言
  • 🏳️‍🌈一、为什么要设计日志系统
  • 🏳️‍🌈二、日志系统逻辑框架
  • 🏳️‍🌈三、关键技术实现解析
    • 3.1 时间戳生成
    • 3.2 日志等级管理
  • 🏳️‍🌈四、日志输出策略
    • 4.1 LogStrategy
    • 4.2 ConsoleLogStrategy
    • 4.3FileLogStrategy
  • 🏳️‍🌈五、日志类
    • 5.1 日志消息构建
    • 5.2 日志类封装
    • 5.3 用户接口
  • 🏳️‍🌈六、整体代码
  • 👥总结


📢前言

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


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

什么是设计模式

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

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

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

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

以下几个指标是可选的

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

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

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

[可读性很好的时间] [⽇志等级] [进程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)。
  • 日志输出策略:支持控制台输出、文件持久化等多种方式。
  • 线程安全:多线程环境下保证日志写入的原子性。
  • 易用性:通过宏定义简化调用。
    在这里插入图片描述
+------------------+
|     Logger        |  --> 管理策略、构建日志消息
+------------------+
       |
       | 使用策略模式
       v
+------------------+
|  LogStrategy      |  --> 抽象接口(控制台/文件输出)
+------------------+
       |       |
       |       +--------> ConsoleLogStrategy
       |       +--------> FileLogStrategy
       v
+------------------+
|  LogMessage       |  --> 封装单条日志的完整信息
+------------------+

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

3.1 时间戳生成

下图是 struct_tm 结构的示意,他能帮助我们获取当前时间
在这里插入图片描述
关键点:使用localtime_r替代localtime保证线程安全。
​输出格式:YYYY-MM-DD HH:MM:SS,便于人类阅读和机器解析

// 获取一下当前系统的时间
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 日志等级管理

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

    // 日志等级
    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

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

4.2 ConsoleLogStrategy

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

// 日志控制台输出策略
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

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

  • 首先,我们需要确定这个日志将要追加的位置,可以提前默认
  • 其实,我们要对文件的创建、打开、关闭负责好,及时在错误的情况输出
  • 最后,我们要做好往文件追加的功能实现
// 默认日志文件地址和名字
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;)

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

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

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

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 日志类封装

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

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 用户接口

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

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

🏳️‍🌈六、整体代码

#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】日志模块实现详解 做了一个较为详细的介绍,不知道对你有没有帮助呢

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

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2329236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础:面向对象高级(四)

内部类&#xff08;类中五大成分之一&#xff09; 四种形式 成员内部类【了解】 静态内部类【了解】 局部内部类【了解】 匿名内部类【重点】 枚举 泛型 什么是泛型 泛型类-模拟ArrayList 泛型接口-操作学生&#xff0c;老师增删改查 泛型方法 泛型擦除和注意事项

easy-poi 一对多导出

1. 需求&#xff1a; 某一列上下两行单元格A,B值一样且这两个单元格&#xff0c; 前面所有列对应单元格值一样的话&#xff0c; 就对A,B 两个单元格进行纵向合并单元格 1. 核心思路&#xff1a; 先对数据集的国家&#xff0c;省份&#xff0c;城市...... id 身份证进行排序…

python通过调用海康SDK打开工业相机(全流程)

首先打开海康机器人-机器视觉-下载中心 下载最新版的 MVS 安装后打开目录找到 ...\MVS\Development\Samples\Python 将MvImport内所有文件拷贝至工作目录 然后到 C:\Program Files (x86)\Common Files\MVS\Runtime 找到适合自己系统的版本&#xff0c;将整个文件夹拷贝至工…

manim,制作专业的数学公式动画

manim是一个Python第三方库,全称是mathematical animation engine(数学动画引擎)。manim用于解说线性代数、微积分、神经网络、黎曼猜想、傅里叶变换以及四元数等数学概念。 manim使你能够以编程的方式创建精确的数学图形、动画和场景。与传统的几何画板等绘图软件不同,man…

小刚说C语言刷题——第15讲 多分支结构

1.多分支结构 所谓多分支结构是指在选择的时候有多种选择。根据条件满足哪个分支&#xff0c;就走对应分支的语句。 2.语法格式 if(条件1) 语句1; else if(条件2) 语句2; else if(条件3) 语句3; ....... else 语句n; 3.示例代码 从键盘输入三条边的长度&#xff0c;…

[ctfshow web入门] web6

前置知识 入口点(目录)爆破 还记得之前说过网站的入口的吗&#xff0c;我们输入url/xxx&#xff0c;其中如果url/xxx存在&#xff0c;那么访问成功&#xff0c;证明存在这样一个入口点&#xff1b;如果访问失败则证明不存在此入口点。所以我们可以通过遍历url/xxx&#xff0c;…

简单程序语言理论与编译技术·22 实现一个从AST到RISCV的编译器

本文是记录专业课“程序语言理论与编译技术”的部分笔记。 LECTURE 22&#xff08;实现一个从AST到RISCV的编译器&#xff09; 一、问题分析 1、完整的编译器&#xff08;如LLVM&#xff09;需先完成AST到IR的转换&#xff0c;并进行代码优化&#xff0c;再到汇编&#xff0…

lua和C的交互

1.C调用lua例子 #include <iostream> #include <lua.hpp>int main() {//用于创建一个新的lua虚拟机lua_State* L luaL_newstate();luaL_openlibs(L);//打开标准库/*if (luaL_dofile(L, "test.lua") ! LUA_OK) {std::cerr << "Lua error: &…

Css:如何解决绝对定位子元素内容被父级元素overflow:hidden属性剪裁

一、问题描述 今天小伙伴提了一个bug&#xff0c;在点击列表项的“…”按钮应该出现的悬浮菜单显示不完整&#xff1a; 二、问题排查 一般这种问题&#xff0c;是由于悬浮菜单采用的是绝对定位&#xff0c;而父级采用了overflow:hidden属性。但需要注意的是&#xff0c;这里的…

RoMo: Robust Motion Segmentation Improves Structure from Motion

前言 看起来像是一篇投稿CVPR的文章&#xff0c;不知道被哪个瞎眼审稿人拒了。同期还有一篇CVPR被接收的工作Segment Any Motion in Videos&#xff0c;看起来不如这篇直白&#xff08;也可能是因为我先看过spotlesssplats的缘故&#xff09;&#xff0c;后面也应该一并介绍了…

MCP 极简入门 - 三分钟 Cline + Smithery 运行 time 服务

文章目录 一、&#x1f680; 初识Smithery&#xff1a;AI服务的新大陆找到心仪的服务 二、Cline 编辑配置文件&#x1f527;1、打开配置文件2. 添加Time Server配置3. 验证配置效果 三、&#x1f4ac; 实战对话&#xff1a;让AI告诉你时间四、服务管理小技巧&#x1f504;&…

基本机动飞行性能

机动飞行时描述飞机在给定构型和发动机工作状态下改变飞行速度、飞行高度和飞行方向的能力 1. 水平加&#xff08;减&#xff09;速 水平加&#xff08;减&#xff09;速性能反映飞机在水平面内改变直线飞行速度的能力。描述水平加&#xff08;减&#xff09;速性能的参数包括…

【Linux】进程间通信、匿名管道、进程池

一.什么是通信 进程间通信(Inter-Process Communication&#xff0c;IPC),是指在操作系统中&#xff0c;不同进程之间进行数据交换和同步的机制。由于每个进程通常拥有独立的内存空间&#xff0c;进程间无法直接访问对方的内存&#xff0c;因此需要通过特定的机制来实现通信和…

【MATLAB定位例程】TDOA(到达时间差)的chan-tylor,三维环境,附完整代码

该代码实现了基于三维空间的动态目标TDOA定位,结合了Chan算法(解析解)与Taylor级数展开法(迭代优化)的双重优势。 文章目录 运行结果MATLAB代码代码讲解代码功能概述核心算法原理代码结构解析可视化与结果分析运行结果 定位示意图: 三轴状态曲线: 三轴误差曲线: MA…

数字化转型中的开源AI智能客服与S2B2C商城小程序的融合创新

摘要 数字经济时代&#xff0c;企业需通过技术重构用户交互与供应链体系。本文以“开源AI智能客服”“AI智能名片”及“S2B2C商城小程序”为核心&#xff0c;研究三者如何通过技术协同与场景化应用实现企业营销、客户服务与供应链管理的智能化升级。通过案例分析、技术架构设…

重生之我是去噪高手——diffusion model

diffusion model是如何运作的&#xff1f; 想象一下&#xff0c;你有一张清晰的图片。扩散模型的核心思想分为两个过程&#xff1a; 前向过程&#xff08;Forward Process / Diffusion Process&#xff09;&#xff1a;逐步加噪反向过程&#xff08;Reverse Process / Denois…

【C#】.net core 6.0 依赖注入常见问题之一,在构造函数使用的类,都需要注入到容器里,否则会提示如下报错,让DeepSeek找找原因,看看效果

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…

论文阅读笔记——RDT-1B: A DIFFUSION FOUNDATION MODEL FOR BIMANUAL MANIPULATION

RDT-1B 论文 模型表达与泛化能力&#xff1a;由于双臂操作中动作空间维度是单臂空间的两倍&#xff0c;传统方法难以建模其多模态分布。 数据&#xff1a;双臂数据少且不同机器人的物理结构和动作空间差异&#xff08;如关节数、运动范围&#xff09;导致数据分布不一致&#x…

Vue中将pdf文件转为图片

平时开发中,我们经常遇到的场景应该是调用后端接口返回给前端pdf格式的文件流,然后我们可以通过URL.createObjectURL的方式转为object url临时路径然后可以通过window.open的方式来打开一个新的浏览器页签来进行预览,效果如下图: 但有时候这样满足不了的需求,它不想这样预…

day39——输入操作:多值输入

数组输入&#xff1a; int main() {//***** 1、多值输入&#xff08;C&#xff09;/*输入&#xff1a;3 --> 3个值5 4 9*/int n;cin >> n; //输入个数const int MAX_SIZE 0xFFFF;//限定最大个数int a[MAX_SIZE];for (int i 0; i < n; i) {//用 n 作控制输入…