如何使用boost::log named_scope并兼容boost::asio::yield_context及协程?
解决boost::log named_scope与boost::asio协程(yield_context)兼容问题
问题根源
BOOST_LOG_NAMED_SCOPE 默认依赖**线程局部存储(TLS)**维护作用域栈,但协程共享线程执行环境,切换协程时线程TLS不会同步切换;同时协程栈的销毁顺序与执行顺序无关,导致默认的作用域记录会出现混乱、与实际执行逻辑不符的情况。
核心解决方案
将作用域栈与协程上下文绑定,而非线程。通过自定义boost::log的named_scope存储接口,为每个协程维护独立的作用域栈,保证协程切换时作用域记录的正确性。
实现步骤与代码示例
1. 自定义协程专属的作用域栈存储类
实现boost::log::named_scope::scope_stack_interface接口,利用协程上下文作为唯一标识,维护每个协程独立的作用域栈:
#include <boost/log/core.hpp> #include <boost/log/attributes/named_scope.hpp> #include <boost/asio.hpp> #include <boost/asio/spawn.hpp> #include <boost/coroutine2/all.hpp> #include <memory> // 协程作用域栈节点定义 struct ScopeNode { std::string name; const char* file; unsigned int line; ScopeNode* next = nullptr; }; // 协程属性的唯一标识key struct CoroutineScopeStackKey {}; // 自定义作用域栈存储,绑定到协程上下文 class CoroutineScopeStack : public boost::log::named_scope::scope_stack_interface { public: // 推送新作用域 void push(const char* name, const char* file, unsigned int line) override { // 获取当前协程的作用域栈,不存在则初始化为nullptr auto& stack = boost::coroutines2::get_attribute<ScopeNode*>(CoroutineScopeStackKey{}, nullptr); stack = new ScopeNode{name, file, line, stack}; } // 弹出作用域(RAII自动触发) void pop() override { auto& stack = boost::coroutines2::get_attribute<ScopeNode*>(CoroutineScopeStackKey{}, nullptr); if (stack) { auto temp = stack; stack = stack->next; delete temp; } } // 获取当前栈顶作用域 boost::log::named_scope::scope_entry get_current() const override { auto& stack = boost::coroutines2::get_attribute<ScopeNode*>(CoroutineScopeStackKey{}, nullptr); if (stack) { return {stack->name.c_str(), stack->file, stack->line}; } return {}; } // 遍历作用域栈(用于日志格式化) void iterate(boost::log::named_scope::scope_stack_iterator const& visitor) const override { auto& stack = boost::coroutines2::get_attribute<ScopeNode*>(CoroutineScopeStackKey{}, nullptr); for (auto node = stack; node; node = node->next) { if (!visitor.visit(node->name.c_str(), node->file, node->line)) { break; } } } }; // 作用域栈工厂类,用于创建自定义存储实例 class CoroutineScopeStackFactory : public boost::log::named_scope::scope_stack_factory { public: boost::log::named_scope::scope_stack_interface* create_stack() override { return new CoroutineScopeStack(); } };
2. 初始化日志与协程环境
在程序启动时设置自定义作用域栈工厂,确保所有协程使用专属的作用域记录:
int main() { // 初始化日志,添加named_scope全局属性 boost::log::add_common_attributes(); boost::log::core::get()->add_global_attribute("Scope", boost::log::attributes::named_scope()); // 设置自定义协程作用域栈工厂 boost::log::named_scope::set_scope_stack_factory(std::make_shared<CoroutineScopeStackFactory>()); boost::asio::io_context io_ctx; // 启动多个协程模拟并发场景 for (int i = 0; i < 3; ++i) { boost::asio::spawn(io_ctx, [i](boost::asio::yield_context yield) { BOOST_LOG_NAMED_SCOPE((std::string("coroutine_") + std::to_string(i)).c_str()); // 模拟异步操作触发协程切换 boost::asio::steady_timer timer(io_ctx); timer.expires_after(std::chrono::milliseconds(50)); timer.async_wait(yield); BOOST_LOG_TRIVIAL(info) << "协程" << i << "执行中"; { BOOST_LOG_NAMED_SCOPE("inner_task"); BOOST_LOG_TRIVIAL(info) << "协程" << i << "进入子任务"; } BOOST_LOG_TRIVIAL(info) << "协程" << i << "退出主作用域"; }); } io_ctx.run(); return 0; }
关键说明
- 协程绑定逻辑:通过
boost::coroutines2::get_attribute获取当前协程的专属作用域栈,每个协程的栈完全独立,不受其他协程切换影响。 - RAII正确性:
BOOST_LOG_NAMED_SCOPE的RAII对象存储在协程栈上,只有当协程栈销毁时才会触发pop操作,保证作用域生命周期与协程执行逻辑严格一致。 - 性能优化:使用协程属性替代全局映射,无需互斥锁同步,避免线程竞争开销。
内容的提问来源于stack exchange,提问作者unegare
相关产品推荐
相关产品推荐

