C++异步日志中间件需解耦log()与flush_to_file(),用shared_ptr管理线程、atomic控制退出、lockfree队列保线程安全,分层设计:接口层打时间戳、策略层注入过滤/格式化、执行层专注IO与调度。

C++ 里没有语言级装饰器,强行套用“装饰器模式”做异步日志中间件,容易陷入对象生命周期混乱、线程安全裸奔、回调地狱这三重坑——别硬套设计模式名字,先理清谁该拥有日志队列、谁负责线程调度、谁暴露同步/异步接口。
如何让 AsyncLogger 真正异步且不丢日志
关键不是“加个装饰器类”,而是把日志提交(log())和实际写入(flush_to_file())彻底解耦。常见错误是让装饰器对象持有 std::thread 或裸 std::queue,结果析构时线程还在跑、队列被销毁。
- 用
std::shared_ptr管理后台线程的生命周期,配合std::atomic<bool></bool>控制退出标志 - 日志缓冲区必须是线程安全的:用
boost::lockfree::queue或自己封装带std::mutex的std::deque,别用std::queue直接塞多线程 - 提交日志时做快速拷贝(
std::string移动构造),避免在临界区做格式化;格式化延迟到消费线程中 - 示例片段:
class AsyncLogger { std::shared_ptr<std::thread> worker_; std::atomic<bool> stop_{false}; boost::lockfree::queue<LogEntry*, boost::lockfree::capacity<1024>> queue_; // ... };
LoggerDecorator 类不该继承 Logger,而应组合 + 重载 log()
继承会强制所有装饰器共享同一套虚函数表,但异步、压缩、加密、采样这些功能彼此正交,混在继承链里会导致接口爆炸、调用栈变深、性能损耗不可控。更自然的做法是让每个增强行为作为独立策略对象注入。
- 定义统一的
LogSink接口(纯虚write(const LogEntry&)),AsyncLogger是一个实现,FileSink、StdoutSink各自实现 - 装饰逻辑放在
LogRouter或LogPipeline这类组合器里,按需串联多个LogSink,例如:router.add_sink(std::make_shared<asyncsink>(file_sink))</asyncsink> - 避免在装饰器里重写
set_level()等配置方法——配置应作用于底层 sink,装饰器只转发或拦截日志内容
层级封装必须区分“用户接口层”“策略层”“执行层”
很多人把日志宏、格式化器、队列、文件 IO 全塞进一个模板类,导致改个输出路径就得重新编译,加个 JSON 格式要动核心。真正可维护的分层是:
立即学习“C++免费学习笔记(深入)”;
-
用户接口层:提供
LOG_INFO("msg {}", var)宏,背后调用LoggerRegistry::get("app")->log(...),完全无感知异步 -
策略层:包含
LogLevelFilter、RateLimiter、JsonFormatter,每个都是无状态或弱状态对象,通过std::function或策略指针注入 -
执行层:仅含
AsyncSink和FileWriter,专注线程模型与资源管理,不碰业务逻辑 - 注意:
std::chrono::steady_clock::now()必须在用户接口层打时间戳,否则异步写入时时间不准
最常被忽略的是信号安全:如果程序收到 SIGTERM,不能靠析构函数刷完队列——得注册 std::signal 处理器,主动调用 AsyncLogger::stop_and_flush(),且该函数内部不能 new/malloc、不能抛异常、不能用 std::cout。


















