【C++篇】:LogStorm——基于多设计模式下的同步异步高性能日志库项目
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:c++篇–CSDN博客
文章目录
- C++ - 基于多设计模式下的同步&异步高性能日志库
- 1. 项目介绍
- 2. 开发环境
- 3. 核心技术
- 4. 核心功能
- 5. 系统框架设计
- 5.1 实用工具模块 (`util.hpp`)
- 5.2 日志等级模块 (`level.hpp`)
- 5.3 日志消息模块 (`message.hpp`)
- 5.4 日志格式化模块 (`format.hpp`)
- 5.5 日志落地模块 (`sink.hpp`)
- 5.6 日志器模块 (`logger.hpp`)
- 5.7 日志器管理模块 (`logger.hpp`)
- 5.8 缓冲区模块 (`buffer.hpp`)
- 5.9 异步工作器模块 (`looper.hpp`)
- 5.10 全局接口模块 (`mylog.hpp`)
- 6. 总结
C++ - 基于多设计模式下的同步&异步高性能日志库
1. 项目介绍
该项目是一个基于 C++17 标准实现的现代化、高性能、线程安全的日志库。
项目链接:https://gitee.com/zhang-minghui0503/log
它旨在为高并发、低延迟的后端服务提供一个易于扩展、性能卓越且安全可靠的日志解决方案。该日志库核心功能包括:
- 同步/异步双模式灵活切换
- 线程安全的日志记录接口
- 高度可定制的日志格式
- 多样化的日志落地策略
- 优雅停机以保证数据完整性
2. 开发环境
该项目在以下环境中开发与测试,核心工具有:
- 操作系统:
Ubuntu 22.04
- 一个稳定、高效且广受开发者欢迎的 Linux 发行版,为 C++ 开发提供了良好的原生支持。
- IDE:
Visual Studio Code
- 一款轻量级且功能强大的代码编辑器,通过丰富的 C++ 扩展,提供了代码补全、语法高亮、集成调试等一站式开发体验。
- 编译器与调试器:
g++
/gdb
- 使用 GNU Compiler Collection (GCC) 中的 g++作为项目的主要编译器,利用其对 C++17 标准的全面支持。GDB 则作为主要的后端调试工具。
- 版本控制:
Git
- 采用 Git 进行代码的版本控制与分支管理,确保了开发过程的可追溯性和团队协作的便利性。
- 构建系统:
CMake
- 一款跨平台的自动化构建工具,用于管理项目的编译过程,简化了在不同环境下的构建与部署流程。
3. 核心技术
该项目深度融合了现代 C++ 的关键特性与经典设计思想,旨在打造一个模块化、高内聚、易于扩展的日志库。
-
现代 C++ 特性:
- C++17: 利用
inline
静态成员变量特性,优雅地解决了在头文件内初始化静态成员变量时可能遇到的 ODR (单一定义规则) 问题。 - C++11: 全面采用智能指针 (
shared_ptr
,unique_ptr
) 进行自动资源管理 (RAII),并广泛使用多线程库 (thread
,mutex
)、Lambda 表达式、以及完美转发等特性来构建核心功能。
- C++17: 利用
-
面向对象设计:
- 通过抽象基类 (
Logger
,LogSink
,FormatItem
) 与继承、多态,构建了清晰的类层次结构,实现了高度的灵活性和可扩展性。
- 通过抽象基类 (
-
并发模型:
- 异步模式基于“生产者-消费者”模型与“双缓冲”技术实现,旨在最大化业务线程的执行效率。
-
设计模式:
- 项目中广泛运用了单例、工厂、建造者、策略等多种设计模式,以解耦模块、简化对象创建并提升系统的可维护性。
4. 核心功能
该日志库提供了三种日志落地策略与两种核心工作模式,以满足不同场景下的需求。
-
日志落地策略:
- 控制台落地 (
StdcoutSink
): 将日志输出至标准控制台。 - 文件落地 (
FileSink
): 将日志写入指定的单个文件。 - 滚动文件落地 (
RollSink
): 根据文件大小自动进行日志文件的切分与滚动。
- 控制台落地 (
-
日志记录模式:
- 同步模式: 调用日志接口的业务线程将直接负责完成所有I/O操作,适用于简单或对性能要求不高的场景。
- 异步模式: 业务线程仅将日志消息推入内存缓冲区便立即返回,由专门的后台线程负责后续的I/O操作,适用于对性能和延迟敏感的高并发环境。
5. 系统框架设计
该日志库采用高内聚、低耦合的模块化设计思想,将复杂的日志系统拆分为以下几个职责明确的核心模块。
5.1 实用工具模块 (util.hpp
)
提供了一系列基础的、与平台相关的辅助功能,主要包括:
- 时间获取: 封装了获取当前系统时间的接口。
- 文件系统操作: 提供了一系列静态方法,用于判断路径是否存在、从完整路径中提取目录,以及递归创建目录等。
5.2 日志等级模块 (level.hpp
)
定义了日志的优先级,用于过滤和标识不同重要性的日志信息。共分为以下等级:
DEBUG
: 用于开发和调试过程中的代码追踪。INFO
: 用于记录系统运行状态的关键信息。WARNING
: 表示潜在的问题或异常,但程序仍可正常运行。ERROR
: 表示发生了错误,影响了部分功能的正常执行。FATAL
: 表示发生了严重错误,导致程序无法继续运行。OFF
: 关闭所有日志输出。
5.3 日志消息模块 (message.hpp
)
logmsg
类是一个核心的数据结构,它封装了一条日志产生时的所有上下文信息,是模块间传递数据的载体。其主要成员包括:
- 日志时间戳: 日志创建的精确时间。
- 日志等级: 该条日志的
loglevel
。 - 源文件名与行号: 记录日志的代码位置。
- 线程 ID: 打印该日志的线程标识。
- 日志器名称: 所使用的
Logger
的名称。 - 日志主体消息: 开发者传入的、真正的日志内容。
5.4 日志格式化模块 (format.hpp
)
该模块负责将 logmsg
对象,根据用户自定义的格式化规则,转换成最终的字符串。
- 核心设计: 采用了经典的策略模式。
Formatter
作为上下文(Context),持有一系列FormatItem
抽象策略接口的派生类对象。每个FormatItem
派生类对象都封装了一种特定的格式化算法(策略),负责处理一种格式化子项。 - 格式化规则字符:
%d
: 日期时间,支持子格式,如%d{%H:%M:%S}
。%p
: 日志级别。%c
: 日志器名称。%t
: 线程 ID。%f
: 源文件名。%l
: 代码行号。%m
: 日志主体消息。%T
: 一个 Tab 缩进。%n
: 换行符。
5.5 日志落地模块 (sink.hpp
)
该模块定义了日志的最终输出目的地。
- 核心设计: 基于继承与多态,定义了
LogSink
抽象基类,所有具体的落地方式都继承自该基类。通过工厂模式 (SinkFactory
),可以方便地创建不同的落地对象。 - 已实现的落地策略:
StdcoutSink
: 标准输出。FileSink
: 单文件输出。RollSink
: 滚动文件输出。
5.6 日志器模块 (logger.hpp
)
Logger
是用户直接交互的核心接口,它整合了格式化与落地模块,负责驱动整个日志记录流程。
- 核心设计: 定义了
Logger
抽象基类,并派生出SyncLogger
(同步日志器) 和AsyncLogger
(异步日志器) 两个子类。通过建造者模式 (LoggerBuilder
) 来简化复杂日志器对象的创建过程。 - 建造者 (
LoggerBuilder
): 该抽象类有两个具体的派生实现,用于满足不同的使用场景:LocalLoggerBuilder
: 用于创建局部日志器。由它创建的Logger
对象是一个独立的实例,不会被全局管理者持有,其生命周期由使用者通过智能指针自行管理。这适用于临时的、小范围的日志记录需求。GlobalLoggerBuilder
: 用于创建全局日志器。它在创建Logger
对象后,会自动将其注册到下文的LoggerManager
中。这使得该日志器成为一个全局单例,可以在程序的任何地方通过其名称被方便地获取和使用。
5.7 日志器管理模块 (logger.hpp
)
LoggerManager
是一个采用单例模式设计的全局管理者,它与 GlobalLoggerBuilder
紧密协作,共同负责全局日志器的生命周期管理。
- 主要职责:
- 存储与管理: 内部使用一个哈希表来存储由
GlobalLoggerBuilder
创建的所有日志器实例。 - 全局访问: 提供
getlogger(name)
接口,允许在代码的任何位置通过名称方便地获取到已注册的全局日志器。 - 默认日志器: 负责创建一个默认的日志器,为简单的日志记录需求提供最大便利。
- 统一停机: 负责在程序退出时,协调所有被管理的日志器,确保后台线程完成工作任务以及系统缓冲区的日志100%安全的写入磁盘,是实现优雅停机机制的核心环节。
- 存储与管理: 内部使用一个哈希表来存储由
5.8 缓冲区模块 (buffer.hpp
)
Buffer
类是为异步模式专门设计的内存缓冲区,用于临时存储业务线程产生的日志数据。它支持动态扩容,但是否进行扩容的策略由其调用者 AsyncLooper
模块根据工作模式来决定。
5.9 异步工作器模块 (looper.hpp
)
AsyncLooper
是异步日志模式的“心脏”,它是一个典型的生产者-消费者模型实现。
- 工作流程: 内部维护一个后台工作线程。业务线程(生产者)将日志数据快速写入“生产缓冲区”,然后由后台线程(消费者)从“消费缓冲区”中取出数据并进行 I/O 操作,两者通过“双缓冲”技术高效协作。为了保证数据在程序终止时不丢失,后台线程被设计为仅在收到停止信号且生产缓冲区完全清空后才会退出。
- 两种工作模式:
AsyncLooper
提供了两种模式来处理“生产缓冲区已满”的临界情况:- 安全模式 (Safe Mode): 这是默认的、用于实际生产环境的模式。当缓冲区空间不足时,生产者(业务线程)会阻塞等待,直到后台线程交换缓冲区后被唤醒。这可以有效防止内存的无节制增长。
- 非安全模式 (Unsafe Mode): 此模式主要用于极限性能测试。当缓冲区空间不足时,
Buffer
会被触发自动进行扩容,生产者(业务线程)不会阻塞。这可以测试出在不考虑内存限制情况下的最高日志吞-吐量。
5.10 全局接口模块 (mylog.hpp
)
为了简化用户的调用,该模块提供了一系列便捷的全局函数和宏,例如:
getlogger(name)
: 用于从LoggerManager
获取指定的日志器。debug(...)
,info(...)
等宏: 代理了对默认日志器成员函数的调用,并自动为主调方填充__FILE__
和__LINE__
等信息。
6. 总结
本文档详细阐述了该日志库的设计思想、核心技术栈以及系统框架。通过模块化的设计与多种设计模式的综合运用,该日志库旨在成为一个兼具高性能、高扩展性与易用性的日志解决方案。