文章

spdlog指南

spdlog指南

spdlog 是一个只有头文件的C++日志库,速度非常快,扩展性很强,更重要的是 社区活跃,文档齐全

spdlog

1. 快速入门

1.1. 线程安全

若要创建线程安全记录器,请使用 _mt 工厂函数。

1
auto logger = spdlog::basic_logger_mt(...);

若要创建单线程记录器,请使用 _st 工厂函数。

1
auto logger = spdlog::basic_logger_st(...);

2. 创建记录器

每个记录器包含一个或多个 std::shared_ptr<spdlog::sink>,内部以 vector 保存。

2.1. 创建终端输出

1
2
#include "spdlog/sinks/stdout_color_sinks.h"
auto console = spdlog::stdout_logger_st("console");

2.2. 创建终端输出(默认,有颜色)

1
2
3
4
5
6
#include "spdlog/sinks/stdout_color_sinks.h"
auto console = spdlog::stdout_color_st("console");

// 或者使用静态函数
#include "spdlog/spdlog.h"
spdlog::info("Welcome to spdlog version {}.{}.{}  !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH);

2.3. 创建普通文件

1
2
3
#include "spdlog/sinks/basic_file_sink.h"
// Create basic file logger (not rotated).
auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt", true);

2.4. 创建滚动文件

1
2
#include "spdlog/sinks/rotating_file_sink.h"
auto file_logger = spdlog::rotating_logger_mt("file_logger", "logs/mylogfile", 1048576 * 5, 3);

2.5. 创建每日文件

1
2
3
#include "spdlog/sinks/daily_file_sink.h"
// Create a daily logger - a new file is created every day on 2:30am.
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);

2.6. 异步创建

1
2
3
4
#include "spdlog/async.h"
// Default thread pool settings can be modified *before* creating the async logger:
// spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread.
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");

2.7. 记录器管理

details::registry 管理所有的日志对象。使用 <name, logger> 将日志对象和其名称对应起来,后面使用的时候可以直接通过名称获取对应的日志对象。

