首页 > 编程语言 >spdlog 源码解析

spdlog 源码解析

时间:2024-03-09 15:45:18浏览次数:43  
标签:log 源码 spdlog msg 日志 解析 logger sink

spdlog是开源、高性能、跨平台,支持header-only/compiled的C++日志库。

本文主要目的是对spdlog v1.13.0的源码进行分析(编译运行环境为6.5.0-18-generic #18~22.04.1-Ubuntu),以探讨spdlog如何构建高性能、可扩展的日志框架的。

github链接

  gabime/spdlog: Fast C++ logging library. (github.com)

应用示例

  spdlog/README.md at v1.x · gabime/spdlog (github.com)

spdlog代码特点

  spdlog主要基于C++11开发(若编译环境支持C++20,则将使用std::fmt取代第三方fmt库)

  spdlog中大量使用移动语义、完美转发以减少对象拷贝,又利用内联、模板等技术尽量减少了抽象的代价

  同时广泛使用了智能指针等降低了内存管理的复杂性,通过spdlog可以深入的了解C++11的优雅实现

spdlog架构

  • logger/async_logger

    日志处理的入口,负责格式化日志信息、日志信息的整理合并(如日志级别、文件名、函数名、文件行号等),最终封装至log_msg对象中,再将log_msg对象投递给下游处理。

    logger与aync_logger区别在于:

      logger是同步处理,会由调用日志记录的线程直接将封装后的log_msg对象投递给下游的sink

      aync_logger则是异步处理,调用日志记录的线程仅负责将封装后的log_msg对象放入线程安全队列,后续由线程池从线程安全队列中不断处理队列中的日志对象

 

  • sink

 

    负责接收log_msg对象,并通过formatter将对象中记录的信息转换为字符串,最终将字符串输出到目标位置(控制台、日志文件等)

  • formatter

    负责将log_msg对象中的信息转换成字符串

  • registry

    负责管理所有的logger(创建、销毁、获取等),并且通过registry还可对所有的logger做全局设置

logger

  以调用logger.warn为例,其调用栈基本可分为如下两类:

  • 无需字符串格式化时
logger.warn("hello world!");
调用栈:
template <typename T>
void warn(const T &msg)
    --> void log(level::level_enum lvl, string_view_t msg); // 增加日志等级作为参数传入
        --> void log(source_loc loc, level::level_enum lvl, string_view_t msg); // 增加文件名、函数名、文件行号作为参数传入
            --> void log(source_loc loc, level::level_enum lvl, string_view_t msg); // 将日志信息封装至details::log_msg结构体中
                --> void logger::log_it_(const spdlog::details::log_msg &log_msg, bool log_enabled, bool traceback_enabled);
                    --> void logger::sink_it_(const details::log_msg &msg); // 将封装后的details::log_msg结构体投递给下游的sink
  • 需要字符串格式化时
logger.warn("hello world! {}", "pond-flower");
调用栈:
template <typename... Args>
void warn(format_string_t<Args...> fmt, Args &&...args);
    --> template <typename... Args>
        void log(level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args); // 增加日志等级作为参数传入
        --> template <typename... Args>
            void log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args); // 增加文件名、函数名、文件行号作为参数传入
            --> template <typename... Args>
                void log_(source_loc loc, level::level_enum lvl, string_view_t fmt, Args &&...args); // 字符串格式化后将日志信息封装至details::log_msg结构体中
                --> void logger::log_it_(const spdlog::details::log_msg &log_msg, bool log_enabled, bool traceback_enabled);
                    --> void logger::sink_it_(const details::log_msg &msg); // 将封装后的details::log_msg结构体投递给下游的sink

  结合上述调用栈可看出两种日志记录形式殊途同归,最终将日志信息封装至details::log_msg后投递给下游的sink进行处理

  其中对日志信息最重要的处理行为是将日志信息封装至details::log_msg以及将封装后的detail::log_msg结构体投递给下游的sink,其源码如下:

    template <typename... Args>
    void log_(source_loc loc, level::level_enum lvl, string_view_t fmt, Args &&...args) {
        bool log_enabled = should_log(lvl); // 判断是否需要记录日志(根据日志级别)
        bool traceback_enabled = tracer_.enabled(); // 判断是否需要traceback
        if (!log_enabled && !traceback_enabled) {
            return;
        }

        SPDLOG_TRY {
            memory_buf_t buf;
#ifdef SPDLOG_USE_STD_FORMAT
            fmt_lib::vformat_to(std::back_inserter(buf), fmt, fmt_lib::make_format_args(args...)); // 字符串格式化处理(使用std::fmt)
#else
            fmt::vformat_to(fmt::appender(buf), fmt, fmt::make_format_args(args...)); // 字符串格式化处理(使用第三方fmt库)
#endif

            details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); // 封装日志信息至log_msg对象中
            log_it_(log_msg, log_enabled, traceback_enabled); // 将log_msg对象投递给sink进行处理
        }
        SPDLOG_LOGGER_CATCH(loc)
    }
