C++- 基于多设计模式下的同步&异步日志系统
第一个项目:13万字,带源代码和详细步骤
目录
第一个项目:13万字,带源代码和详细步骤
1. 项目介绍
2. 核心技术
3. 日志系统介绍
3.1 为什么需要⽇志系统
3.2 ⽇志系统技术实现
3.2.1 同步写⽇志
3.2.2 异步写⽇志
4.知识点和单词补充
4.1单词补充
4.2知识点补充
4.2.1完美转发forward
4.2.3 override
4.2.4 虚函数和纯虚函数
4.2.5智能指针中的reset
4.2.6继承和多态中,函数返回类型是父类的,返回子类的类型
4.2.7继承和多态中,函数返回类型是父类的,怎么样才可以返回子类的类型
4.2.8 stat(文件存不存在)
4.2.9 文件路径查找
4.3.0 c++线程
4.3.1 using的用法
4.3.2 ostream将数据写入
4.3.3 using和智能指针一起控制类对象
4.3.4使用 strftime 格式化时间
4.3.5 stringstream
4.3.6 终止一个程序的执行abort
4.3.7 cout.write
4.3.8 ofstream中的接口
4.3.9 stringstream
4.4.0 enum clss
4.4.1 atomic原子操作的库
4.4.2 unique_lock
4.4.3 智能指针中get接口
4.4.4 copy
4.2.5 ifstream的成员函数
4.2.6 seekg
4.2.7 condition_variable(多线程)
4.2.8 回调函数和智能指针
4.2.9 c++中的线程
4.3.0 线程的接口
4.3.1 bind绑定
5. 相关技术知识补充
5.1 不定参函数
不定参宏函数
打印文件名和行号
宏函数
编辑
c语言中不定参函数的使用,不定参数据的访问
模拟一个printf
C++风格不定参函数
5.2 设计模式
六⼤原则:
单例模式
• 饿汉模式:
懒汉模式
编辑
⼯⼚模式
抽象工厂模式(开闭原则没有遵循)
建造者模式:
代理模式
6. 日志系统框架设计
6.1模块划分
6.2 模块关系图
7. 代码设计
7.1 实⽤类(常用)设计
7.2 日志等级类设计
7.3 日志消息类设计
7.4 日志输出格式化类设计
7.5日志落地类的设计
7.6 日志器类(Logger)设计(建造者模式)
7.7 双缓冲区异步任务处理器(AsyncLooper)设计(实现异步日志缓冲区)
7.8 异步⽇志器(AsyncLogger)设计
7.9 单例日志器管理类设计(单例模式)
7.10日志宏&全局接口设计 (代理模式)
8.目前为止代码基本完成
buffer.hpp
format.hpp
level.hpp
ljwlog.h
logger.hpp
loop.hpp
makefile
message.hpp
sink.hpp
test.cc
util.hpp
1. 项目介绍
本项目主要实现⼀个日志系统,其主要⽀持以下功能:
• ⽀持多级别日志消息
• ⽀持同步日志和异步日志
• ⽀持可靠写⼊日志到控制台、⽂件以及滚动⽂件中
• ⽀持多线程程序并发写日志
• ⽀持扩展不同的日志落地⽬标地
2. 核心技术
• 类层次设计(继承和多态的应⽤)
• C++11(多线程、auto、智能指针、右值引⽤等)
• 双缓冲区
• ⽣产消费模型
• 多线程
• 设计模式(单例、⼯⼚、代理、建造者等)
本项⽬不依赖其他任何第三⽅库,只需要安装好CentOS/Ubuntu+vscode/vim(vscode写)环境即可开发。
3. 日志系统介绍
3.1 为什么需要⽇志系统
• ⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题,可以借助日志系统来打印⼀些日志帮助开发⼈员解决问题
• 上线客户端的产品出现bug无法复现并解决,可以借助⽇志系统打印⽇志并上传到服务端帮助开发⼈员进⾏分析
• 对于⼀些⾼频操作(如定时器、⼼跳包)在少量调试次数下可能⽆法触发我们想要的⾏为,通过断点的暂停⽅式,我们不得不重复操作⼏⼗次、上百次甚⾄更多,导致排查问题效率是⾮常低下,可以借助打印⽇志的⽅式查问题
• 在分布式、多线程/多进程代码中,出现bug⽐较难以定位,可以借助⽇志系统打印log帮助定位 bug
• 帮助⾸次接触项⽬代码的新开发⼈员理解代码的运⾏流程
3.2 ⽇志系统技术实现
⽇志系统的技术实现主要包括三种类型:
• 利⽤printf、std::cout等输出函数将⽇志信息打印到控制台
• 对于⼤型商业化项目,为了⽅便排查问题,我们⼀般会将日志输出到⽂件或者是数据库系统⽅便查询和分析日志,主要分为同步⽇志和异步⽇志⽅式
◦ 同步写日志
◦ 异步写日志
3.2.1 同步写⽇志
同步⽇志是指当输出⽇志时,必须等待⽇志输出语句执⾏完毕后,才能执⾏后⾯的业务逻辑语句,⽇志输出语句与程序的业务逻辑语句将在同⼀个线程运⾏。每次调⽤⼀次打印⽇志API就对应⼀次系统调 ⽤write写⽇志⽂件。
在⾼并发场景下,随着⽇志数量不断增加,同步⽇志系统容易产⽣系统瓶颈:
• ⼀⽅⾯,⼤量的⽇志打印陷⼊等量的write系统调⽤,有⼀定系统开销.
• 另⼀⽅⾯,使得打印⽇志的进程附带了⼤量同步的磁盘IO,影响程序性能.
3.2.2 异步写⽇志
异步⽇志是指在进⾏⽇志输出时,⽇志输出语句与业务逻辑语句并不是在同⼀个线程中运⾏,⽽是有专⻔的线程⽤于进⾏⽇志输出操作。业务线程只需要将⽇志放到⼀个内存缓冲区中不⽤等待即可继续执⾏后续业务逻辑(作为⽇志的⽣产者),⽽⽇志的落地操作交给单独的⽇志线程去完成(作为⽇志的消费者),这是⼀个典型的⽣产-消费模型。
这样做的好处是即使⽇志没有真的地完成输出也不会影响程序的主业务,可以提⾼程序的性能:
• 主线程调⽤⽇志打印接口成为⾮阻塞操作
• 同步的磁盘IO从主线程中剥离出来交给单独的线程完成
4.知识点和单词补充
4.1单词补充
单例模式:Singleton pattern
实例:instance
工厂:factory
指针:pointer(ptr的由来)(智能指针shared_ptr等等)
建造者:Builder
主板:Motherboard(_board)
显示器:Display(_display)
操作系统:Operating System(_os)
参数:paramater
指挥者:director
建造(组建):construct
房东:landlord
中介:intermediary
实用类:Utility class(util)
存在:exist
文件:file
路径:path
目录:directory
格式:format
项:item
模式:pattern
格式化程序:Formatter
有效载荷:payload
下沉(落地):sink
基础:base
限制:limit
同步:synchronization
异步:asynchronous
序列化:serialization
同步:synchronization
异步:asynchronous
缓冲:buffer
默认的:default
阈值:threshold
增量:increment
循环:loop
消费者:consumer
生产者:producer
日志记录器:logger
管理者:manager
根,根源:root
整体的全局的:global
4.2知识点补充
4.2.1完美转发forward
在C++中,完美转发(Perfect Forwarding)是一种用于在函数模板中保持参数原有类型的技巧,包括它们的const和volatile修饰符以及引用属性。这在编写模板代码时非常有用,特别是在创建工厂函数或者封装其他函数调用时。
以下是对完美转发的一些总结:
基本概念
-
转发:将函数的参数原封不动地传递给另一个函数。
-
完美转发:在转发过程中保持参数的左值或右值属性。
关键字和操作符
-
std::forward(u):条件性转发,如果u是左值,则返回左值引用;如果u是右值,则返回右值引用。
-
T&&:通用引用,在模板参数中用来捕获任意类型的参数。
完美转发的实现
在函数模板中,通常结合使用T&&和std::forward来实现完美转发:
template void func(T&& arg) { someOtherFunc(std::forward(arg)); }
这里,T&&是一个通用引用,可以绑定到任何类型的参数上,std::forward(arg)则确保了参数的左右值属性在转发时得以保持。
注意事项
-
通用引用:只有当T&&是模板参数时,它才是通用引用。否则,它可能是一个右值引用。
-
引用折叠:当将一个引用类型绑定到另一个引用类型上时,会发生引用折叠,例如,T& &会折叠成T&,而T&& &会折叠成T&。
-
转发时的const保持:如果参数是const的,完美转发也会保持const属性。
完美转发的用途
-
在模板库中,如工厂模式、函数适配器等,允许用户以最有效率的方式传递参数。
-
在函数重载和模板特化中,确保参数的值类别不变。
示例
#include #include // for std::forward void print(int& x) { std::cout
-
-