spdlog是一个常见的第三方日志库,因为诸多优点得到青睐。这篇文章就对spdlog进行一个简单的介绍。内容主要围绕以下几个方面:
一、spdlog的基本信息
二、spdlog的基本概念
三、spdlog的使用相关
一、spdlog的基本信息
简单点儿说spdlog就是一个开源日志库,支持跨平台(Windows、Linux、Mac、Android),主要开发语言是C++,其整体语言占比如下图:
目前为止最新版本是1.10.0
GitHub的网址是:gabime/spdlog: Fast C++ logging library. (github.com)
二、spdlog的基本概念
ps:网上很多文章对于spdlog的介绍都是先表明其特点,然后介绍各自特点的实现原理。本文采用自下而上的方法,希望在第二部分之后读者能对spdlog内部有一个清晰的认知。
2.1 spdlog的结构
spdlog可以分成三级结构,从上而下是logger registry、logger、sink,其各自功能如下:
logger registry(日志管理器):负责管理所有的logger,用户建立的所有logger都会在registry处进行登记然后统一管理
logger(日志记录器):是用户直接操作的对象,通过操作logger进行日志逻辑的生成
sink(日志记录器槽):受logger控制,执行具体的动作(动作包括写入日志文件/输出到控制台)
三级结构的关系如下图所示:
简单来说,就是一个logger registry管多个logger,一个logger管多个sink。logger registry中的logger是通过name进行对应的。后面使用的时候可以直接通过名称获取对应的日志对象。
有了这种层级结构,在代码调用的时候,logger的每个操作都会下顺到sink层面,调用sink的对象。比如像一些set_pattern()和set_level()。
说到底,日志库的目的就是把日志信息写到指定地方。从上面对于结构的功能描述,sink才是真正操作日志进行写操作的结构,那sink可以把日志信息写到哪里呢?主要有三个去向:
1)控制台输出(stdout)——默认输出方式
2)日志文件
3)数据库或其他外部实体
再看这三种去向,控制台输出是为了用户便于即时查看代码状态。通常情况下,日志的数量是比较大的,特别是一些debug的模式。把日志信息存储起来供以后复盘才是更好的方法。从使用的角度上讲,用文件存储日志比直接存储在数据库中更加常用一点。所以本文主要介绍的就是文件的存储方法了。
当然,存储文件也有多种方式了。比如是全写到一个文件中能还是按照某种方式进行分开存放。spdlog中提供了以下几种存放方式,基本能够覆盖对于日志库的常规需求了。
1)当天日志(spdlog::daliy_logger):记录当天的所有日志,但在指定时间点会把日志清空
2)循环日志(spdlog::rotating_logger):日志创建成功后,如果写入的日志大小超过限制就会写入到新日志文件中去。不过同时存在的日志总数是有上限的,达到上限后按指定策略淘汰。需要特别注意的是日志更迭的规则是:当日志A存满时,将日志A名称更改为B,再新建一个日志命名为A,依次类推,直到达到上限数字
3)单个日志(spdlog::basic_logger):只有一个日志文件,所有日志都会在该文件中累加
除了3种文件日志外,输出终端(控制台)也比较常用啦
4)输出终端(spdlog::stdout_color):日志打印至终端,不同等级日志颜色不同
明确了spdlog的结构,再看看spdlog提供的几个特性吧:
2.2 特性——多种日志等级
这个不是spdlog特有的,是很常见的功能,对于日志信息分级可以对日志信息进行筛选,对指定用户显示指定信息了。spdlog以枚举的方式提供了七个等级:
enum level_enum {
trace = SPDLOG_LEVEL_TRACE 0
debug = SPDLOG_LEVEL_DEBUG 1
info = SPDLOG_LEVEL_INFO 2(默认输出等级)
warn = SPDLOG_LEVEL_WARN 3
err = SPDLOG_LEVEL_ERROR 4
critical = SPDLOG_LEVEL_CRITICAL 5
off = SPDLOG_LEVEL_OFF 6
}
等级按照大小进行排序,设定等级A后,小于A的信息将不再输出.
2.3 特性——同步、异步
这里的同步/异步指日志信息是否直接输出/写入文件,直接写就是同步,稍后写就是异步。spdlog默认的状态就是同步了,同步也没什么好说的。这里介绍下异步的逻辑实现:
异步状态下,日志会先存入队列,然后由线程从队列中取数据,当队列满的时候会有淘汰策略。如果工作线程中抛出了异常,向队列写入下一条日志时异常会再次抛出,可以在写入队列时捕捉工作者线程的异常,淘汰策略一般两种:
1)阻塞新来的的日志,直到队列有剩余空间(默认处理方式)
2)把新的日志丢掉(需要设定:spdlog::set_async_mode(队列大小,spdlog::async_overflow_policy::discard_log_msg))
2.4 特性——单、多线程处理模式
spdlog中提供了单线程和多线程模式,由使用者在对象创建中自己指定。
st:单线程版本,不用加锁,效率高,但不保证线程安全
mt:多线程版本,保证多线程并发情况线程安全,但效率稍低
2.5 特性——个性化输出格式(pattern)
一般情况下,希望输出的日志信息中带有各类基本信息。但具体情况要具体分析,spdlog中提供了个性化的输出方式,可以自己指定模式进行输出。基本上就是各类“%参数”的组合。具体内容可以参考:Custom formatting · gabime/spdlog Wiki (github.com)
2.6 特性——刷新方式
刷新方式指日志何时写入文件中,spdlog提供了两种刷新方式:
1)程序正常退出时写入(默认)
2)程序运行中,在指定位置进行写入(实时刷新日志,便于锁定错误所在位置)
要想使用实时刷新日志,spdlog提供了两种方法:
方法一:logger对象->flush_on(设定等级),flush_on是一次性刷新,执行到此时按照设定等级进行日志刷新。
方法二:logger对象->flush_every(周期时间),flush_every是设置刷新周期,定时进行刷新。刷新的级别采取默认了。
2.7 特性——异常处理
对于日志库来说,当异常发生时,应该要输出异常出现的位置。spdlog会向std::err打印一条语句(终端可显示)。为了防止异常语句刷屏,打印的频率固定在每分钟一条。
三、spdlog的使用相关
上一部分介绍了spdlog一些特性,下面结合具体代码明确下各特性的体现。
使用spdlog一定要明确它的各种头文件,常用的头文件如下所示,使用时选择相关的就可以:
最简单的使用:
#include "spdlog/spdlog.h"
int main()
{
//Use the default logger (stdout, multi-threaded, colored)
spdlog::info("Hello, {}!", "World");
}
上面这个代码里面info函数中的格式看上去有点儿奇怪,具体的教程在spdlog输出格式详解_1 - 哔哩哔哩 (bilibili.com)
日志等级显示:
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#include "spdlog/spdlog.h"
int main()
{
spdlog::info("{:<30}", "left aligned");
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::error("Some error message with arg: {}", 1);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::set_level(spdlog::level::debug); // Set global log level to debug
spdlog::debug("This message should be displayed..");
// change log pattern
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
// Compile time log levels
// define SPDLOG_ACTIVE_LEVEL to desired level,if not define, don't show
SPDLOG_TRACE("Some trace message with param {}", 42);
SPDLOG_DEBUG("Some debug message");
return 0;
}
三种文件写入方式:
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h"
#include <iostream>
void basic_logfile_example()
{
try
{
auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
void rotating_example()
{
// Create a file rotating logger with 5mb size max and 3 rotated files
//auto max_size = 1024*1024 * 5;
auto max_size = 256;
auto max_files = 3;
auto logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", max_size, max_files);
for (int i=0; i<10000; i++) {
logger->info("{} * {} equals {:>10}",i, i, i*i);
}
}
void daily_example()
{
// Create a daily logger - a new file is created every day on 2:30am
auto logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
}
int main() {
basic_logfile_example();
rotating_example();
daily_example();
return 0;
}
再从代码使用上看同时操作所有logger
1)apply_all操作所有logger进行输出:
spd::apply_all([&](std::shared_ptr<spdlog::logger> l) {
l->info(“hello world!”);
})
2)drop_all释放所有logger
spdlog::drop_all();
以上说了spdlog这么多,来总结下它的优点吧(其实也就是特性了):
1)丰富的日志输出格式,自定义
2)多种输出文件日志类型,支持控制台
3)支持单多线程、支持同步异步
4)清晰的代码结构,便于读者阅读
5)写日志非常的快,效果很好
留一些学习过程中的参考资料:
参考:spdlog使用_蜗牛单行道的博客-CSDN博客_spdlog
参考:spdlog学习笔记_haojie_superstar的博客-CSDN博客_spdlog
参考(内部有多个demo测试):[C++]-日志记录库SPDLog简介[通俗易懂] - 全栈程序员必看 (javaforall.cn)
参考:spdlog简介_JontyZh的博客-CSDN博客_spdlog 输出格式
源码阅读:spdlog 基本结构分析 - 小胖西瓜 - 博客园 (cnblogs.com)
多线程使用:c++ 日志输出库 spdlog 简介(4)- 多线程txt输出日志 (shuzhiduo.com)
因作者水平有限,如有错误之处,请在下方评论区指正,谢谢!