SPDLOG_INLINE void logger::sink_it_(const details::log_msg &msg) {
    // 遍历所有sink,将msg交由各个sink进行处理
    // 单个logger对象可对应多个sink——即单个输入端允许对应多个输出端
    // 以此实现了同份日志内容可输出至日志文件、控制台等
    for (auto &sink : sinks_) {
        if (sink->should_log(msg.level)) {
            SPDLOG_TRY { sink->log(msg); }
            SPDLOG_LOGGER_CATCH(msg.source)
        }
    }

    // 根据日志级别是否超出flush_level_判断是否需要立即对sink进行flush操作
    if (should_flush_(msg)) {
        flush_();
    }
}
  • 总结

    logger的实现中大量使用了可变参数模板与模板实例化技术,逐步将日志所需要的信息逐层传递,同时通过模板内联避免了函数逐层传递带来的性能损耗,代码简洁易懂、扩展性强,极具参考意义。

async-logger

  async-logger继承自logger,通过重写父类的sink_it_、flush_以实现由线程池执行sink->log(msg)、sink->flush(),实现了异步日志打印   因此async-logger相较于logger新增了两个成员变量
std::weak_ptr<details::thread_pool> thread_pool_; // 线程池对象
async_overflow_policy overflow_policy_; // 日志队列满时处理策略:阻塞、丢弃新日志、丢弃旧日志

  重写后的sink_it_源码如下

SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){
    SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
        pool_ptr->post_log(shared_from_this(), msg, overflow_policy_); // 将日志消息投递到线程池的消息队列中
}
else {
    throw_spdlog_ex("async log: thread pool doesn't exist anymore");
}
}
SPDLOG_LOGGER_CATCH(msg.source)
}
  重写后的flush_源码如下:   
SPDLOG_INLINE void spdlog::async_logger::flush_(){
    SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
        pool_ptr->post_flush(shared_from_this(), overflow_policy_); // 将flush请求投递到线程池的消息队列中
}
else {
    throw_spdlog_ex("async flush: thread pool doesn't exist anymore");
}
}
SPDLOG_LOGGER_CATCH(source_loc())
}

 

 

 

标签:log,源码,spdlog,msg,日志,解析,logger,sink
From: https://www.cnblogs.com/pond-flower/p/18062789