1
2
3
4
5
6
7
8
// Create rotating file multi-threaded logger
#include "spdlog/sinks/rotating_file_sink.h"
auto file_logger = spdlog::rotating_logger_mt("file_logger", "logs/mylogfile", 1048576 * 5, 3);
file_logger->info("Welcome to spdlog version {}.{}.{}  !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH);

// ...
auto same_logger= spdlog::get("file_logger");
same_logger->info("Welcome to spdlog version {}.{}.{}  !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH);

3. 自定义格式

3.1. 默认格式

每个记录器的接收器都有一个格式化程序,用于将消息格式化为其目标。

SPDLOG 的默认日志记录格式为:

1
[2014-10-31 23:46:59.678] [my_loggername] [info] Some message

有两种方法可以自定义记录器的格式:

  • 设置模式字符串(推荐):
1
set_pattern(pattern_string);

3.2. 自定义格式使用

格式可以全局应用于所有已注册的记录器:

1
spdlog::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***");

或特定记录器对象:

1
some_logger->set_pattern(">>>>>>>>> %H:%M:%S %z %v <<<<<<<<<");

或特定接收器对象:

1
2
some_logger->sinks()[0]->set_pattern(">>>>>>>>> %H:%M:%S %z %v <<<<<<<<<");
some_logger->sinks()[1]->set_pattern("..");

3.3. 匹配模式

模式标志的形式类似于 strftime 函数:%flag

标志意义示例
%v要记录的实际文本“some user text”
%t线程标识“1232”
%P进程标识“3456”
%n记录器的名称“some logger name”
%l消息的日志级别“debug”, “info”, etc
%L消息的短日志级别“D”, “I”, etc
%a工作日名称的缩写“Thu”
%A工作日全名“Thursday”
%b缩写月份名称“Aug”
%B全月名称“August”
%c日期和时间表示形式“Thu Aug 23 15:35:46 2014”
%C年份 2 位数“14”
%Y年份(4 位数字)“2014”
%D%x短月/日/年日期“08/23/14”
%m01-12月“11”
%d月中的某一天 01-31“29”
%H小时数 24 格式 00-23“23”
%I小时 12 格式 01-12“11”
%M分钟 00-59“59”
%S秒 00-59“58”
%e毫秒部分的当前秒 000-999“678”
%f微秒部分当前第二 000000-999999“056789”
%F纳秒部分当前第二 000000000-999999999“256789123”
%p上午/下午“AM”
%r12小时制“02:55:02 PM”
%R24 小时 HH:MM 时间,相当于 %H:%M“23:55”
%T%XISO 8601 时间格式 (HH:MM:SS),相当于 %H:%M:%S“23:55:59”
%zISO 8601 与 UTC 时区的偏移量 (+/-]HH:MM)“+02:00”
%E自纪元以来的秒数“1528834770”
%%% 符号”%”
%+SPDLOG 的默认格式“[2014-10-31 23:46:59.678] [mylogger] [info] Some message”
%^起始颜色范围(只能使用一次)“[mylogger] [info(green)] Some message”
%$结束颜色范围(例如 %^[+++]%$ %v)(只能使用一次)[+++] Some message
%@源文件和行some/dir/my_file.cpp:123
%s源文件的基本名称my_file.cpp
%g宏中显示的源文件的完整或相对路径some/dir/my_file.cpp
%#源行123
%!源函数my_func
%o自上一条消息以来经过的时间(以毫秒为单位)456
%i自上一条消息以来经过的时间(以微秒为单位)456
%u自上一条消息以来经过的时间(以纳秒为单位)11456
%O自上一条消息以来经过的时间(以秒为单位)4

4. 项目分析

4.1. 代码结构

spdlog 的代码结构如下:

1
2
3
4
5
6
7
8
9
10
spdlog
    ├─example  用法代码
    ├─include  实现目录
    │  └─spdlog
    │      ├─details	功能函数目录
    │      ├─fmt		{fmt} 库目录
    │      ├─sinks		落地文件格式实现
    │      └─*.h		异步模式,日志库接口等实现
    ├─src	.cpp 文件,组成编译模块生成静态库使用
    ├─test	测试代码
  1. 提供的日志格式非常丰富,并且允许用户自定义需要的格式。
  2. 对日志文件的类型也做了充分扩展,支持控制台,普通文件,按大小滚动文件,按时间滚动文件,如果不能满足需要,可以自己扩展格式,见 spdlog/sinks/base_sink.h
  3. 支持单/多线程,异步/同步,阻塞非阻塞模式。

4.2. 逻辑图

spdlog.drawio

有几个比较重要的文件:

  • spdlog/spdlog.h 为日志库接口,提供日志宏的属性控制函数。
  • spdlog/logger.h 为日志管理器,为前后端连接的枢纽。
  • spdlog/async.h 为异步模式接口。
  • spdlog/sinks/base_sink.h 为日志文件格式父类,后面所有的日志文件格式都是继承该类来实现不同功能。
  • spdlog/details/registry.h 用于登记所有的 logger

4.3. 接口

4.3.1. 日志接口

spdlog 总体而言提供了日志接口:

  1. spdlog::debug(), 默认的日志对象,使用默认的日志信息格式,输出至 stdout
  2. logger->debug(), 指定日志对象进行日志记录,输出至该日志对象对应的文件中。
  3. SPDLOG_LOGGER_DEBUG(logger), SPDLOG_DEBUG(), 使用宏对以上两种接口进行包装,产生的日志格式包含文件、函数、行。

4.3.2. 文件类型

提供的一些落地的文件类型:

  • 标准输出 stdout_sink_base
  • 带颜色的标准输出(默认),根据平台选择 wincolor_sinkansicolor_sink
  • 基本文件
  • 可设定时间的滚动文件(按天、按小时)
  • 可设定大小的滚动文件

4.3.3. sink 扩展

这里简单提一下 sinks 的实现,所有落地文件的类型都是从 base_sink(忽略sink) 继承而来,提供了两个纯虚函数 sink_it_()flush_(),这样一来,需要扩展的类型文件只需要实现两个函数,很大的简化了扩展的流程。而单/多线程通过模板来确定是否需要使用互斥量来保持同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct null_mutex
{
    void lock() const {}
    void unlock() const {}
};

// 例子使用 base_file_sink
using basic_file_sink_mt = basic_file_sink<std::mutex>;
using basic_file_sink_st = basic_file_sink<details::null_mutex>;

template<typename Mutex>
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg &msg)
{
    std::lock_guard<Mutex> lock(mutex_);
    sink_it_(msg);
}

如果需要使用多线程模式,就使用 std::mutex,如果是单线程,我们就不需要互斥来同步,这里实现一个空的互斥量类,就使得 log() 的代码完全不需要修改了,也提高了内聚性。

参考

[1] Home · gabime/spdlog Wiki (github.com)

[2] spdlog 基本结构分析

本文由作者按照 CC BY 4.0 进行授权