【C++基于多设计模式下的同步&异步日志系统】
文章目录
- @[toc]
- 1 :peach:项目介绍:peach:
- 2 :peach:开发环境:peach:
- 3 :peach:核心技术:peach:
- 4 :peach:环境搭建:peach:
- 5 :peach:日志系统介绍:peach:
- 5.1 :apple:为什么需要日志系统?:apple:
- 5.2 :apple:日志系统技术实现:apple:
- 5.2.1 :lemon:同步写日志:lemon:
- 5.2.2 :lemon:异步写日志:lemon:
- 6 :peach:相关技术知识补充:peach:
- 6.1 :apple:不定参函数:apple:
- 6.1.1 :lemon:不定参宏函数:lemon:
- 6.1.2 :lemon:C风格不定参函数:lemon:
- 6.1.3 :lemon:C++风格不定参函数:lemon:
- 6.2 :apple:设计模式:apple:
- 6.2.1 :lemon:六大原则:lemon:
- 6.2.2 :lemon:单例模式:lemon:
- 6.2.3 :lemon:工厂模式:lemon:
- 6.2.4 :lemon:建造者模式:lemon:
- 6.2.5 :lemon:代理模式:lemon:
- 7 :peach:日志系统框架设计:peach:
- 7.1 :apple:模块划分:apple:
- 8 :peach:代码设计(重要):peach:
- 8.1 :apple:实用类设计:apple:
- 8.2 :apple:日志等级类设计:apple:
- 8.3 :apple:日志消息类设计:apple:
- 8.4 :apple:日志输出格式化类设计:apple:
- 8.5 :apple:日志落地类设计:apple:
- 8.6 :apple:日志器类设计:apple:
- 8.7 :apple:双缓冲区异步任务处理器设计:apple:
- 8.8 :apple:异步日志器设计:apple:
- 8.9 :apple:单例日志器管理类设计:apple:
- 8.10 :apple:日志宏&全局接口设计:apple:
- 9 :peach:性能测试:peach:
- 9.1 :apple:测试环境:apple:
- 9.2 :apple:测试方法:apple:
- 9.3 :apple:测试结果与结论:apple:
- 9.3.1 :lemon:单线程同步日志:lemon:
- 9.3.2 :lemon:多线程同步日志:lemon:
- 9.3.3 :lemon:单线程异步日志:lemon:
- 9.3.4 :lemon:多线程异步日志:lemon:
- 9.3.5 :lemon:结论:lemon:
- 10 :peach:扩展:peach:
- @[toc]
- 1 :peach:项目介绍:peach:
- 2 :peach:开发环境:peach:
- 3 :peach:核心技术:peach:
- 4 :peach:环境搭建:peach:
- 5 :peach:日志系统介绍:peach:
- 5.1 :apple:为什么需要日志系统?:apple:
- 5.2 :apple:日志系统技术实现:apple:
- 5.2.1 :lemon:同步写日志:lemon:
- 5.2.2 :lemon:异步写日志:lemon:
- 6 :peach:相关技术知识补充:peach:
- 6.1 :apple:不定参函数:apple:
- 6.1.1 :lemon:不定参宏函数:lemon:
- 6.1.2 :lemon:C风格不定参函数:lemon:
- 6.1.3 :lemon:C++风格不定参函数:lemon:
- 6.2 :apple:设计模式:apple:
- 6.2.1 :lemon:六大原则:lemon:
- 6.2.2 :lemon:单例模式:lemon:
- 6.2.3 :lemon:工厂模式:lemon:
- 6.2.4 :lemon:建造者模式:lemon:
- 6.2.5 :lemon:代理模式:lemon:
- 7 :peach:日志系统框架设计:peach:
- 7.1 :apple:模块划分:apple:
- 8 :peach:代码设计(重要):peach:
- 8.1 :apple:实用类设计:apple:
- 8.2 :apple:日志等级类设计:apple:
- 8.3 :apple:日志消息类设计:apple:
- 8.4 :apple:日志输出格式化类设计:apple:
- 8.5 :apple:日志落地类设计:apple:
- 8.6 :apple:日志器类设计:apple:
- 8.7 :apple:双缓冲区异步任务处理器设计:apple:
- 8.8 :apple:异步日志器设计:apple:
- 8.9 :apple:单例日志器管理类设计:apple:
- 8.10 :apple:日志宏&全局接口设计:apple:
- 9 :peach:性能测试:peach:
- 9.1 :apple:测试环境:apple:
- 9.2 :apple:测试方法:apple:
- 9.3 :apple:测试结果与结论:apple:
- 9.3.1 :lemon:单线程同步日志:lemon:
- 9.3.2 :lemon:多线程同步日志:lemon:
- 9.3.3 :lemon:单线程异步日志:lemon:
- 9.3.4 :lemon:多线程异步日志:lemon:
- 9.3.5 :lemon:结论:lemon:
- 10 :peach:扩展:peach:
1 🍑项目介绍🍑
本项⽬主要实现⼀个⽇志系统, 其主要⽀持以下功能:
- 1️⃣⽀持多级别⽇志消息;
- 2️⃣⽀持同步⽇志和异步⽇志;
- 3️⃣⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中;
- 4️⃣⽀持多线程程序并发写⽇志;
- 5️⃣⽀持扩展不同的⽇志落地⽬标.
2 🍑开发环境🍑
- 1️⃣CentOS 7.6(2核,内存2GB,SSD云硬盘40GB)
- 2️⃣vscode/vim
- 3️⃣g++/gdb
- 4️⃣Makefile
3 🍑核心技术🍑
- 1️⃣类层次设计(继承和多态的应⽤)
- 2️⃣C++11(多线程、bind、智能指针、右值引用、互斥锁等)
- 3️⃣双缓冲区
- 4️⃣生产消费模型
- 6️⃣设计模式(单例、工厂、代理、模板等)
4 🍑环境搭建🍑
本项⽬不依赖其他任何第三⽅库, 只需要安装好CentOS/Ubuntu + vscode/vim环境即可开发。
5 🍑日志系统介绍🍑
5.1 🍎为什么需要日志系统?🍎
- ⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题, 可以借助日志系统来打印⼀些⽇志帮助开发⼈员解决问题;
- 上线客户端的产品出现bug⽆法复现并解决, 可以借助日志系统打印⽇志并上传到服务端帮助开发⼈员进⾏分析;
- 对于⼀些⾼频操作(如定时器、心跳包)在少量调试次数下可能⽆法触发我们想要的⾏为,通过断点的暂停⽅式,我们不得不重复操作⼏⼗次、上百次甚⾄更多,导致排查问题效率是⾮常低下, 可以借助打印⽇志的⽅式查问题;
- 在分布式、多线程/多进程代码中, 出现bug比较难以定位, 可以借助⽇志系统打印log帮助定位bug;
- 帮助⾸次接触项⽬代码的新开发⼈员理解代码的运⾏流程。
5.2 🍎日志系统技术实现🍎
⽇志系统的技术实现主要包括三种类型:
- 利⽤printf、std::cout等输出函数将⽇志信息打印到控制台;
- 对于⼤型商业化项⽬, 为了⽅便排查问题,我们⼀般会将⽇志输出到⽂件或者是数据库系统⽅便查询和分析⽇志, 主要分为同步⽇志和异步⽇志⽅式。
5.2.1 🍋同步写日志🍋
同步日志是指当输出日志时,必须等待⽇志输出语句执⾏完毕后,才能执⾏后⾯的业务逻辑语句,日志输出语句与程序的业务逻辑语句将在同⼀个线程运⾏。每次调⽤⼀次打印⽇志API就对应⼀次系统调⽤write写⽇志⽂件。
但是在⾼并发场景下,随着⽇志数量不断增加,同步⽇志系统容易产⽣系统瓶颈:
- ⼀⽅⾯,⼤量的⽇志打印陷入等量的write系统调⽤,有⼀定系统开销;
- 另⼀⽅⾯,使得打印⽇志的进程附带了⼤量同步的磁盘IO,程序性能下降。(一旦输出日志的进程阻塞,整个流程都将不被推进)
5.2.2 🍋异步写日志🍋
异步日志是指在进行日志输出时,日志输出语句与业务逻辑语句并不是在同⼀个线程中运行,⽽是有专⻔的线程⽤于进⾏⽇志输出操作。业务线程只需要将⽇志放到⼀个内存缓冲区中不⽤等待即可继续执后续业务逻辑(作为⽇志的⽣产者),⽽⽇志的落地操作交给单独的⽇志线程去完成(作为⽇志的消费者), 这是⼀个典型的生产者-消费者模型。
这样做的好处是即使日志没有真的完成输出也不会影响程序的主业务,可以提⾼程序的性能:
- 主线程调⽤⽇志打印接⼝成为非阻塞操作(因为主线程只需要将日志消息放在日志缓冲区即可);
- 同步的磁盘IO从主线程中剥离出来交给单独的线程完成。
6 🍑相关技术知识补充🍑
6.1 🍎不定参函数🍎
在初学C语⾔的时候,我们都⽤过printf函数进⾏打印。其中printf函数就是⼀个不定参函数,在函数内部可以根据格式化字符串中格式化字符分别获取不同的参数进⾏数据的格式化。而这种不定参函数在实际的使⽤中也⾮常多见。
6.1.1 🍋不定参宏函数🍋
#include #include #define LOG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) int main() { LOG("%s-%s", "hello", "world"); return 0; }
6.1.2 🍋C风格不定参函数🍋
#include #include void printNum(int n, ...) { va_list al; va_start(al, n);//让al指向n参数之后的第⼀个可变参数 for (int i = 0; i
6.2.5 🍋代理模式🍋
代理模式指代理控制对其他对象的访问, 也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客户端和⽬标对象之间起到中介的作⽤。代理模式的结构包括⼀个是真正的你要访问的对象(⽬标类)、⼀个是代理对象。⽬标对象与代理对象实现同⼀个接⼝,先访问代理类再通过代理类访问⽬标对象。代理模式分为静态代理、动态代理。
- 静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
- 动态代理指的是,在运⾏时才动态⽣成代理类,并将其与被代理类绑定。这意味着,在运⾏时才能确定代理类要代理的是哪个被代理类。
class RentHouse { public: virtual void rent_house() = 0; }; /*房东类:将房⼦租出去*/ class Landlord : public RentHouse { public: virtual void rent_house() { std::cout public: virtual void rent_house() { std::cout Intermediary intermediary; intermediary.rent_house(); return 0; } %H:%M:%S}][%t][%p][%c][%f:%l]%T%m%n namespace util { class Date { public: static size_t now() { return (size_t)time(nullptr); } }; class File { public: static bool exist(const std::string &pathname) { struct stat st; return stat(pathname.c_str(), &st) == 0; } static std::string path(const std::string &name) { if (name.empty()) return "."; size_t pos = name.find_last_of("/\\"); if (pos == std::string::npos) return "."; return name.substr(0, pos + 1); } static void create(const std::string &name) { int cur=0,pos=0; while(cur