spdlog 日志库
在项目中弃用 glog,改用 spdlog。花了三个小时,跟了一遍 example.cpp 中提到 xxx_example()
,当底层跳转要进入 fmt 时就不再追踪。
学习 spdlog 源码
- 在单参数构造函数中明确使用
explicit
关键词,避免隐式构造,避免隐式类型转换。 - log.h 存放模板类声明,但在头文件末尾
#include log_impl.h
,后者存放模板类的定义 - 颜色默认只针对 level 关键词,不同级别预设了不同的色彩。但也可以使用
%^
%$
划定收尾。 - 线程安全,支持多线程
- 注册表工厂模式?
- fmt/spdlog 如何输出和二进制的?
spdlog::to_hex
与 fmt 自身的二进制格式化输出有什么不同? fmt 对自定义类型的支持 - spdlog 本身是线程安全的,async_ 定位?
- spdlog 开启
FMT_HEADER_ONLY
预编译宏,使用 fmt - 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 手册
- How to use spdlog in DLLs
- Default logger,此特性在 v1.3.0 之后才添加
vcpkg
spdlog 在 windows 平台可以使用 vcpkg 部署。但是 vcpkg 部署其依赖的 fmt 时并未采用 head-only 方式,造成在我们自己的项目中需要依赖 fmt.dll 动态库( vcpkg 会自动帮我们拷贝到 .exe 目标目录中)
fmt
- fmt 是如何支撑 head-only ?
- 为什么同时提供 head-only 和 dll?
- Formatting user-defined types
- 其 类型安全 方面的错误,是如何做到在编译期间抛出的?
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 | fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.", |
fmt 库较小,也可能和其较新有关,网上有关的中文描述很少:Fmt:更方便的 c++ format 库
引入 fmt 时引发的编译失败现象:引入 fmt,#include
需要带前置目录,或者需要删除(或重命名)库中的 time.h
和 locale.h
头文件
为什么放弃 glog
当初放弃 glog,改用 spdlog 的动机大概是以下三点:
- 沿用了标准输出流的 <<,在格式化输出上短板;
- head file + lib,无法做到 head-only
- 服务端在用 spdlog,作为菜鸟初上手 glog 不自信;
现在回头看,似乎都站不住脚。