当前位置: 首页 > news >正文

轻量级可扩展日志框架-日志系统设计思路与前置知识

日志系统设计思路与前置知识

文章目录

  • 日志系统设计思路与前置知识
    • 一、日志系统的基本介绍
      • 日志系统功能
      • 日志系统实现
    • 二、前置基础知识
      • 2.1 C/C++ 不定参数
      • 2.2 设计模式及其六大原则
        • 单一职责原则
        • 开闭原则
        • 李氏替换原则
        • 依赖倒置原则
        • 迪米特法则
        • 接口隔离原则
        • 抽象与实现的设计原则
      • 2.3 单例模式的定义与应用
        • 饿汉方式的实现与特点
        • 懒汉方式的思想与特点
      • 2.4 工厂模式
    • 三、日志系统功能概述与模块划分
      • 3.1 日志系统的模块化设计
        • 日志消息模块与格式化模块功能详解
        • 日志落地模块及其功能实现
        • 日志器管理模块与设计模式应用
      • 3.2 日志系统模块关系与工作流程
    • 四、总结

这是一套C++ 日志系统学习笔记,涵盖同步与异步双模式日志的设计与实现。核心特点:模块化分层(格式→落地→日志器→建造者接口)、双缓冲区生产者-消费者模型、多种设计模式(单例/工厂/代理/建造者)应用、C++11 多线程与智能指针实践,代码可直接在 Linux 下编译测试。

一、日志系统的基本介绍

日志是程序运行过程中记录的状态信息,用于追踪程序每一步的操作和状态变化。日志的存在是为了帮助程序员分析系统运行状况,定位程序出错的位置和原因。在程序调试阶段,程序员通过打印信息来验证程序运行是否符合预期,这些打印信息本质上就是日志。

日志系统功能

日志系统的主要功能是方便用户进行日志输出和控制,包括支持多级别日志消息、同步异步日志输出、可靠写入日志到不同目标以及支持多线程并发写日志等功能。

  • 多级别日志消息:将日志分为调试、提示、警告、错误和致命等级别,不同级别对应不同场景,可以通过设置限制输出级别来控制日志输出量。
  • 同步与异步日志:同步日志由业务线程自己完成日志写入操作,而异步日志则将日志放入缓存,由专门的工作线程负责实际输出,避免因磁盘或数据库问题导致业务线程阻塞。
  • 多目标输出:日志可以输出到控制台、文件或滚动文件中。滚动文件通过限制文件大小或按日期切换来管理日志文件,避免单个文件过大。日志系统还支持扩展,允许用户自定义日志落地方向,如写入数据库或发送到日志分析服务器。
  • 线程安全:日志系统是线程安全的,支持多线程并发写日志而不会产生数据混乱。

日志系统实现

项目开发环境基于 Linux 操作系统,使用 VS Code、G++ 编译器、GDB 调试器和 Makefile 构建工具。项目中应用了类的层次设计和模块化设计思想,大量使用继承和多态。涉及多种设计模式,包括单例模式、工厂模式、代理模式和建造者模式,这些模式在实际开发场景中的应用和优势将被详细讲解。项目还使用了 C++11 特性,如多线程、auto 智能指针和右值引用。采用了双缓冲区设计思想提高异步工作线程的日志处理效率,以及多线程和生产者-消费者模型等技术。


二、前置基础知识

2.1 C/C++ 不定参数

(本节简要介绍 C/C++ 中的不定参数机制,日志系统的日志输出接口需要使用不定参数来接收用户传入的格式化字符串和可变参数。)

C 语言中通过stdarg.h头文件提供的不定参数机制实现可变参数函数,核心宏包括va_listva_startva_argva_end。在日志系统中,日志输出接口(如debuginfoerror等)需要使用不定参数来接收用户传入的格式化字符串和可变参数,最终通过vasprintfvsnprintf将格式化后的字符串作为日志消息内容。

2.2 设计模式及其六大原则

设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路,它不是一种语法规定,而是一种设计思想和设计经验。通过设计模式可以提高代码的复用性、维护性、可读性、稳健性和安全性。设计模式需要遵循六大原则:单一职责原则、开闭原则、李氏替换原则、依赖倒置原则、迪米特法则和接口隔离原则。

单一职责原则

单一职责原则指的是每一个类,它的职责都应该功能单一化,一个类只做一件事情,职责划分清晰明了。每次改动的时候都只改动最小单位的一个类。两个完全不一样的功能,不应该放到同一个类当中,一个类当中应该是一组相关性很高的函数和数据的封装。