相关文章

  • 通达信牛熊趋势共振主图指标公式源码
    {通达信牛熊趋势共振主图指标公式源码}HH:=HHV(H,9);LL:=LLV(L,9);FG:=(HH-LL)/100;RSV:=(C-LL)/(HH-LL)*100;K1:=SMA(RSV,3,1);DA:=SMA(K1,3,1);J1:=3*K1-2*DA;K:K1*FG+LL,NODRAW;D:DA*FG+LL,NODRAW;J:=J1*FG+LL;DRAWBAND(K,RGB(250,0,0),D,RGB(0,180,0));ZIF:=EM......
  • 通达信六道轮回主图指标公式源码附图
    {通达信六道轮回主图指标公式源码附图}VAR1:=1;VAR2:=EMA(SLOPE(CLOSE,21)*20+CLOSE,55);VAR3:=EMA(CLOSE,3);VAR4:=(2*CLOSE+LOW+HIGH)/4*VAR1;VAR5:=VAR2-VAR3;风险线:EMA(VAR4,45)*1.15;必卖线:EMA(VAR4,55)*1.3;底线:EMA(VAR4,40)*0.85;铁底线:EMA(VAR4,88)*0.7,C......
  • 通达信K线解盘预测指标公式源码
    {通达信K线解盘预测指标公式源码}STICKLINE(CURRBARSCOUNT=118,100,0,200,0),COLORWHITE;{均线}MA1:=MA(C,5),NODRAW;均价:=(OPEN+HIGH+LOW+CLOSE*2)/5,NODRAW;突破:=均价+HIGH-LOW,NODRAW;阻力:=均价*2-LOW,NODRAW;支撑:=均价*2-HIGH,NODRAW;下降:=均价-HIGH+LOW,NODR......
  • 通达信波段低位主图指标公式源码
    {通达信波段低位主图指标公式源码}MA3:MA(C,3),COLORRED,LINETHICK3;MA5:MA(C,5),COLORWHITE;MA10:MA(C,10),COLORGREEN;MA20:MA(C,20),COLORYELLOW,LINETHICK3;NOTEXT1:IF(SAR.sar>=H,SAR.SAR,DRAWNULL),CIRCLEDOT,COLORGREEN;NOTEXT2:IF(SAR.SAR<=L,SAR.SAR,DRAWNULL)......
  • 通达信山谷淘金指标公式源码副图
    {通达信山谷淘金指标公式源码副图}DRAWGBK(C>0,RGB(0,150,150),RGB(0,00,100),0,'',0),LINETHICK2;DRAWTEXT_FIX(1,0,0.1,0,'山谷淘金'),COLOR00FFFF;VAR1:=REF(LOW,1);VAR2:=SMA(Abs(LOW-VAR1),3,1)/SMA(MAX(LOW-VAR1,0),3,1)*100;VAR3:=EMA(IF(CLOSE*1.3,VAR2*......
  • 通达信买入翻倍选股指标公式源码副图
    {通达信买入翻倍选股指标公式源码副图}通达信买入翻倍选股指标公式VAR1:=REF(CLOSE,1);VAR2:=SMA(MAX(CLOSE-VAR1,0),6,1)/SMA(Abs(CLOSE-VAR1),6,1)*100;VAR3:=SMA(MAX(CLOSE-VAR1,0),12,1)/SMA(ABS(CLOSE-VAR1),12,1)*100;VAR4:=(BArslAst(VAR3<20)<=3ANDcrOSS(VAR2,V......
  • 通达信资金进出捉妖指标公式源码附图
    {通达信资金进出捉妖指标公式源码附图}{指标介绍:红线上穿紫线和绿线,后面通常会有一波行情,如果结合后面的红柱那就上升的可能性更大。}VAR1:=C-REF(C,1);VAR2:=O-REF(O,2);VAR3:=H-REF(H,1);VAR4:=L-REF(L,1);VAR5:=(VAR1+VAR2+VAR3+VAR4)/4;资金:=(MA(VAR5*vol,10)+EMA(......
  • 通达信波段强弱指标公式源码副图
    {通达信波段强弱指标公式源码副图}AA:=REF(CLOSE,1);BBB:=SMA(MAX(CLOSE-AA,0),34,1)/SMA(Abs(CLOSE-AA),34,1)*1000;HHH:=BBB-LLV(BBB,36);短期:(MA(HHH,2)*3+HHH*13)/16;中期:MA(HHH,10);差值:短期-中期,NODRAW;CCC:=LLV(BBB,36)-BBB;短期二:(MA(CCC,2)*3+CCC*13)/......
  • 通达信价格中枢主图指标公式源码
    {通达信价格中枢主图指标公式源码}年认同价:=MA(C,240);价格中枢:=(SUM(年认同价,0)/(BARSSINCE(年认同价>0)+239));G:=价格中枢*1.2,LINETHICK2,COLOR009900;G2:价格中枢*1.618,POINTDOT,COLORWHITE;G3:价格中枢*1.382,POINTDOT,COLORWHITE;G1:=价格中枢*0.8,LINETHICK2......
  • app自动化测试环境安装和原理解析
    1.移动测试的基本介绍定义:测试手机程序:appApp程序测试点:功能测试安装卸载测试升级测试兼容性测试不同的手机的系统使用测试环境不同androidios其他:基于Android二次开发系统华为:鸿蒙系统小米:澎湃系统网络切换网络终端使用中来电,短信横竖屏切换健壮性......