跳到主要内容 C++ 中间件 spdlog 日志库介绍与二次封装 | 极客日志
C++
C++ 中间件 spdlog 日志库介绍与二次封装 spdlog 是一款高性能 C++ 日志库,支持零配置、异步日志及多平台。相比 glog,spdlog 在同步场景下性能更优且支持异步写入。 spdlog 的核心概念(日志等级、格式、记录器、工厂类、落地类),展示了同步与异步日志的使用样例,并提供了包含行号文件名功能的二次封装方案,适用于调试与发布模式切换。
CryptoLab 发布于 2026/3/23 更新于 2026/4/16 29K 浏览I. spdlog 的介绍
GitHub 链接
spdlog 是一个高性能、超快速、零配置的 C++ 日志库,它旨在提供简洁的 API 和丰富的功能,同时保持高性能的日志记录。它支持多种输出目标、格式化选项、线程安全以及异步日志记录。特点如下所示:
高性能 :spdlog 专为速度而设计,即使在高负载情况下也能保持良好的性能。
零配置 :无需复杂的配置,只需包含头文件即可在项目中使用。
异步日志 :支持异步日志记录,减少对主线程的影响。
格式化 :支持自定义日志消息的格式化,包括时间戳、线程 ID、日志级别等。
多平台 :跨平台兼容,支持 Windows 、Linux 、macOS 等操作系统。
丰富的 API :提供丰富的日志级别和操作符重载,方便记录各种类型的日志。
spdlog 与 glog 组件的区别
glog 和 spdlog 都是流行的 C++ 日志库,它们各自具有不同的特点和优势。以下是对这两个库的对比分析,包括性能测试的结果和使用场景的考量。
glog 是由 Google 开发的一个开源 C++ 日志库,它提供了丰富的日志功能,包括多种日志级别、条件日志记录、日志文件管理、信号处理、自定义日志格式等。glog 默认情况下是同步记录日志的,这意味着每次写日志操作都会阻塞直到日志数据被写入磁盘。根据性能对比测试分析,glog 在同步调用的场景下的性能较 spdlog 慢 。在一台低配的服务器上,glog 耗时 1.027 秒处理十万笔日志数据,而在固态硬盘上的耗时为 0.475 秒。
spdlog 是一个开源的、高性能的 C++ 日志库,它 支持异步日志记录 ,允许在不影响主线程的情况下进行日志写入。spdlog 旨在提供零配置的用户体验,只需包含头文件即可使用。它还支持多种输出目标、格式化选项和线程安全。在同样的性能测试中,spdlog 在同步调用的场景下比 glog 快 。在低配服务器上的耗时为 0.135 秒,而在固态硬盘上的耗时为 0.057 秒。此外,spdlog 还提供了异步日志记录的功能,其简单异步模式的耗时为 0.158 秒。
对比总结如下:
性能 :从性能测试结果来看,spdlog 在同步调用场景下的性能优于 glog 。当涉及到大量日志数据时,spdlog 显示出更快的处理速度。
异步日志 :spdlog 支持异步日志记录,这在处理高负载应用程序时非常有用,可以减少日志操作对主线程的影响。
易用性 :spdlog 提供了更简单的集成和配置方式,只需包含头文件即可使用,而 glog 可能需要额外的编译和配置步骤。
功能 :glog 提供了一些特定的功能,如条件日志记录和信号处理,这些在某些场景下可能非常有用。
使用场景 :glog 可能更适合那些对日志性能要求不是特别高,但需要一些特定功能的场景。而 则适合需要高性能日志记录和异步日志能力的应用程序。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
spdlog
在选择日志库时,开发者应根据项目的具体需求和性能要求来决定使用哪个库。如果项目对日志性能有较高要求,或者需要异步日志记录来避免阻塞主线程,spdlog 可能是更好的选择 。如果项目需要一些特定的日志功能,或者已经在使用 glog 且没有显著的性能问题,那么继续使用 glog 也是合理的。
sudo apt-get install libspdlog-dev
II. Spdlog 的使用
1. 头文件 && 链接库
注意事项:
由于 spdlog.h 头文件可能没有包含一些落地类的头文件,比如 spdlog/sinks/stdout_color_sinks.h ,所以报错的时候需要我们用 grep 去 /usr/include/spdlog 文件夹中查找一下需要的头文件,然后包含进来!
还需要在编译时候带上 -lfmt 的选项进行链接!
2. 日志输出等级枚举 namespace level {
enum level_enum : int {
trace = SPDLOG_LEVEL_TRACE,
debug = SPDLOG_LEVEL_DEBUG,
info = SPDLOG_LEVEL_INFO,
warn = SPDLOG_LEVEL_WARN,
err = SPDLOG_LEVEL_ERROR,
critical = SPDLOG_LEVEL_CRITICAL,
off = SPDLOG_LEVEL_OFF,
n_levels
};
}
3. 日志输出格式自定义 logger->set_pattern ("%Y-%m-%d %H:%M:%S [%t] [%-8l] %v" );
%t :线程 ID(Thread ID)
%n :日志器名称(Logger name)
%l :日志级别名称(Level name),如 INFO 、DEBUG 、ERROR 等
%v :日志内容(message)
%Y :年(Year)
%m :月(Month)
%d :日(Day)
%H :小时(24-hour format)
%M :分钟(Minute)
%S :秒(Second)
4. 日志记录器类 所有的日志信息都是通过该日志记录器类也就是 logger 类来进行输出的!
创建一个基本的日志记录器,并设置日志级别和输出模式:
namespace spdlog {
class logger {
logger (std::string name);
logger (std::string name, sink_ptr single_sink);
logger (std::string name, sinks_init_list sinks);
void set_level (level::level_enum log_level) ;
void set_formatter (std::unique_ptr<formatter> f) ;
template <typename ... Args>
void trace (fmt::format_string<Args...> fmt, Args &&...args) ;
template <typename ... Args>
void debug (fmt::format_string<Args...> fmt, Args &&...args) ;
template <typename ... Args>
void info (fmt::format_string<Args...> fmt, Args &&...args) ;
template <typename ... Args>
void warn (fmt::format_string<Args...> fmt, Args &&...args) ;
template <typename ... Args>
void error (fmt::format_string<Args...> fmt, Args &&...args) ;
template <typename ... Args>
void critical (fmt::format_string<Args...> fmt, Args &&...args) ;
void flush () ;
void flush_on (level::level_enum log_level) ;
};
}
set_level() 用于设置日志级别的函数。日志级别决定了哪些日志消息会被实际输出,哪些会被忽略。
例如,当你调用 logger->set_level(spdlog::level::warn); 时,只有 warn、error 和 critical 级别的日志消息会被记录,而 trace、debug 和 info 级别的消息将被忽略 。
flush_on() 用于设置日志在何种级别的消息输出时进行刷新操作。刷新操作是将日志信息从缓冲区写入到实际的输出目标(如文件、控制台等)。
例如,当你调用 logger->flush_on(spdlog::level::error);,表示在 输出 error 级别及以上的日志时,会立即刷新日志缓冲区 。在一些需要确保重要日志信息及时输出的场景下很重要,避免信息滞留在缓冲区而没有及时写入文件或显示在控制台,比如在处理错误时,确保错误信息能尽快输出,避免程序崩溃后丢失错误日志。
flush_every() 用于设置日志的定期刷新间隔。
5. 异步日志记录类 异步日志记录类的作用是将日志的写入操作与主线程分离,通过线程池来异步完成日志记录工作,从而提高性能 ,可以使用 spdlog::async_logger 来创建:
class async_logger final : public logger {
async_logger (std::string logger_name,
sinks_init_list sinks_list,
std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block
);
async_logger (std::string logger_name, sink_ptr single_sink,
std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block
);
class SPDLOG_API thread_pool {
thread_pool (size_t q_max_items,
size_t threads_n,
std::function<void ()> on_thread_start,
std::function<void ()> on_thread_stop
);
thread_pool (size_t q_max_items, size_t threads_n, std::function<void ()> on_thread_start);
thread_pool (size_t q_max_items, size_t threads_n);
};
};
std::shared_ptr<spdlog::details::thread_pool> thread_pool () {
return details::registry::instance ().get_tp ();
}
inline void init_thread_pool (size_t q_size, size_t thread_count) ;
auto async_logger = spdlog::async_logger_mt ("async_logger" , "logs/async_log.txt" );
async_logger->info ("This is an asynchronous info message" );
6. 日志记录器工厂类 using async_factory = async_factory_impl<async_overflow_policy::block>;
template <typename Sink, typename ... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async (std::string logger_name, SinkArgs &&...sink_args) ;
template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stdout_color_mt (const std::string &logger_name, color_mode mode = color_mode::automatic) ;
template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stderr_color_mt (const std::string &logger_name, color_mode mode = color_mode::automatic) ;
template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> basic_logger_mt (const std::string &logger_name, const filename_t &filename, bool truncate = false , const file_event_handlers &event_handlers = {}) ;
template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> rotating_logger_mt (const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false ) ;
7. 日志落地类 所谓日志落地类,就是指定日志要往哪里输出 ,是往文件输出,还是往终端设备输出,都是用该类指定的!
namespace spdlog {
namespace sinks {
class SPDLOG_API sink {
public :
virtual ~sink () = default ;
virtual void log (const details::log_msg &msg) = 0 ;
virtual void flush () = 0 ;
virtual void set_pattern (const std::string &pattern) = 0 ;
virtual void set_formatter (std::unique_ptr<spdlog::formatter> sink_formatter) = 0 ;
void set_level (level::level_enum log_level) ;
};
#ifdef _WIN32
using stdout_color_sink_mt = wincolor_stdout_sink_mt;
using stdout_color_sink_st = wincolor_stdout_sink_st;
using stderr_color_sink_mt = wincolor_stderr_sink_mt;
using stderr_color_sink_st = wincolor_stderr_sink_st;
#else
using stdout_color_sink_mt = ansicolor_stdout_sink_mt;
using stdout_color_sink_st = ansicolor_stdout_sink_st;
using stderr_color_sink_mt = ansicolor_stderr_sink_mt;
using stderr_color_sink_st = ansicolor_stderr_sink_st;
#endif
sink_ptr rotating_file_sink (filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false , const file_event_handlers &event_handlers = {}) ;
using rotating_file_sink_mt = rotating_file_sink<std::mutex>;
sink_ptr basic_file_sink (const filename_t &filename, bool truncate = false , const file_event_handlers &event_handlers = {}) ;
using basic_file_sink_mt = basic_file_sink<std::mutex>;
using kafka_sink_mt = kafka_sink<std::mutex>;
using mongo_sink_mt = mongo_sink<std::mutex>;
using tcp_sink_mt = tcp_sink<std::mutex>;
using udp_sink_mt = udp_sink<std::mutex>;
}
}
auto console_logger = spdlog::stdout_logger_mt ("console_logger" );
console_logger->info ("This is an info message to stdout." );
8. 全局接口 spdlog 中的 set_level 接口既存在于 logger 类中,又有全局的 set_level 接口,这种设计是为了提供灵活的日志级别控制 。
void set_level (level::level_enum log_level) ;
void flush_every (std::chrono::seconds interval) ;
void flush_on (level::level_enum log_level) ;
9. 记录日志 logger->trace ("This is a trace message" );
logger->debug ("This is a debug message" );
logger->info ("This is an info message" );
logger->warn ("This is a warning message" );
logger->error ("This is an error message" );
logger->critical ("This is a critical message" );
若要打印占位元素信息,不再像 printf 等函数一样用 %s 等来表示,而是直接用 {} 来表示占位符 ,如下所示:
logger->debug ("你好啊!{}" , "小明" );
III. 使用样例 下面我们分别创建同步日志记录器和异步日志记录器来看看区别,无非就是创建工厂类使用的参数不同而已,其他基本都是一样的!
#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
using namespace std;
int main () {
spdlog::flush_every (std::chrono::seconds (1 ));
spdlog::flush_on (spdlog::level::level_enum::debug);
spdlog::set_level (spdlog::level::level_enum::debug);
auto logger = spdlog::stdout_color_mt ("default-logger" );
logger->flush_on (spdlog::level::level_enum::debug);
logger->set_level (spdlog::level::level_enum::info);
logger->set_pattern ("[%H:%M:%S][%t][%-8l] %v" );
logger->trace ("你好!{}" , "liren" );
logger->debug ("你好!{}" , "liren" );
logger->info ("你好!{}" , "liren" );
logger->warn ("你好!{}" , "liren" );
logger->error ("你好!{}" , "liren" );
logger->critical ("你好!{}" , "liren" );
std::cout << "同步日志输出演示完毕!" << std::endl;
return 0 ;
}
#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/async.h>
using namespace std;
int main () {
spdlog::flush_every (std::chrono::seconds (1 ));
spdlog::flush_on (spdlog::level::level_enum::debug);
auto logger = spdlog::stdout_color_mt <spdlog::async_factory>("default-logger" );
logger->flush_on (spdlog::level::level_enum::debug);
logger->set_level (spdlog::level::level_enum::info);
logger->set_pattern ("[%H:%M:%S][%t][%-8l] %v" );
logger->trace ("你好!{}" , "liren" );
logger->debug ("你好!{}" , "liren" );
logger->info ("你好!{}" , "liren" );
logger->warn ("你好!{}" , "liren" );
logger->error ("你好!{}" , "liren" );
logger->critical ("你好!{}" , "liren" );
std::cout << "异步日志输出演示完毕!" << std::endl;
return 0 ;
}
all : sync async
sync : sync.cc
g++ -std=c++17 -o $@ $^ -lspdlog -lfmt
async : async.cc
g++ -std=c++17 -o $@ $^ -lspdlog -lfmt
IV. spdlog 的二次封装 – logger.hpp
为了避免单例对象的锁冲突 ,所以需要直接创建一个全局的线程安全的日志器进行使用!因为对于日志操作,通常是频繁调用的操作,如果每次调用都需要加锁检查单例对象是否存在,会带来性能损耗,而在程序初始化时创建一个全局的日志器,可以避免这种开销。
因为 spdlog 中的日志输出 没有包含【行号】与【文件名】 ,所以需要用宏来二次封装输出
封装出一个初始化接口,便于使用 !如根据不同模式采取不同的输出落地目标,调试模式输出到标准输出,而发行模式则输出到文件中!
#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <string>
#include <memory>
std::shared_ptr<spdlog::logger> logger;
void init_logger (bool mode, const std::string& file, int level) {
if (mode == false )
{
logger = spdlog::stdout_color_mt ("default-logger" );
logger->set_level (spdlog::level::level_enum::trace);
logger->flush_on (spdlog::level::level_enum::trace);
}
else
{
logger = spdlog::basic_logger_mt ("default-logger" , file);
logger->set_level ((spdlog::level::level_enum)level);
logger->flush_on (spdlog::level::level_enum::trace);
}
logger->set_pattern ("[%n][%H:%M:%S][%t][%-8l]%v" );
}
#define LOG_TRACE(format,...) logger->trace(std::string("[{}:{}] " )+ format,__FILE__,__LINE__,##__VA_ARGS__);
#define LOG_DEBUG(format,...) logger->debug(std::string("[{}:{}] " )+ format,__FILE__,__LINE__,##__VA_ARGS__);
#define LOG_INFO(format,...) logger->info(std::string("[{}:{}] " )+ format,__FILE__,__LINE__,##__VA_ARGS__);
#define LOG_WARN(format,...) logger->warn(std::string("[{}:{}] " )+ format,__FILE__,__LINE__,##__VA_ARGS__);
#define LOG_ERROR(format,...) logger->error (std::string("[{}:{}] " )+ format,__FILE__,__LINE__,##__VA_ARGS__);
#define LOG_CRITICAL(format,...) logger->critical(std::string("[{}:{}] " )+ format,__FILE__,__LINE__,##__VA_ARGS__);
下面是测试样例,结合上前面学到的 gflags 一起使用:
#include "logger.hpp"
#include <gflags/gflags.h>
DEFINE_bool (run_mode, false , "程序运行模式:true 为发布模式,false 为调试模式" );
DEFINE_string (log_file, "" , "发布模式下用于指定日志的输出文件" );
DEFINE_int32 (log_level, 0 , "发布模式下用于指定日志输出等级" );
int main (int argc, char * argv[]) {
google::ParseCommandLineFlags (&argc, &argv, true );
init_logger (FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
LOG_TRACE ("你好啊,{}!" , "liren" );
LOG_DEBUG ("你好啊,{}!" , "liren" );
LOG_INFO ("你好啊,{}!" , "liren" );
LOG_WARN ("你好啊,{}!" , "liren" );
LOG_ERROR ("你好啊,{}!" , "liren" );
LOG_CRITICAL ("你好啊,{}!" , "liren" );
return 0 ;
}
main : main.cc
g++ -std=c++17 -o $@ $^ -lspdlog -lfmt -lgflags