【C++项目】基于设计模式的同步异步日志系统(前置基础知识)
目录
- 一:日志系统概述
- 二:同步日志与异步日志
- 1. 同步日志:
- 2.异步日志:
- 三:基础知识
- 1. C风格不定参
- 搭配#define使用
- 2.C++风格不定参 ->可变模板(template<typename... Args>
- 基本概念
- 包拓展的使用
- 四:设计模式(重要)
- **设计模式的本质**
- 六大设计原则
- 总结:抽象是灵魂
- 设计模式:
- 1. 单例模式
- **饿汉模式(泛型)**
- **懒汉模式**
- 2.工厂模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- 3. 建造者模式
一:日志系统概述
- 为什么需要日志系统?
-
调试与问题定位的基础工具
在代码世界里,日志是你在黑暗里点亮的一盏灯。程序是黑箱运行的,除非你在每个关键点“留下痕迹”,否则一旦出错你只能猜。 -
生产环境中的“时间回溯机器”
有时候,线上 bug 往往无法复现,日志是唯一能“回放现场”的证据:
1.谁在什么时候调用了哪个接口?
2.哪个参数触发了异常?
3.系统当时的状态(内存/线程/请求量)如何?
因此日志系统必须持久化、分级别(INFO/WARN/ERROR)、可检索,否则日志再多也只是“噪音” -
性能分析与行为监控
你可以通过日志统计:
1.每个接口的响应时间分布;
2.每秒的请求量(QPS);
3.某段时间错误率的波动。
这些信息让你提前发现性能瓶颈或潜在的雪崩风险。 -
安全与合规审计
在金融、政企等行业,日志是安全审计的法律凭证。
谁登录了系统?改了什么配置?删除了哪条记录?
这类行为必须记录且不可篡改,否则出了问题根本查不清责任。
总之,日志让程序的运行状态在事后可被观察、分析、复现和审计。
二:同步日志与异步日志
1. 同步日志:

原理:
当你调用:
LOG(INFO) << "User login success: " << username;
同步日志会:
1.立即格式化日志内容;
2.立刻调用 write() 或 fwrite() 写入磁盘;
3.等操作系统确认写入成功后,再返回继续执行。
也就是说,主线程阻塞等待 I/O 完成。
比喻:你亲手写完报告、盖章、存档,然后才回去继续工作。
优点:
- 可靠性强:崩溃前的日志一定已经写入文件。
- 逻辑简单:实现容易,没有多线程竞争问题。
- 适合调试或小规模程序:比如嵌入式系统、单线程工具。
缺点:
- 性能低:磁盘 I/O 是慢操作(毫秒级),写日志可能成为性能瓶颈。
- 线程争用严重:多线程频繁写日志会加锁,造成严重阻塞。
2.异步日志:

原理:
异步日志的思想是写日志的事交给后台线程做。
- 主线程写日志时,不直接写文件,而是把日志内容放进无锁队列(或环形缓冲区);
- 一个后台日志线程周期性从队列取出日志;
- 后台线程批量写入文件(减少 I/O 次数)。
优点:
- 性能高:主线程几乎不阻塞,日志写入速度非常快。
- 吞吐量大:适合高并发服务器(例如 web server、交易系统)。
- 可批量刷盘:比如每 100 条日志或每 1 秒刷一次,I/O 成本低。
缺点
- 可能丢日志:崩溃时队列中的日志还没写出。
- 实现复杂:涉及线程同步、内存队列、日志丢失保护等。
- 延迟写入:日志不会立即出现在文件里(这对调试不便)。
三:基础知识
1. C风格不定参
- C 风格(… + stdarg.h)是运行时、非类型安全、用于兼容 C API
- printf接口就是一个典型的例子,可以传随机的参数。
搭配#define使用
#define LOG(fmt, ...) printf("[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
解释:
- #define LOG(fmt, …) LOG 是宏名。
- (fmt, …) 表示这个宏接收 一个格式化字符串 fmt,以及 可变参数 …
- 这里的 … 对应 C99 / C++11 的 可变参数宏
- printf("[%s:%d] " fmt, FILE, LINE, ##VA_ARGS)
这个是宏展开的内容,功能是打印日志信息,并自动带上文件名和行号:
-
[%s:%d] " ,%s → 文件名,%d → 行号
这两个信息来自:
FILE → 预定义宏,当前源文件名
LINE → 预定义宏,当前行号 -
fmt:用户传入的格式化字符串,例如 “value=%d\n”
-
##VA_ARGS
这里的 VA_ARGS 对应可变参数的值
##是 GCC/Clang 的 可变参数宏的“逗号吞掉”技巧
当没有额外参数时,前面的逗号会自动去掉,避免编译错误!!!
2.C++风格不定参 ->可变模板(template<typename… Args>
基本概念
- 所谓可变参数模版,就是支持传入n个参数。
两个参数包:
- 模版参数包:表示零或者多个模版参数;
- 函数参数包:表示零或者多个函数参数;
template <class …Args>
void func(Args …args)
{}
template <class …Args>
void func(Args& …args)
{}
template <class …Args>
void func(Args&& …args)
{}
- 我们可以使用sizeof…来计算参数保重的个数
template<class ...Args>
void func(Args ...args)
{//计算参数保重的个数cout<<sizeof...(args)<<endl;
}
包拓展的使用
除了使用sizeof…计算包的参数个数,我们还需将包拓展,能够使用包里面的参数
例1:
#include<iostream>
using namespace std;
//3.终止条件
void func()
{cout << endl;
}
//2.
template<class T, class ...Args>
void func(T&& t, Args&&... args)
{cout << t << endl;func(args...);
}
//1.
template<class ...Args>
void Print(Args&&... args)
{func(args...);
}int main()
{Print(1, "nihao", 3);return 0;
}
例2:
#include <iostream>
#include <cstdarg>
#include <memory>
#include <functional>
void xprintf() {
std::cout << std::endl;
}
template<typename T, typename ...Args>
void xprintf(const T &value, Args &&...args) {
std::cout << value << " ";
if ((sizeof ...(args)) > 0) {
xprintf(std::forward<Args>(args)...);
}else {
xprintf();
}
}
int main()
{xprintf("同步");xprintf("同步", "异步");xprintf("同步", "异步", "日志");xprintf("同步", "异步", "日志","系统");return 0;
}
四:设计模式(重要)
设计模式的本质
- 设计模式是是前辈们对代码经验的总结,设计模式不是创造,是发现。
- 总结出四点:1.让代码更容易修改(可维护性)2.让代码更容易复用(可复用性)3.让代码更容易理解(可读性)4.让系统更稳健、更安全(鲁棒性).
六大设计原则
- 单一职责原则(SRP)
- 定义:一个类负责一件事情,一个函数只做一件事.
- 解释:一个代码的复杂性不可避免,但是可以被隔离,把变化分区管理,让一个变化的原因只影响到一个类.
- 比喻:医院的外科医生不负责配药;前端不写 SQL。职责越清晰,改动越安全。
示例代码:
class ChatNetwork {
public:void connect();void sendData();
};class ChatLogic {
public:void processMessage();
};
网络逻辑和聊天逻辑分开,这样在改动聊天逻辑的时候,网路逻辑不会被影响,其实,在正常写代码的过程中,这样的方式我们也在用.
- 开闭原则(OCP)
- 定义:对扩展开放,对修改封闭。
- 解释:意思就是不要改动老代码,在需要增加业务需求的时候,可以在源代码的基础上进行新增代码,而不是修改代码,这也是为了保证系统的稳定性,
- 比喻:当排插不够的时候,我们可以再买一个排插,然后插在那个排插上面.
示例代码:
class PricePolicy {
public:virtual double getPrice(double base) = 0;
};class NormalPrice : public PricePolicy {
public:double getPrice(double base) override { return base; }
};class DiscountPrice : public PricePolicy {
public:double getPrice(double base) override { return base * 0.8; }
};
新增 DiscountPrice 不需要改动老代码。
- 里氏替换原则(LSP)
定义:父类能出现的地方,子类也能安全替代.
比喻:企鹅是鸟,但鸟会飞,而企鹅不会飞 —— 所以企鹅不该继承“会飞的鸟类”,否则就破坏了替换原则。
class Runner {
public:virtual void run() = 0;
};class Sprinter : public Runner {
public:void run() override { /* 短跑逻辑 */ }
};
子类要遵守父类行为契约,不得改变预期。
- 依赖倒置原则(DIP)
定义:高层模块不依赖低层模块,二者都依赖抽象。
解释:抽象比实现更稳定,让变化的部分依附在抽象上,而不是直接绑死。
比喻:
一个司机去开一辆奔驰,司机如果想换一辆宝马去开,开的方式还是一样,不可能说换了车的牌子之后,司机需要重新考驾照,或者是,开车的逻辑都变了.这样会造成资源的浪费.
class ICar {
public:virtual void run() = 0;
};class Driver {
public:void drive(ICar& car) { car.run(); }
};
司机(高层)不关心是奔驰还是宝马,程序更灵活
- 迪米特法则(LoD / Law of Demeter)
定义:最少知道原则:一个对象应尽量少了解其他对象的内部结构。
解释:降低耦合,减少连锁反应。“朋友的朋友,不是你的朋友。”
比喻:老师只跟班长打交道,不直接去和每个学生沟通。
class Student {};
class Monitor {
public:void rollCall() { /* 点名 */ }
};
class Teacher {
public:void startClass(Monitor& m) { m.rollCall(); }
};
Teacher 只和 Monitor 交互,不直接访问 Student 列表。
- 接口隔离原则(ISP)
定义:接口要小而专,客户端不应依赖不需要的功能。
解释:“胖接口”意味着臃肿与混乱。接口的职责要清晰。
比喻::登录系统里,“修改密码”接口不应暴露“删除用户”。
class IPasswordService {
public:virtual void changePassword(const std::string& newPwd) = 0;
};
小接口提升灵活性和安全性。
总结:抽象是灵魂
最后一句话非常点睛:
“用抽象构建框架,用实现扩展细节。”
抽象是防御复杂性的盾牌。
实现是现实世界的具体细节。
设计原则的真正意图是——让我们用抽象隔离变化,用接口连接模块,用职责划分边界。
设计模式:
1. 单例模式
定义:保证在系统中只有一个实例,并且提供一个全局访问点。
结构和角色:
- Singleton(单例类):负责创建唯一实例并提供访问。
- 私有化构造函数:防止外部创建对象。
- 静态实例指针:保存唯一实例。
- 静态访问方法:对外提供获取实例的方式。
饿汉模式:程序启动时就会创建⼀个唯⼀的实例对象。 因为单例对象已经确定, 所以⽐较适⽤于多线程环境中, 多线程获取单例对象不需要加锁, 可以有效的避免资源竞争, 提⾼性能。
饿汉模式(泛型)
定义:程序启动时就会创建⼀个唯⼀的实例对象。 因为单例对象已经确定, 所以比较适⽤于多线程环境中, 多线程获取单例对象不需要加锁, 可以有效的避免资源竞争, 提⾼性能。
template<typename T>
class Singleton {
private:static Singleton<T> _eton; // 饿汉式,类加载时初始化private:Singleton() {} // 私有构造~Singleton() {}public:Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton<T>& getInstance() {return _eton;}
};// 静态成员定义
template<typename T>
Singleton<T> Singleton<T>::_eton;
使用:
class MyClass {
public:void hello() { std::cout << "Hello, Singleton!" << std::endl; }
};int main() {auto& s = Singleton<MyClass>::getInstance();s.hello();
}
懒汉模式
定义:第⼀次使⽤要使⽤单例对象的时候创建实例对象。如果单例对象构造特别耗时或者耗费资源(加载插件、加载⽹络资源等), 可以选择懒汉模式, 在第一次使用的时候才创建对象。
class Singleton {
private:static Singleton* instance;Singleton() { std::cout << "构造函数被调用\n"; }~Singleton() {}public:static Singleton* getInstance() {if (instance == nullptr) // 第一次调用才创建instance = new Singleton();return instance;}
};// 静态成员初始化
Singleton* Singleton::instance = nullptr;
int main() {auto* s1 = Singleton::getInstance();auto* s2 = Singleton::getInstance();std::cout << (s1 == s2) << std::endl;
}
2.工厂模式
- 工厂模式分为简单工厂模式,工厂方法模式,抽象工厂模式。
简单工厂模式
概念:简单⼯⼚模式实现由⼀个⼯⼚对象通过类型决定创建出来指定产品类的实例。假设有个⼯⼚能⽣产出⽔果,当客⼾需要产品的时候明确告知⼯⼚⽣产哪类⽔果,⼯⼚需要接收⽤⼾提供的类别信息,当新增产品的时候,⼯⼚内部去添加新产品的⽣产⽅式。
实例:
#include <iostream>
#include <string>
#include <memory>class Fruit {
public:Fruit() {}virtual void show() = 0;
};class Apple : public Fruit {
public:Apple() {}virtual void show() {std::cout << "我是一个苹果" << std::endl;}
};class Banana : public Fruit {
public:Banana() {}virtual void show() {std::cout << "我是一个香蕉" << std::endl;}
};class FruitFactory {
public:static std::shared_ptr<Fruit> create(const std::string& name) {if (name == "苹果") {return std::make_shared<Apple>();}else if (name == "⾹蕉") {return std::make_shared<Banana>();}return std::shared_ptr<Fruit>();}
};int main()
{std::shared_ptr<Fruit> fruit = FruitFactory::create("苹果");fruit->show();fruit = FruitFactory::create("⾹蕉");fruit->show();return 0;
}
工厂方法模式
定义:在简单⼯⼚模式下新增多个⼯⼚,多个产品,每个产品对应⼀个⼯⼚。假设现在有A、B 两种产品,则开两个⼯⼚,⼯⼚ A 负责⽣产产品 A,⼯⼚ B 负责⽣产产品 B,用户只知道产品的⼯⼚名,⽽不知道具体的产品信息,⼯⼚不需要再接收客⼾的产品类别,⽽只负责⽣产产品。
#include <iostream>
#include <string>
#include <memory>class Fruit {
public:Fruit() {}virtual void show() = 0;
};class Apple : public Fruit {
public:Apple() {}virtual void show() {std::cout << "我是一个苹果" << std::endl;}
};class Banana : public Fruit {
public:Banana() {}virtual void show() {std::cout << "我是一个香蕉" << std::endl;}
};class FruitFactory {
public:virtual std::shared_ptr<Fruit> create() = 0;
};class AppleFactory : public FruitFactory{
public:virtual std::shared_ptr<Fruit> create() {return std::make_shared<Apple>();}
};class BananaFactory : public FruitFactory {
public:virtual std::shared_ptr<Fruit> create() {return std::make_shared<Banana>();}
};int main()
{std::shared_ptr<FruitFactory> factory(new AppleFactory);std::shared_ptr<Fruit> fruit = factory->create();fruit->show();factory.reset(new BananaFactory);fruit = factory->create();fruit->show();return 0;
}
抽象工厂模式
抽象⼯⼚模式: ⼯⼚⽅法模式通过引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责太重的问
题,但由于⼯⼚⽅法模式中的每个⼯⼚只⽣产⼀类产品,可能会导致系统中存在⼤量的⼯⼚类,势
必会增加系统的开销。此时,我们可以考虑将⼀些相关的产品组成⼀个产品族(位于不同产品等级
结构中功能相关联的产品组成的家族),由同⼀个⼯⼚来统⼀⽣产,这就是抽象⼯⼚模式的基本思
想。
#include <iostream>
#include <string>
#include <memory>
//抽象⼯⼚:围绕⼀个超级⼯⼚创建其他⼯⼚。每个⽣成的⼯⼚按照⼯⼚模式提供对象。
// 思想:将⼯⼚抽象成两层,抽象⼯⼚ & 具体⼯⼚⼦类, 在⼯⼚⼦类种⽣产不同类型的⼦产品
class Fruit {
public:Fruit() {}virtual void show() = 0;
};
class Apple : public Fruit {
public:Apple() {}virtual void show() {std::cout << "我是⼀个苹果" << std::endl;}
private:std::string _color;
};
class Banana : public Fruit {
public:Banana() {}virtual void show() {std::cout << "我是⼀个⾹蕉" << std::endl;}
};
class Animal {
public:virtual void voice() = 0;
};
class Lamp : public Animal {
public:void voice() { std::cout << "咩咩咩\n"; }
};
class Dog : public Animal {
public:void voice() { std::cout << "汪汪汪\n"; }
};
class Factory {
public:virtual std::shared_ptr<Fruit> getFruit(const std::string& name) = 0;virtual std::shared_ptr<Animal> getAnimal(const std::string& name) = 0;
};
class FruitFactory : public Factory {
public:virtual std::shared_ptr<Animal> getAnimal(const std::string& name) {return std::shared_ptr<Animal>();}virtual std::shared_ptr<Fruit> getFruit(const std::string& name) {if (name == "苹果") {return std::make_shared<Apple>();}else if (name == "⾹蕉") {return std::make_shared<Banana>();}return std::shared_ptr<Fruit>();}
};
class AnimalFactory : public Factory {
public:virtual std::shared_ptr<Fruit> getFruit(const std::string& name) {return std::shared_ptr<Fruit>();}virtual std::shared_ptr<Animal> getAnimal(const std::string& name) {if (name == "⼩⽺") {return std::make_shared<Lamp>();}else if (name == "⼩狗") {return std::make_shared<Dog>();}return std::shared_ptr<Animal>();}
};
class FactoryProducer {
public:static std::shared_ptr<Factory> getFactory(const std::string& name) {if (name == "动物") {return std::make_shared<AnimalFactory>();}else {return std::make_shared<FruitFactory>();}}
};
int main()
{std::shared_ptr<Factory> fruit_factory = FactoryProducer::getFactory("⽔果");std::shared_ptr<Fruit> fruit = fruit_factory->getFruit("苹果");fruit->show();fruit = fruit_factory->getFruit("⾹蕉");fruit->show();std::shared_ptr<Factory> animal_factory = FactoryProducer::getFactory("动物");std::shared_ptr<Animal> animal = animal_factory->getAnimal("⼩⽺");animal->voice();animal = animal_factory->getAnimal("⼩狗");animal->voice();return 0;
}
3. 建造者模式
定义:建造者模式是一种创建型设计模式,用于将一个复杂对象的“构建过程”与它的“表示形式”分离。
它让我们可以一步步地创建对象,而不是在一个庞大的构造函数里一次性完成所有初始化工作。
这种模式常用于“对象构造步骤多且固定,但具体细节可能变化”的场景。
比如组装电脑:不管品牌如何,电脑都由主板、显示器和操作系统组成,但不同品牌的实现细节各不相同
模式的核心组成
建造者模式通常由以下四个角色组成:
-
产品类(Product)
复杂对象本身,包含若干需要被构建的部件。
示例:Computer 类,包含主板、显示器和操作系统等字段。 -
抽象建造者(Builder)
定义创建产品各个部件的接口,规范构建流程但不具体实现。 -
具体建造者(ConcreteBuilder)
实现抽象建造者接口,完成实际的构建过程。
示例:MacBookBuilder 负责构建苹果电脑的具体细节。 -
指挥者(Director)
负责统一构建过程,决定调用建造者的顺序。
调用者通过指挥者来获取完整产品,而不关心内部细节。
可以把这个过程类比为“装修房子”:
Director 是设计师,负责制定装修顺序:先砌墙、再刷漆、最后布置家具。
Builder 是施工队,知道如何执行这些步骤。
Product 就是最终装修好的房子。
设计师(Director)只需要告诉施工队(Builder)要什么风格,不必参与具体施工。
这样即使换了施工队(比如从“北欧风”改为“中式风”),设计师的工作流程也不需要变。
实例代码:
#include <iostream>
#include <memory>
#include <string>/* 抽象电脑类 */
class Computer {
public:using ptr = std::shared_ptr<Computer>;void setBoard(const std::string &board) { _board = board; }void setDisplay(const std::string &display) { _display = display; }virtual void setOs() = 0;std::string toString() const {std::string computer = "Computer {\n";computer += "\tboard: " + _board + "\n";computer += "\tdisplay: " + _display + "\n";computer += "\tos: " + _os + "\n";computer += "}\n";return computer;}protected:std::string _board;std::string _display;std::string _os;
};/* 具体产品类 */
class MacBook : public Computer {
public:using ptr = std::shared_ptr<MacBook>;void setOs() override {_os = "Mac OS X 12";}
};/* 抽象建造者类 */
class Builder {
public:using ptr = std::shared_ptr<Builder>;virtual ~Builder() = default;virtual void buildBoard(const std::string &board) = 0;virtual void buildDisplay(const std::string &display) = 0;virtual void buildOs() = 0;virtual Computer::ptr build() = 0;
};/* 具体建造者类 */
class MacBookBuilder : public Builder {
public:using ptr = std::shared_ptr<MacBookBuilder>;MacBookBuilder() : _computer(std::make_shared<MacBook>()) {}void buildBoard(const std::string &board) override {_computer->setBoard(board);}void buildDisplay(const std::string &display) override {_computer->setDisplay(display);}void buildOs() override {_computer->setOs();}Computer::ptr build() override {return _computer;}private:Computer::ptr _computer;
};/* 指挥者类 */
class Director {
public:Director(const Builder::ptr &builder) : _builder(builder) {}void construct(const std::string &board, const std::string &display) {_builder->buildBoard(board);_builder->buildDisplay(display);_builder->buildOs();}private:Builder::ptr _builder;
};/* 主函数 */
int main() {auto builder = std::make_shared<MacBookBuilder>();Director director(builder);director.construct("英特尔主板", "VOC显示器");auto computer = builder->build();std::cout << computer->toString() << std::endl;return 0;
}
- 代理模式
定义:代理模式(Proxy Pattern)是一种结构型设计模式,它的核心思想是:
通过一个“代理对象”来控制对真实对象的访问。
有时候,我们不希望或者不能让客户端直接操作目标对象,这时就可以引入一个代理来充当“中间层”。
代理在转发请求的同时,还可以附加额外的逻辑,比如权限检查、日志记录、性能监控、远程访问等。
模式结构
代理模式一般由以下三部分组成:
抽象主题(Subject)
定义了客户端与真实主题之间的公共接口,让代理类和真实类可以互换使用。
真实主题(RealSubject)
执行真正业务逻辑的对象,是代理要代表的那个“核心角色”。
代理类(Proxy)
持有真实主题的引用,并在调用前后添加控制逻辑,比如访问限制、缓存、日志、统计等。
假设房东要出租房子,但他不想自己处理所有细节,比如:
发布租房信息
带人看房
处理租后维修
于是房东找了一个“中介”(代理)帮他代劳。中介在“租房流程”中加入了额外步骤:发布广告、带看、维修等。
#include <iostream>
#include <string>/* 抽象主题类 */
class RentHouse {
public:virtual void rentHouse() = 0;virtual ~RentHouse() = default;
};/* 真实主题:房东 */
class Landlord : public RentHouse {
public:void rentHouse() override {std::cout << " 房东:将房子租出去\n";}
};/* 代理类:中介 */
class Intermediary : public RentHouse {
public:Intermediary() : _landlord(std::make_shared<Landlord>()) {}void rentHouse() override {std::cout << " 中介:发布招租启示\n";std::cout << " 中介:带客户看房\n";_landlord->rentHouse(); // 代理调用真实对象std::cout << " 中介:负责租后维修\n";}private:std::shared_ptr<Landlord> _landlord;
};int main() {Intermediary intermediary;intermediary.rentHouse();return 0;
}
