spdlog 简要解析

前言

spdlog 是一个 c++ 日志库,支持同步异步日志、多线程日志、日志格式配置、多种对端输出等功能,这篇文章对 spdlog 的源码做一个简单的梳理,主要介绍 spdlog 打印日志的流程。

总体框架

spglog 日志输出的框架如图所示

在 spdlog 中,通过 logger 前端接收想要打印的日志信息并组装成内部的 log_msg 格式,然后通过 sink 后端将日志信息同步或者异步地输出到终端、文件等多种对端。sink 支持单线程、多线程输出日志,支持不同的日志输出格式等功能。每个 logger 可以包含多个 sink,比如可以同时输出日志到终端和文件。

同步日志

使用 spdlog 同步输出日志的一段代码为:

// 1.创建一个单线程、同步的logger,输出到文件对端
std::shared_ptr<logger> logger 
        = synchronous_factory::create<basic_file_sink<null_mutex>>("logger", "logger.txt");

// 2.打印日志
logger->info("info log, {}, {}, {}", 1, 5.0, "ms");
logger->error("error spdlog, {}", 2);

这里创建了一个同步的、单线程,输出对端为文件的 logger,如果修改 null_mutex 为 是std::mutex,则可以创建一个支持多线程的 logger。synchronous_factory::create 的源代码为:

struct synchronous_factory
{
    template<typename Sink, typename... SinkArgs>
    static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&...args)
    {
        auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
        auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink));
        details::registry::instance().initialize_logger(new_logger);
        return new_logger;
    }
};

可以看出创建 logger 的过程是首先创建一个 sink 并传递到 logger 中,然后调用 registry 的 initialize_logger 接口注册该 logger。registry 是一个全局单例,会把 logger 注册到一个字典中,并提供 logger 的一些管理接口。logger 创建完成后,就可以调用 logger::info,logger::debug 等函数来打印日志,这些打印日志的接口的主要流程如图:

所有的 logger::info,logger::warn,logger::debug 等接口,在内部都会统一调用 log_ 接口,log_ 接口中主要完成四个步骤:检查 level 等级检查该条日志是否需要打印;检查是否需要 trace;生成统一的 log_msg 数据;调用 log_it_ 传递 log_msg 到 sink 后端。然后 logger 会遍历所有的 sink 后端输出 log_msg,并根据条件选择 flush sink 后端。格式化日志以及输出到对端的处理都在 sink 中处理。需要说明的是,如果开启 trace,logger 会将最新的日志 push 到一个环形的 buffer 中,即相当于缓存了最新的几条日志,可以调用 logger 的 dump_backtrace 接口来打印这些缓存的日志。

异步日志

使用 spdlog 异步输出日志的一段代码为:

// 1.创建一个多线程、异步的logger,输出到文件对端
std::shared_ptr<async_logger> logger
        = async_factory::create<basic_file_sink<std::mutex>>("logger", "logger.txt");

// 2.打印日志
logger->info("info log, {}, {}, {}", 1, 5.0, "ms");
logger->error("error spdlog, {}", 2);

这和创建同步日志的代码是类似的,不过是调用的异步的 create 接口,async_factory::create的源代码为:

template<async_overflow_policy OverflowPolicy = async_overflow_policy::block>
struct async_factory_impl
{
    template<typename Sink, typename... SinkArgs>
    static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&...args)
    {
        auto &registry_inst = details::registry::instance();

        // create global thread pool if not already exists..

        auto &mutex = registry_inst.tp_mutex();
        std::lock_guard<std::recursive_mutex> tp_lock(mutex);
        auto tp = registry_inst.get_tp();
        if (tp == nullptr)
        {
            tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1U);
            registry_inst.set_tp(tp);
        }

        auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
        auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy);
        registry_inst.initialize_logger(new_logger);
        return new_logger;
    }
};

和同步日志不相同的是,异步日志会创建一个线程池,并传递给 async_logger。异步日志的主要流程如图:

和同步日志不同的是,异步日志的 async_logger 前端把 log 消息发送到消息队列,后台有一个线程池不断地从消息队列读取消息,然后 sink 在子线程中处理读取的 log 消息。spglog 中的消息队列是一个支持多生产者多消费者的模型,支持多个 async_logger 输入 log 消息,同时也支持后台开启多个线程处理队列中的消息。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值