spdlog 日志库

在项目中弃用 glog,改用 spdlog。花了三个小时,跟了一遍 example.cpp 中提到 xxx_example(),当底层跳转要进入 fmt 时就不再追踪。

学习 spdlog 源码

  1. 在单参数构造函数中明确使用 explicit 关键词,避免隐式构造,避免隐式类型转换。
  2. log.h 存放模板类声明,但在头文件末尾 #include log_impl.h,后者存放模板类的定义
  3. 颜色默认只针对 level 关键词,不同级别预设了不同的色彩。但也可以使用 %^ %$ 划定收尾。
  4. 线程安全,支持多线程
  5. 注册表工厂模式?
  6. fmt/spdlog 如何输出和二进制的?spdlog::to_hex 与 fmt 自身的二进制格式化输出有什么不同? fmt 对自定义类型的支持
  7. spdlog 本身是线程安全的,async_ 定位?
  8. spdlog 开启 FMT_HEADER_ONLY 预编译宏,使用 fmt
  9. spdlog 以 head-only 方式使用

spdlog 整体框架分为两层:logger /async_logger 和 sink。一个 logger 可以拥有多个 sink;一个 sink 可以同时被多个 logger 使用。

spdlog 用户传入的原始内容,在 logger 经过 fmt 格式化 后赋值给 raw_msg,然后结合 time/threadid/level 等属性构造 log_msg 对象传递给底层的 sink 们,sink 负责打印到屏幕或输出到文件(循环写入或者每天生成新文件等多种方式)。

time/threadid/level 等属性和 raw_msg 的格式化1(以怎样的形式打印),是通过 sink 设置 pattern_format

通过两层结构实现两次 level 过滤,实现内容和属性的两次格式化;通过 logger sink 多对多组合,支持不同模块不同级别的日志输出,极大地实现自由度,解耦合。

线程安全 特性:

  • sink 区分 _mt_st 两种:前者加锁,是线程安全的;后者没有使用锁,所以只能在单线程中使用。
  • logger 携带的状态除了错误处理句柄之外都是原子的(无锁结构?),所以如果不修改错误句柄,也是线程安全的。
  • async_logger 是把「递送 log_msg 给 sink,sink 输出」通过队列交给了其余线程来做,当业务线程对于日志模块「递送,输出」耗时敏感时才有必要使用 async_logger 类型。更多请移步到 手册

sup1:此次格式化由 spdlog 约定,和 fmt 无关。

flush 接口,根据底层 sink 类型,要么为空(eg. 控制台),要么调用 std::fflush( std::FILE* stream )2。更多 Flush 策略请查看其 手册

sup2:正如手册中描述的,std::fflush() 并不能用于输入流。扩展阅读

阅读 spdlog 手册

  1. How to use spdlog in DLLs
  2. Default logger,此特性在 v1.3.0 之后才添加

vcpkg

spdlog 在 windows 平台可以使用 vcpkg 部署。但是 vcpkg 部署其依赖的 fmt 时并未采用 head-only 方式,造成在我们自己的项目中需要依赖 fmt.dll 动态库( vcpkg 会自动帮我们拷贝到 .exe 目标目录中)

fmt

  1. fmt 是如何支撑 head-only ?
  2. 为什么同时提供 head-only 和 dll?
  3. Formatting user-defined types
  4. 类型安全 方面的错误,是如何做到在编译期间抛出的?

The fmt::print function performs formatting and writes the result to a stream:

1
fmt::print(stderr, "System error code = {}\n", errno);

The file argument can be omitted in which case the function prints to stdout:

1
fmt::print("Don't {}\n", "panic");

The Format API also supports positional arguments useful for localization:

1
fmt::print("I'd rather be {1} than {0}.", "right", "happy");

Named arguments can be created with fmt::arg. This makes it easier to track what goes where when multiple values are being inserted:

1
2
fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.",
fmt::arg("name", "World"), fmt::arg("number", 42));

fmt 库较小,也可能和其较新有关,网上有关的中文描述很少:Fmt:更方便的 c++ format 库

引入 fmt 时引发的编译失败现象引入 fmt,#include 需要带前置目录,或者需要删除(或重命名)库中的 time.hlocale.h 头文件

为什么放弃 glog

当初放弃 glog,改用 spdlog 的动机大概是以下三点:

  1. 沿用了标准输出流的 <<,在格式化输出上短板;
  2. head file + lib,无法做到 head-only
  3. 服务端在用 spdlog,作为菜鸟初上手 glog 不自信;

现在回头看,似乎都站不住脚。