装饰器模式通过Logger接口、LoggerDecorator基类及具体装饰器(如SamplingDecorator、ContextInjectDecorator)实现日志功能动态叠加,支持独立开关、顺序可控与线程安全。

你需要在C++服务中为异步日志系统动态叠加功能(如采样、敏感字段脱敏、上下文注入、格式转换),又不能修改原有日志器核心逻辑,且要求各增强模块可独立开关、顺序可控、线程安全——装饰器模式正是解决这一问题的直接路径。
定义基础日志器接口与默认实现
新建头文件 logger_interface.h,声明纯虚接口 Logger,包含 log 方法,参数为等级、模块名、消息体;所有装饰器和最终落地实现都继承它。
在 default_logger.h 中实现 ConsoleLogger:构造时接收 std::ostream&(默认 std::cout),log 方法直接格式化输出到流。这一步是装饰链的终点,不带任何增强逻辑。
注意:接口中 log 必须是 const 成员函数,否则后续装饰器无法对临时对象调用——【装饰器链必须能接受 const 左值或右值】。
立即学习“C++免费学习笔记(深入)”;
构建可组合的装饰器基类
创建 logger_decorator.h,定义抽象基类 LoggerDecorator,私有持有 std::shared_ptr<Logger> 成员 next_,构造函数强制传入被装饰对象。
所有具体装饰器(如 SamplingDecorator、RedactDecorator)均公有继承 LoggerDecorator,并在其 log 方法中先执行自身逻辑,再调用 next_->log(...)。
这一步的关键是:装饰器本身不负责内存管理,next_ 的生命周期由外部控制;若传入空指针,log 方法应直接返回而非解引用——【空 next_ 会导致段错误,必须判空】。
实现采样装饰器(按比例丢弃日志)
方法一:使用 std::random_device + std::uniform_real_distribution<double> 生成 [0,1) 随机数,与配置采样率比较。
方法二:采用原子计数器递增后取模,避免随机数生成开销,适合高频日志场景;例如每 100 条留 1 条,就用 counter_.fetch_add(1, std::memory_order_relaxed) % 100 == 0 判断是否通过。
采样率设为 0.01 表示保留约 1% 日志;设为 0 则全部丢弃,设为 1 则全量透出。注意:该装饰器必须放在链首,否则上游已过滤的日志无法被下游装饰器处理。
实现上下文注入装饰器
第一步:定义线程局部存储结构 thread_local std::unordered_map<std::string, std::string> context_,用于保存 trace_id、user_id 等键值对。
第二步:在 ContextInjectDecorator::log 中,遍历 context_,将每对键值插入到原始消息前,格式为 [key=value],多个上下文项用空格分隔。
第三步:提供静态接口 set_context(const std::string& key, const std::string& value) 和 clear_context(),供业务代码在请求入口/出口调用。
这一步不可省略线程局部性声明——若用全局 map,多线程并发写会崩溃;【thread_local 缺失将导致数据污染和 core dump】。
组装完整装饰链并启用异步投递
在初始化阶段,按需顺序拼接装饰器:例如 std::make_shared<SamplingDecorator>(0.05, std::make_shared<ContextInjectDecorator>(std::make_shared<RedactDecorator>(std::make_shared<ConsoleLogger>())))。
将最终得到的 std::shared_ptr<Logger> 传入异步日志队列包装器(如基于 boost::lockfree::queue 或 moodycamel::ConcurrentQueue 实现的 AsyncLoggerWrapper)。
该包装器内部启动独立线程,从队列取出日志条目后,调用装饰链的 log 方法——至此,所有增强逻辑均在异步线程中执行,主线程零阻塞。


















