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

📅 2026/7/5 4:48:42
轻量级可扩展日志框架-日志系统设计思路与前置知识
日志系统设计思路与前置知识文章目录日志系统设计思路与前置知识一、日志系统的基本介绍日志系统功能日志系统实现二、前置基础知识2.1 C/C 不定参数2.2 设计模式及其六大原则单一职责原则开闭原则李氏替换原则依赖倒置原则迪米特法则接口隔离原则抽象与实现的设计原则2.3 单例模式的定义与应用饿汉方式的实现与特点懒汉方式的思想与特点2.4 工厂模式三、日志系统功能概述与模块划分3.1 日志系统的模块化设计日志消息模块与格式化模块功能详解日志落地模块及其功能实现日志器管理模块与设计模式应用3.2 日志系统模块关系与工作流程四、总结这是一套C 日志系统学习笔记涵盖同步与异步双模式日志的设计与实现。核心特点模块化分层格式→落地→日志器→建造者接口、双缓冲区生产者-消费者模型、多种设计模式单例/工厂/代理/建造者应用、C11 多线程与智能指针实践代码可直接在 Linux 下编译测试。一、日志系统的基本介绍日志是程序运行过程中记录的状态信息用于追踪程序每一步的操作和状态变化。日志的存在是为了帮助程序员分析系统运行状况定位程序出错的位置和原因。在程序调试阶段程序员通过打印信息来验证程序运行是否符合预期这些打印信息本质上就是日志。日志系统功能日志系统的主要功能是方便用户进行日志输出和控制包括支持多级别日志消息、同步异步日志输出、可靠写入日志到不同目标以及支持多线程并发写日志等功能。多级别日志消息将日志分为调试、提示、警告、错误和致命等级别不同级别对应不同场景可以通过设置限制输出级别来控制日志输出量。同步与异步日志同步日志由业务线程自己完成日志写入操作而异步日志则将日志放入缓存由专门的工作线程负责实际输出避免因磁盘或数据库问题导致业务线程阻塞。多目标输出日志可以输出到控制台、文件或滚动文件中。滚动文件通过限制文件大小或按日期切换来管理日志文件避免单个文件过大。日志系统还支持扩展允许用户自定义日志落地方向如写入数据库或发送到日志分析服务器。线程安全日志系统是线程安全的支持多线程并发写日志而不会产生数据混乱。日志系统实现项目开发环境基于 Linux 操作系统使用 VS Code、G 编译器、GDB 调试器和 Makefile 构建工具。项目中应用了类的层次设计和模块化设计思想大量使用继承和多态。涉及多种设计模式包括单例模式、工厂模式、代理模式和建造者模式这些模式在实际开发场景中的应用和优势将被详细讲解。项目还使用了 C11 特性如多线程、auto 智能指针和右值引用。采用了双缓冲区设计思想提高异步工作线程的日志处理效率以及多线程和生产者-消费者模型等技术。二、前置基础知识2.1 C/C 不定参数本节简要介绍 C/C 中的不定参数机制日志系统的日志输出接口需要使用不定参数来接收用户传入的格式化字符串和可变参数。C 语言中通过stdarg.h头文件提供的不定参数机制实现可变参数函数核心宏包括va_list、va_start、va_arg、va_end。在日志系统中日志输出接口如debug、info、error等需要使用不定参数来接收用户传入的格式化字符串和可变参数最终通过vasprintf或vsnprintf将格式化后的字符串作为日志消息内容。2.2 设计模式及其六大原则设计模式是前辈们对代码开发经验的总结是解决特定问题的一系列套路它不是一种语法规定而是一种设计思想和设计经验。通过设计模式可以提高代码的复用性、维护性、可读性、稳健性和安全性。设计模式需要遵循六大原则单一职责原则、开闭原则、李氏替换原则、依赖倒置原则、迪米特法则和接口隔离原则。单一职责原则单一职责原则指的是每一个类它的职责都应该功能单一化一个类只做一件事情职责划分清晰明了。每次改动的时候都只改动最小单位的一个类。两个完全不一样的功能不应该放到同一个类当中一个类当中应该是一组相关性很高的函数和数据的封装。例如有一个网络聊天类里面包含网络通信和聊天两个功能这两个功能完全不一样如果放到同一个类中会导致改动时出现问题。应该将其分割成为一个网络通信类和一个聊天类分开后改变通信方式时只需要替换网络通信类所实例化的对象即可。开闭原则开闭原则指的是对拓展开放对修改封闭。在对一个实体进行改动的时候最好使用扩展而非修改即不修改原本的内容而是添加新的内容进来。例如在超市中商品都有一个价格当超市搞大促销时商品价格下降但并不是修改商品的原价格而是在原价格的价签上新增一个促销标签显示促销价格。促销完毕后只需去掉促销价格即可恢复原价而不是对原来的内容进行修改。开闭原则强调不要去修改原有的内容而是去添加新的功能进来。李氏替换原则李氏替换原则指的是只要是父类能够出现的地方子类就可以出现而且替换为子类也不会产生任何的错误或者异常。子类必须完全实现父类的方法并且子类可以有自己的个性覆盖或实现父类方法的时候输入参数可以被放大输出可以被缩小。例如有一个跑步运动员类会跑步即可在其基础上实现一个子类长跑运动员类首先实现父类的会跑步功能同时还有自己擅长长跑的特性。另一个子类短跑运动员类也实现了父类的会跑步功能同时擅长短跑。任何用到跑步运动员对象的地方都可以将其替换为长跑运动员对象或短跑运动员对象因为它们都可以实现跑步功能可以完全替换。依赖倒置原则依赖倒置原则描述的是高层模块不应该依赖低层模块两者都应该依赖其抽象。每个类都应该尽量有一个抽象类并且任何类都不应该从一个具体类去派生。例如有一个奔驰车司机类只能开奔驰车不能开宝马这时需要对奔驰车司机进行抽象改为司机类司机类会开车给什么车就开什么车。任何用到司机的地方都是使用司机类而不是具体的奔驰车司机类。迪米特法则迪米特法则又叫做最少知道法则指的是尽量减少对象之间的交互从而减少类之间的耦合。一个对象应该对其他对象有最少的了解只和直接的朋友交流。例如老师让班长点名老师给班长一个名单班长完成点名并将名单交给老师而不是班长直接对学生点名老师勾选这样会导致三个类耦合在一起。迪米特法则强调减少类间关系降低类之间的耦合度。接口隔离原则接口隔离原则指的是客户端不应该依赖它不需要的接口类间的依赖关系应该建立在最小的接口之上。抽象与实现的设计原则用抽象来构建框架用实现来扩展细节。类之间的整体关系应该用抽象来描述具体细节由具体实现来完善。每条设计原则对应注意事项单一原则要求每个类职责单一李氏替换原则要求不破坏继承体系子类应实现父类所有功能依赖倒置原则要求面向接口编程依赖关系通过抽象完成接口隔离原则要求设计接口时精简单一迪米特法则要求降低耦合度开闭原则是总纲要求设计时扩展开放修改关闭。2.3 单例模式的定义与应用单例模式指一个类只能创建一个对象保证系统该类只有一个实例并提供全局访问点该实例能被所有程序模块共享使用前提是包含对应的头文件。单例模式有两种实现方式饿汉方式和懒汉方式。饿汉方式的实现与特点饿汉方式在程序启动时创建唯一实例对象以空间换时间资源不管用不用都在程序启动时实例化好使用时直接可用。好处是用时方便缺点是增加程序初始化时间。饿汉方式适用于多线程环境对象实例化不需加锁保护能有效避免资源竞争提高程序性能。实现时需将构造函数私有化析构函数内部实现删除拷贝构造函数提供静态全局访问接口返回引用。类内静态成员需在类外定义和实例化。懒汉方式的思想与特点懒汉方式采用懒加载即延迟加载思想对象到用时才实例化而非程序启动时。优点是不用时不占用资源缺点是首次实例化较耗时。懒汉方式与饿汉方式各有优缺点前者节省资源但首次使用慢后者使用方便但初始化耗时。通过加锁实现的懒汉模式在实现时定义对象时不直接实例化而是定义静态资源指针在访问接口时检查指针为空则进行 new 操作创建对象。这种方式存在线程安全问题加锁会增加锁冲突导致串行化执行效率降低。为解决这个问题引入 double check 二次检测机制在外层增加检测层。classSingleton{public:staticSingleton*GetInstance(){// 注意这里一定要使用Double-Check的方式加锁才能保证效率和线程安全if(nullptrm_pInstance){m_mtx.lock();if(nullptrm_pInstance){m_pInstancenewSingleton();}m_mtx.unlock();}returnm_pInstance;}// 实现一个内嵌垃圾回收类classCGarbo{public:CGarbo(){if(Singleton::m_pInstance)deleteSingleton::m_pInstance;}};// 定义一个静态成员变量程序结束时系统会自动调用它的析构函数从而释放单例对象staticCGarbo Garbo;private:// 构造函数私有Singleton(){};// 防拷贝Singleton(Singletonconst);Singletonoperator(Singletonconst);staticSingleton*m_pInstance;// 单例对象指针staticmutex m_mtx;// 互斥锁};Singleton*Singleton::m_pInstancenullptr;Singleton::CGarbo Garbo;mutex Singleton::m_mtx;使用静态局部变量实现C11 开始静态局部变量的线程安全性得到保证文档明确指出多个线程同时初始化同一静态局部变量时初始化只执行一次其他线程会等待初始化完成。这种实现方式相比传统方法更加简洁优雅是 Effective C 书中推荐的单例模式实现方式。templatetypenameTclassSingleton{private:Singleton(){}~Singleton(){}public:Singleton(constSingleton)delete;Singletonoperator(constSingleton)delete;staticTgetInstance(){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 线程负责将日志池中的数据实际落地这样业务线程不会阻塞。四、总结本文介绍了日志系统的基本概念和整体设计思路详细讲解了六大设计原则以及单例模式、工厂模式等核心设计模式。在后续文章中我们将逐步进入代码实现阶段从实用工具类开始一步步构建一个功能完备的同步异步日志系统。