例如有一个网络聊天类,里面包含网络通信和聊天两个功能,这两个功能完全不一样,如果放到同一个类中会导致改动时出现问题。应该将其分割成为一个网络通信类和一个聊天类,分开后改变通信方式时只需要替换网络通信类所实例化的对象即可。

开闭原则

开闭原则指的是对拓展开放,对修改封闭。在对一个实体进行改动的时候,最好使用扩展而非修改,即不修改原本的内容,而是添加新的内容进来。例如在超市中,商品都有一个价格,当超市搞大促销时,商品价格下降,但并不是修改商品的原价格,而是在原价格的价签上新增一个促销标签,显示促销价格。促销完毕后,只需去掉促销价格即可恢复原价,而不是对原来的内容进行修改。开闭原则强调不要去修改原有的内容,而是去添加新的功能进来。

李氏替换原则

李氏替换原则指的是只要是父类能够出现的地方,子类就可以出现,而且替换为子类也不会产生任何的错误或者异常。子类必须完全实现父类的方法,并且子类可以有自己的个性,覆盖或实现父类方法的时候,输入参数可以被放大,输出可以被缩小。例如有一个跑步运动员类,会跑步即可,在其基础上实现一个子类长跑运动员类,首先实现父类的会跑步功能,同时还有自己擅长长跑的特性。另一个子类短跑运动员类也实现了父类的会跑步功能,同时擅长短跑。任何用到跑步运动员对象的地方,都可以将其替换为长跑运动员对象或短跑运动员对象,因为它们都可以实现跑步功能,可以完全替换。

依赖倒置原则

依赖倒置原则描述的是高层模块不应该依赖低层模块,两者都应该依赖其抽象。每个类都应该尽量有一个抽象类,并且任何类都不应该从一个具体类去派生。例如有一个奔驰车司机类,只能开奔驰车,不能开宝马,这时需要对奔驰车司机进行抽象,改为司机类,司机类会开车,给什么车就开什么车。任何用到司机的地方都是使用司机类,而不是具体的奔驰车司机类。

迪米特法则

迪米特法则又叫做最少知道法则,指的是尽量减少对象之间的交互,从而减少类之间的耦合。一个对象应该对其他对象有最少的了解,只和直接的朋友交流。例如老师让班长点名,老师给班长一个名单,班长完成点名并将名单交给老师,而不是班长直接对学生点名,老师勾选,这样会导致三个类耦合在一起。迪米特法则强调减少类间关系,降低类之间的耦合度。

接口隔离原则

接口隔离原则指的是客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口之上。

抽象与实现的设计原则

用抽象来构建框架,用实现来扩展细节。类之间的整体关系应该用抽象来描述,具体细节由具体实现来完善。

每条设计原则对应注意事项:单一原则要求每个类职责单一;李氏替换原则要求不破坏继承体系,子类应实现父类所有功能;依赖倒置原则要求面向接口编程,依赖关系通过抽象完成;接口隔离原则要求设计接口时精简单一;迪米特法则要求降低耦合度;开闭原则是总纲,要求设计时扩展开放,修改关闭。

2.3 单例模式的定义与应用

单例模式指一个类只能创建一个对象,保证系统该类只有一个实例并提供全局访问点,该实例能被所有程序模块共享使用(前提是包含对应的头文件)。单例模式有两种实现方式:饿汉方式和懒汉方式。

饿汉方式的实现与特点

饿汉方式在程序启动时创建唯一实例对象,以空间换时间,资源不管用不用都在程序启动时实例化好,使用时直接可用。好处是用时方便,缺点是增加程序初始化时间。饿汉方式适用于多线程环境,对象实例化不需加锁保护,能有效避免资源竞争提高程序性能。实现时需将构造函数私有化,析构函数内部实现,删除拷贝构造函数,提供静态全局访问接口返回引用。类内静态成员需在类外定义和实例化。

懒汉方式的思想与特点

懒汉方式采用懒加载即延迟加载思想,对象到用时才实例化而非程序启动时。优点是不用时不占用资源,缺点是首次实例化较耗时。懒汉方式与饿汉方式各有优缺点,前者节省资源但首次使用慢,后者使用方便但初始化耗时。

通过加锁实现的懒汉模式:在实现时定义对象时不直接实例化,而是定义静态资源指针,在访问接口时检查指针为空则进行 new 操作创建对象。这种方式存在线程安全问题,加锁会增加锁冲突导致串行化执行效率降低。为解决这个问题引入 double check 二次检测机制,在外层增加检测层。

classSingleton{public:staticSingleton*GetInstance(){// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全if(nullptr==m_pInstance){m_mtx.lock();if(nullptr==m_pInstance){m_pInstance=newSingleton();}m_mtx.unlock();}returnm_pInstance;}// 实现一个内嵌垃圾回收类classCGarbo{public:CGarbo(){if(Singleton::m_pInstance)deleteSingleton::m_pInstance;}};// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象staticCGarbo Garbo;private:// 构造函数私有Singleton(){};// 防拷贝Singleton(Singletonconst&);Singleton&operator=(Singletonconst&);staticSingleton*m_pInstance;// 单例对象指针staticmutex m_mtx;// 互斥锁};Singleton*Singleton::m_pInstance=nullptr;Singleton::CGarbo Garbo;mutex Singleton::m_mtx;

使用静态局部变量实现:C++11 开始静态局部变量的线程安全性得到保证,文档明确指出多个线程同时初始化同一静态局部变量时初始化只执行一次,其他线程会等待初始化完成。这种实现方式相比传统方法更加简洁优雅,是 Effective C++ 书中推荐的单例模式实现方式。

template<typenameT>classSingleton{private:Singleton(){}~Singleton(){}public:Singleton(constSingleton&)=delete;Singleton&operator=(constSingleton&)=delete;staticT&getInstance(){staticSingleton _eton;return_eton;}};

2.4 工厂模式

(工厂模式是一种创建型设计模式,将对象的创建和使用分离。)

在日志系统中,日志落地模块支持多种不同的落地方向(标准输出、文件输出、滚动文件输出等),这些落地对象的创建通过工厂模式来管理。工厂模式将对象的创建逻辑封装在一个工厂类中,客户端只需要告诉工厂需要什么类型的对象,工厂负责实例化并返回。这样当需要新增落地方向时,只需添加新的落地类并在工厂中注册即可,无需修改客户端的代码,符合开闭原则。在代码实现中,日志系统采用了模板工厂的方式,通过不定参数模板支持不同构造参数的落地对象创建。


三、日志系统功能概述与模块划分

日志系统的主要作用是将消息格式化为指定格式的字符串后写入指定位置,目前包括标准输出、指定文件和滚动文件三种方式,并支持扩展到数据库和远程服务器。

日志系统支持同步和异步两种写入方式,同步方式由业务线程负责写入,流程简单但可能阻塞;异步方式由业务线程将日志放入缓冲区,由其他线程负责写入,避免业务线程阻塞。

日志系统支持多日志器模式,不同项目组可以使用不同的输出策略。

日志系统需要实现日志等级模块,枚举日志的不同等级(调试、提示、警告、错误、致命错误),以便控制输出哪些等级的日志。日志消息模块封装日志所需的各种要素,包括时间、线程 ID、文件名、行号、消息主体和日志等级,便于日志分析。

3.1 日志系统的模块化设计

日志消息模块与格式化模块功能详解
  • 日志消息模块对日志的各项要素进行封装,包括时间、线程 id、日志等级、日志数据、文件名和行号等信息。
  • 日志消息模块之后是消息格式化模块,该模块按照指定格式对日志消息的关键要素进行组织,最终得到指定格式的字符串。格式化字符包括:
    • %d{%H:%M:%S}:表示日期时间,花括号中的内容表示日期时间的格式。
    • %T:表示制表符缩进。
    • %t:表示线程 ID。
    • %p:表示日志级别。
    • %c:表示日志器名称,不同的开发组可以创建自己的日志器进行日志输出,小组之间互不影响。
    • %f:表示日志输出时的源代码文件名。
    • %l:表示日志输出时的源代码行号。
    • %m:表示给予的日志有效载荷数据。
    • %n:表示换行。
日志落地模块及其功能实现
  • 日志落地模块负责对日志消息进行写入输出,支持标准输出、指定文件和滚动文件三种落地方向,并且支持扩展其他方向。该模块的功能是对日志消息进行指定方向的写入输出。日志落地模块之后是日志器模块,该模块是对前面几个模块的整合,包含消息格式化对象、日志落地对象和日志输出等级限制。
  • 日志器模块分为同步日志器和异步日志器,同步日志器完成日志的同步输出功能,异步日志器将日志放到指定位置由其他线程完成输出。异步线程模块负责异步日志的实际落地输出功能,异步日志器在日志输出时只是把日志放到内存里,异步线程模块把内存里的日志取出并实际写入文件。
日志器管理模块与设计模式应用

为了便于管理日志器和在项目任何位置获取指定日志器进行日志输出,设计了单例的日志器管理模块,该模块对日志器进行全局管理。在日志器的生成中使用建造者模式,在日志落地模块中使用工厂模式生产不同的落地对象,在日志输出中使用代理模式通过宏来代理输出。这些设计模式的应用使得日志系统更加灵活和可扩展。

3.2 日志系统模块关系与工作流程

日志器模块提供 debug、info、warning、error 和 fatal 等级日志的输出接口,派生出同步日志器(SyncLogger)和异步日志器(AsyncLogger)。异步日志器包含异步线程(AsyncLooper)子模块,负责将日志池中的日志实际输出。每个日志器都有日志等级限制信息。通过接口输出日志时会封装出包含关键要素的日志消息,如果日志等级符合要求,则使用格式化模块对日志消息进行格式化组织,得到格式化字符串。同步日志直接进行落地,有三种不同方向;异步日志将格式化后的消息放到异步任务池中,由 AsyncLooper 线程负责将日志池中的数据实际落地,这样业务线程不会阻塞。


四、总结

本文介绍了日志系统的基本概念和整体设计思路,详细讲解了六大设计原则以及单例模式、工厂模式等核心设计模式。在后续文章中,我们将逐步进入代码实现阶段,从实用工具类开始,一步步构建一个功能完备的同步异步日志系统。

http://www.cnnetsun.cn/news/3152165.html

相关文章:

  • 2026笔记本避坑指南:低色域屏、8GB内存、赛扬CPU为何成体验地雷
  • 奔驰曲轴皮带盘脱层,A级/GLA/GLB异响的来源
  • 抖音批量下载工具完全指南:3大核心功能+4步配置方案,轻松实现无水印视频下载与智能管理
  • 老字号书法国画班,手残党也能变大师![特殊字符]✨
  • Boss-Key老板键:如何在3分钟内掌握一键隐藏窗口的终极隐私保护技巧
  • 抽奖页高频查询优化:Redis 如何缓存活动详情和中奖记录
  • AI智能体:未来已来的数字分身,你准备好了吗?
  • DsHidMini:三步让PS3手柄在Windows上完美重生的终极指南
  • Power BI中替代Excel COUNTIF的DAX计数逻辑
  • Trilium中文版终极指南:免费开源笔记软件如何彻底改变你的知识管理
  • 【设计原则和建议】 方法
  • 如何3分钟为Windows和Linux安装精美macOS光标主题:免费开源桌面美化终极指南
  • 再回到技术面,研究 T-SQL 的 UNION、EXISTS、EXCEPT、INTERSECT 运算符。
  • freerots接口代码示例
  • 《通信电子线路》全套PPT课件
  • OpenClaw 2.7.9 搭建实操,桌面自动化工具避坑完整流程
  • 怎样在5分钟内免费将图片转换为SVG矢量图形:SVGcode实用指南
  • DiffuMeta:基于代数语言与扩散Transformer的3D超材料AI设计
  • 短视频穿搭性别偏好分析程序,区分男女用户对潮流色彩,版型的不同偏好。
  • 5个简单步骤:在Windows上解锁Apple触控板的完整功能
  • 开题撰写告别反复改稿,okbiye 一站式 AI 开题报告创作功能深度解析
  • 告别命令行恐惧:3分钟学会用Crontab UI可视化管理Linux定时任务
  • SciPy L-BFGS-B 实战:3个关键参数调优与收敛速度对比分析
  • 美团 Leaf-snowflake 分布式 ID 生成器 k8s 改造的想法
  • 164、PCIE在VMware中的虚拟化:当硬件变成“软件定义”
  • 解决层高、角柱难题!抚州美伦熙语别墅土建井道定制曳引电梯实录
  • Unitree RL Gym:四足机器人强化学习框架完全指南
  • 轻量级AI智能体:安全、场景与硬件穿透的工程实践
  • AI绘画本地插件部署指南:实现“指哪改哪”的精准图像编辑
  • 终极指南:如何3步免费下载百度文库文档(开源脚本完整教程)