news 2026/9/11 9:38:06

spdlog C++日志库完整实战:从基础API到MFC集成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
spdlog C++日志库完整实战:从基础API到MFC集成

之前在做 C++ 项目日志模块时,反复纠结于是用 printf 打点、OutputDebugString 输出,还是自己封装一个文件日志类。前两者功能太弱,自研的轮转、分级、线程安全都要从零实现,费时费力还容易埋坑。后来换上了 spdlog,整个日志模块的代码量从上千行降到几十行,而且性能足够好、功能也完整。这篇文章就把 spdlog 的完整使用思路整理出来,包含基础 API、关键配置、完整示例代码,以及 MFC 项目里的集成过程,新手可以直接照着落地,有 C++ 基础的开发者也能快速对照排查问题。

1. 背景与核心概念

1.1 spdlog 是什么

spdlog 是一个开源的 C++ 日志库,由 Gabi Melman 开发并托管在 GitHub 上,项目仓库为gabime/spdlog。它最大的特点是头文件为主、性能优秀、接口简洁,在 C++ 社区中非常流行。

用一句话概括:spdlog 是 C++ 世界里开箱即用的日志方案,提供格式化的日志输出、多级日志级别、多种输出目标(控制台、文件、调试器、网络等),并且内部处理了线程安全、日志轮转、异步写入等繁琐问题。

与自研日志模块和传统日志库相比,spdlog 的核心优势如下:

  • 快速:日志写入通过格式化与批量输出优化,性能高于一般自己写的fprintf方案。
  • 零额外依赖:默认实现不依赖第三方库,使用 C++11 及以上特性。
  • 功能完整:支持日志级别、格式 pattern、轮转文件、按大小或时间分割、异步日志、异常回调。
  • 接入简单:既可以源码编译,也可以直接引入头文件使用。
  • 社区活跃:GitHub 上 star 数量高,issue 更新快,版本迭代稳定。

1.2 spdlog 解决什么问题

在实际项目开发中,日志至少承担这几个职责:

  • 问题排查:程序崩溃、逻辑异常时,靠日志还原现场。
  • 运行监控:记录请求耗时、接口状态、资源变化。
  • 业务审计:保存关键操作记录,方便追溯。
  • 开发调试:替代临时 printf,支持分级输出,方便动态开关。

但自研日志往往要面对以下问题:

  • 多个线程同时写文件,如果不加锁会出现内容交叉。
  • 日志文件越来越大,没有轮转机制。
  • 日志格式五花八门,很难统一解析。
  • 异步落盘与程序退出之间的数据丢失。

spdlog 已经把这些问题全部处理好了,我们要做的只是配置和使用,这大大降低了日志模块的开发成本。

1.3 常见应用场景

spdlog 适合以下几种场景:

  • C++ 服务端程序:Linux/Windows 下的后台服务,需要稳定的文件日志。
  • 客户端软件:比如基于 MFC、Qt 的桌面程序,需要同时输出文件和调试窗口日志。
  • 工具脚本/中间件:C++ 编写的命令行工具、数据中间件,需要记录运行状态。
  • 游戏/嵌入式应用:对性能要求较高的场景,spdlog 的异步模式很适合。

2. 环境准备与版本说明

2.1 spdlog 下载与获取

下载 spdlog 有三种常见方式,读者可以根据项目情况选择。

方式一:GitHub 获取源码

直接访问 GitHub 仓库https://github.com/gabime/spdlog,在 Releases 页面下载最新稳定版的源码包,例如spdlog-x.x.x.zip。这种方式最直观,也方便查看源码和示例。

方式二:vcpkg/conan 包管理

如果项目本身使用 vcpkg,可以直接执行:

vcpkg install spdlog

使用 conan 的话,在conanfile.txt中声明:

[requires] spdlog/1.14.1 [generators] cmake

需要说明的是,具体版本号请以你执行命令时 vcpkg/conan 仓库中的最新版本为准。

方式三:系统包管理器

Ubuntu/Debian 下可以尝试:

sudo apt-get install libspdlog-dev

但系统仓库中的版本可能偏旧,如果对版本有要求,更推荐前两种方式。

2.2 编译与集成方式

spdlog 支持两种集成方式。

仅头文件模式(Header-only)

spdlog 默认是 header-only 的,直接把include/目录加入头文件路径即可,不需要单独编译库文件。这是最快的接入方式,适合中小型项目。

编译成静态库/动态库

如果项目较大,或者希望加快编译速度,可以定义SPDLOG_COMPILED_LIB宏并编译 spdlog 库。CMake 方式如下:

cmake_minimum_required(VERSION 3.10) project(SpdlogDemo) set(CMAKE_CXX_STANDARD 17) add_executable(demo main.cpp) # 方式一:header-only,直接把 include 目录引进来 target_include_directories(demo PRIVATE "path/to/spdlog/include") # 方式二:使用 spdlog 提供的 CMake 目标 # add_subdirectory(path/to/spdlog) # target_link_libraries(demo PRIVATE spdlog::spdlog)

建议使用方式二,通过add_subdirectory引入 spdlog,它会自动处理头文件路径和编译选项。如果你用 vcpkg,CMake 中通过find_package(spdlog REQUIRED)也可以。

2.3 版本与编译器说明

不同版本的 spdlog 对 C++ 标准要求略有不同。老版本通常要求 C++11,新版本对 C++17 支持更好。本文示例基于常见的 1.x 版本写法,重点演示用法,具体 API 名称在不同版本间基本稳定。如果遇到编译错误,先检查版本对应文档,再检查编译标准是否设置正确。

示例环境大致如下:

  • 操作系统:Windows 10/11 或 Ubuntu 20.04+。
  • 编译器:MSVC 2019/2022,或 GCC 9+。
  • CMake:3.14 及以上。
  • C++ 标准:C++17(建议)。

如果项目中使用了 MFC,请确保 spdlog 的头文件路径与 MFC 头文件路径不冲突,这一点后面会专门说明。

3. 核心用法拆解

3.1 核心对象:logger、sink、formatter

spdlog 中最重要的三个概念是 logger、sink、formatter。

  • logger(日志器):对外暴露日志接口的对象,比如logger->info("...")。一个 logger 内部可以有多个 sink。
  • sink(输出目标):日志实际写到哪里,比如控制台、文件、MSVC 调试窗口。每种 sink 负责一种输出渠道。
  • formatter(格式化器):决定日志行的格式,比如是否包含时间戳、线程 id、日志级别。

理解三者的关系后,就可以明白 spdlog 为什么灵活:logger 接收日志消息,经过 formatter 格式化,再交给一个或多个 sink 输出。这样我们可以让同一条日志同时输出到控制台和文件,而不用写两遍代码。

下面是一个典型的多 sink 示例:

#include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/basic_file_sink.h" int main() { // 创建两个 sink:一个彩色控制台,一个普通文件 auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multi.log", true); // 创建 logger,并把两个 sink 都挂上去 spdlog::logger my_logger("multi", {console_sink, file_sink}); my_logger.set_level(spdlog::level::info); my_logger.info("这条日志会同时写入控制台和文件"); my_logger.warn("警告信息也会同时输出"); return 0; }

注意stdout_color_sink_mt中的_mt表示 multi-thread,即线程安全版本。spdlog 提供了_mt_st两种版本,前者适合多线程环境,后者适合单线程环境,性能略高。

3.2 日志级别说明

spdlog 的日志级别从低到高为:

级别枚举值说明
tracespdlog::level::trace最详细,一般用于调试
debugspdlog::level::debug调试信息
infospdlog::level::info普通信息
warnspdlog::level::warn警告
errspdlog::level::err错误
criticalspdlog::level::critical严重错误,可能导致程序退出

设置 logger 级别后,低于该级别的日志不会被输出。比如设置为info,则tracedebug不会记录,这样可以在发布版本中减少日志写入量。

logger->set_level(spdlog::level::info); logger->trace("这条不会输出"); logger->debug("这条不会输出"); logger->info("这条会输出");

3.3 格式化 pattern 详解

在真实项目中,我们不可能只记录一条孤立的文本,通常需要包含时间、线程、日志级别、源文件位置等信息。spdlog 通过 pattern 语法控制输出格式。

常用 pattern 标志:

标志含义
%Y-%m-%d年-月-日
%H:%M:%S时:分:秒
%e毫秒
%t线程 id
%l日志级别
%nlogger 名称
%s源文件名
%#源文件行号
%!函数名
%v日志内容本身
%P进程 id

示例:

#include "spdlog/spdlog.h" int main() { auto logger = spdlog::stdout_color_mt("console"); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [thread %t] [%s:%#] %v"); logger->info("用户登录成功"); return 0; }

输出效果类似:

[2025-01-18 14:22:31.128] [info] [thread 1234] [main.cpp:12] 用户登录成功

注意:%s%#%!这类源码位置信息需要在编译时启用SPDLOG_ACTIVE_LEVEL宏才能完整记录。后面的最佳实践章节会提到这一点。

3.4 刷新策略与性能考虑

spdlog 默认情况下的刷盘策略是为了平衡性能与实时性。如果你的日志非常重要,希望每条日志都立即写入磁盘,可以设置刷新策略:

// 每条日志都立即刷盘 logger->flush_on(spdlog::level::info); // 或者在程序退出时统一刷新 spdlog::shutdown();

在异步模式下,日志先进入队列,由后台线程批量写入文件。这样做的好处是主线程不会因为磁盘 IO 而阻塞,缺点是如果程序崩溃,队列中尚未写入的数据可能会丢失。所以对关键系统,要合理选择异步队列大小和 flush 策略。

4. 完整实战案例

4.1 控制台与文件日志基础案例

先看一个最完整的入门示例。假设我们的项目结构如下:

spdlog_demo/ ├── CMakeLists.txt └── src/ └── main.cpp

CMakeLists.txt内容:

cmake_minimum_required(VERSION 3.14) project(SpdlogDemo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(spdlog_demo src/main.cpp) # 这里假设你已经下载 spdlog 源码并放在 third_party/spdlog 目录 add_subdirectory(third_party/spdlog) target_link_libraries(spdlog_demo PRIVATE spdlog::spdlog)

如果你的环境不想用 CMake,也可以直接把 spdlog 的include目录加入 IDE 头文件包含路径,然后包含头文件即可。

src/main.cpp内容:

#include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/rotating_file_sink.h" int main() { // 1. 初始化一个文件 logger,第二个参数 true 表示追加写 auto file_logger = spdlog::basic_logger_mt("file_logger", "logs/app.log", true); file_logger->set_level(spdlog::level::debug); file_logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v"); // 2. 输出不同级别日志 file_logger->debug("这是一条 debug 日志"); file_logger->info("这是一条 info 日志"); file_logger->warn("这是一条 warn 日志"); file_logger->error("这是一条 error 日志,参数:{}", 42); // 3. 使用花括号格式化 file_logger->info("用户名:{},年龄:{}", "Alice", 25); // 4. 程序退出时刷新并释放资源 spdlog::shutdown(); return 0; }

运行程序后,logs/app.log文件中会出现类似内容:

[2025-01-18 14:30:01.123] [debug] [4421] 这是一条 debug 日志 [2025-01-18 14:30:01.123] [info] [4421] 这是一条 info 日志 [2025-01-18 14:30:01.124] [warn] [4421] 这是一条 warn 日志 [2025-01-18 14:30:01.124] [error] [4421] 这是一条 error 日志,参数:42 [2025-01-18 14:30:01.124] [info] [4421] 用户名:Alice,年龄:25

这段代码里最重要的两个点是:

  • spdlog::basic_logger_mt创建的 logger 默认是多线程安全的,适合在多个线程中共享使用。
  • {}是 spdlog 的格式化占位符,相当于 C 语言printf里的%d%s,但类型安全。

4.2 轮转文件日志(按大小分割)

实际项目中,单文件日志会越来越大,既不便于查看,也容易占满磁盘。spdlog 的rotating_logger_mt支持按大小自动轮转。

#include "spdlog/spdlog.h" #include "spdlog/sinks/rotating_file_sink.h" int main() { // 参数说明: // 第一个参数:logger 名称 // 第二个参数:日志文件路径 // 第三个参数:每个日志文件最大字节数(这里 1MB) // 第四个参数:最多保留日志文件个数 auto logger = spdlog::rotating_logger_mt("rotating", "logs/rotate.log", 1024 * 1024, 3); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v"); for (int i = 0; i < 10000; ++i) { logger->info("这是第 {} 条日志", i); } spdlog::shutdown(); return 0; }

logs/rotate.log超过 1MB 时,spdlog 会自动把它改名为rotate.1.log,并新建一个新的rotate.log。如果已经有rotate.1.log,则继续滚成rotate.2.log。最多保留 3 个文件,超出后最老的会被删除。

这种机制在生产环境非常实用,建议在服务类程序中优先使用轮转文件日志。

4.3 异步日志的配置

如果日志写入量很大,或者在主线程中写日志不能阻塞业务逻辑,可以用异步模式。异步模式下,日志先进入队列,后台线程负责批量写入。

#include "spdlog/spdlog.h" #include "spdlog/async.h" #include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/basic_file_sink.h" int main() { // 初始化异步线程池,参数1:队列大小,参数2:后台线程数 spdlog::init_thread_pool(8192, 1); // 创建异步文件 logger auto logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_logger", "logs/async.log", true); logger->set_level(spdlog::level::info); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v"); logger->info("异步日志写入开始"); for (int i = 0; i < 100; ++i) { logger->info("异步日志:{}", i); } // 异步模式下,一定要调用 shutdown 或 drop,否则可能丢失队列中的日志 spdlog::shutdown(); return 0; }

需要提醒的是,异步日志在程序崩溃时会存在丢失风险。对于记录崩溃原因的日志,可以考虑单独使用同步模式,确保数据马上落盘。

4.4 MFC 集成 spdlog 的完整案例

接下来看一个更贴近实际工程的案例:在 MFC 项目中使用 spdlog 例子代码。

MFC 项目与普通控制台程序有几个不同点:

  • MFC 项目通常使用 Unicode 字符集,即CString内部是wchar_t
  • 除了写文件,我们还希望日志输出到 Visual Studio 的输出窗口,方便调试。
  • 页面卡死或崩溃前,往往需要将诊断信息写入本地文件。

下面给出标准做法。

4.4.1 创建 MFC 项目结构
MfcSpdlogDemo/ ├── MfcSpdlogDemo.h ├── MfcSpdlogDemo.cpp ├── MfcSpdlogDemoDlg.h ├── MfcSpdlogDemoDlg.cpp └── spdlog/ └── include/ ...

假设项目已经创建好了 MFC 对话框程序,接下来做日志封装。

4.4.2 添加头文件包含路径

在项目属性 -> C/C++ -> 常规 -> 附加包含目录中,添加 spdlog 的 include 路径。如果要把 spdlog 编译成库,需要在链接器中添加对应的 lib;如果使用 header-only 模式,则不需要额外链接。

4.4.3 实现文件与调试窗口双输出

这里我们会用到msvc_sink,它可以把日志输出到 Visual Studio 的 Output 窗口,对 MFC 调试非常方便。这里为了让类型兼容,将宽字符内容转为 UTF-8 后再写入 spdlog。

新建一个LogHelper.h

#pragma once #include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/msvc_sink.h" #include <memory> #include <string> class LogHelper { public: static void Init() { if (m_initialized) { return; } // 1. 文件 sink auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/mfc_app.log", true); // 2. MSVC 调试窗口 sink auto msvc_sink = std::make_shared<spdlog::sinks::msvc_sink_mt>(); // 3. 组合成 logger auto logger = std::make_shared<spdlog::logger>("mfc_logger", spdlog::sinks_init_list{file_sink, msvc_sink}); logger->set_level(spdlog::level::debug); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v"); spdlog::set_default_logger(logger); m_initialized = true; } // 将 CString 转换为 UTF-8 字符串再输出 static void Info(const CString& msg) { std::string utf8 = CStringToUtf8(msg); spdlog::info(utf8); } static void Warn(const CString& msg) { std::string utf8 = CStringToUtf8(msg); spdlog::warn(utf8); } static void Error(const CString& msg) { std::string utf8 = CStringToUtf8(msg); spdlog::error(utf8); } private: static std::string CStringToUtf8(const CString& str) { if (str.IsEmpty()) { return ""; } #ifdef _UNICODE // 宽字符转 UTF-8 int len = WideCharToMultiByte(CP_UTF8, 0, str.GetString(), -1, nullptr, 0, nullptr, nullptr); std::string result(len - 1, '\0'); WideCharToMultiByte(CP_UTF8, 0, str.GetString(), -1, &result[0], len - 1, nullptr, nullptr); return result; #else // 多字节环境下直接返回,但需要确保源码文件保存为 UTF-8 return std::string(str.GetString()); #endif } static bool m_initialized; };

LogHelper.cpp中初始化静态变量:

#include "LogHelper.h" bool LogHelper::m_initialized = false;
4.4.4 在 MFC 程序中使用

在对话框初始化函数中调用LogHelper::Init(),然后就可以在按钮事件、线程函数中使用:

void CMfcSpdlogDemoDlg::OnBnClickedButton1() { LogHelper::Init(); CString str; str.Format(_T("用户点击了按钮,参数:%d"), 100)); LogHelper::Info(str); // 模拟一个错误 LogHelper::Error(_T("数据库连接失败,错误码:0x%08X"), 1024); }

这里把日志输出到 VS 的 Output 窗口,开发调试非常直观。同时文件保存为logs/mfc_app.log,用户反馈问题时可以快速收集日志。

4.4.5 MFC 集成注意事项
  • 请将项目字符集设置为 Unicode,确保CStringWideCharToMultiByte配合正常。
  • 如果日志文件中出现乱码,优先检查CStringToUtf8是否正确设置CP_UTF8,以及文件是否以 UTF-8 编码打开。
  • MFC 项目中常见的ERRORminmax等宏可能与 spdlog 产生冲突。如果遇到宏重定义问题,可以在包含 spdlog 头文件之前定义NOMINMAX
#ifndef NOMINMAX #define NOMINMAX #endif #include "spdlog/spdlog.h"
  • 不要在全局对象构造期间过早调用LogHelper::Init(),因为 spdlog 内部可能依赖某些初始化状态。建议在InitInstance或对话框OnInitDialog中初始化。

4.5 运行与结果验证

无论使用控制台项目还是 MFC 项目,验证日志是否正常主要看两个点:

  1. 日志文件是否正确生成,内容是否完整。
  2. 控制台或调试窗口是否出现预期输出。

关于中文乱码,只要保证三处编码一致即可:

  • 源码文件编码(建议 UTF-8 with BOM)。
  • spdlog 写入文件时使用的编码(示例中统一转为 UTF-8)。
  • 查看日志文件的编辑器编码(比如 VSCode 或 Notepad++ 使用 UTF-8 打开)。

5. 常见问题与排查思路

5.1 常见问题排查表

问题现象常见原因解决思路
编译错误:找不到 spdlog.h头文件路径未配置检查附加包含目录是否正确
编译错误:C++ 标准版本过低项目编译器标准低于 C++11设置 C++14 或 C++17
链接错误:无法解析外部符号header-only 模式与编译模式混用统一使用 header-only 或编译库模式
中文日志乱码CString 与 UTF-8 转换不正确使用WideCharToMultiByte(CP_UTF8)转换
日志没有写入文件文件路径错误或权限不足检查目录是否存在,程序是否具备写权限
异步日志丢失程序退出前未调用 shutdown确保程序退出时调用spdlog::shutdown()
与 Windows 宏冲突ERROR、min、max 等宏定义定义NOMINMAX,或调整包含顺序
控制台不显示彩色信息Windows 旧版本控制台不支持 ANSI 转义使用 MSVC 调试窗口 sink,或者输出纯文本

5.2 典型报错细节说明

错误 1:无法解析的外部符号

error LNK2019: 无法解析的外部符号 "class std::shared_ptr<class spdlog::logger> __cdecl spdlog::basic_logger_mt<class spdlog::sinks::basic_file_sink_mt>(...) "

原因:项目同时混用了 header-only 与SPDLOG_COMPILED_LIB编译库模式。

解决方法:统一模式。如果全部使用 header-only,不要定义SPDLOG_COMPILED_LIB;如果定义了该宏,就必须链接 spdlog 库。

错误 2:min/max 宏冲突

在 Windows 下,windows.h中的minmax宏与标准库冲突,导致 spdlog 内部模板编译失败。

解决方法:

#define NOMINMAX #include "spdlog/spdlog.h"

错误 3:日志文件打开失败

如果程序没有创建日志目录的权限,会抛出异常。建议在初始化之前做好目录创建和错误捕捉:

#include <filesystem> namespace fs = std::filesystem; fs::create_directories("logs"); try { auto logger = spdlog::basic_logger_mt("file_logger", "logs/app.log", true); } catch (const spdlog::spdlog_ex& ex) { // 处理日志初始化失败 }

错误 4:CString 输出乱码

原因多数是编码转换问题。请严格按前面的CStringToUtf8方式转换,并确保项目字符集为 Unicode。

6. 最佳实践与工程建议

6.1 全局 logger 管理

在实际项目中,不建议在每次写日志时都创建一个新 logger,而是应该通过封装类或全局函数统一管理。前面 MFC 示例中的LogHelper就是一种做法。

更好的做法是创建一个线程安全、跨模块可用的日志单例,将所有业务模块的日志都汇总到一起。这样写日志的代码只需要一行:

LOG_INFO("用户登录成功,用户名:{}", username);

可以通过宏定义来隐藏 spdlog 的细节:

#define LOG_INFO(...) spdlog::info(__VA_ARGS__) #define LOG_WARN(...) spdlog::warn(__VA_ARGS__) #define LOG_ERROR(...) spdlog::error(__VA_ARGS__) #define LOG_DEBUG(...) spdlog::debug(__VA_ARGS__)

在需要记录源文件位置的场景,可以使用SPDLOG_ACTIVE_LEVELSPDLOG_LOGGER_CALL,但这需要一定宏配置,建议查阅对应版本的源码示例。

6.2 编译期日志级别开关

spdlog 允许在编译期定义一个激活级别,低于该级别的日志连参数都不会求值,从而获得更好的性能。

#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG #include "spdlog/spdlog.h"

此时可以用:

SPDLOG_LOGGER_DEBUG(logger, "调试信息,变量:{}", value);

如果之后把SPDLOG_ACTIVE_LEVEL改为SPDLOG_LEVEL_INFO,则所有SPDLOG_LOGGER_DEBUG会在编译期被直接移除。这在发布版本中能显著减少日志开销。

6.3 日志文件管理建议

  • 使用rotating_logger_mtdaily_logger_mt(按天生成文件),避免单个文件无限增长。
  • 定期清理过期日志,或者通过外部脚本归档。
  • 日志文件路径最好可配置,比如从配置文件读取,而不是写死在代码里。
  • 生产环境建议把日志目录和数据目录分开,防止日志占满磁盘影响业务。

6.4 敏感信息脱敏

日志中不要直接输出密码、token、身份证号等敏感信息。如果业务需要记录参数,建议在写入日志前做脱敏处理。

std::string maskPassword(const std::string& pwd) { if (pwd.size() <= 4) { return "****"; } return "****" + pwd.substr(pwd.size() - 4); }

这是一个容易被忽略但非常重要的工程习惯。

6.5 多线程与性能建议

  • 多线程环境中使用_mt结尾的 sink,保证线程安全。
  • 高频日志使用异步模型,减少主线程阻塞。
  • 不要在生产环境打印超大对象或容器内容,除非确定有必要。
  • 可以使用logger->should_log(spdlog::level::debug)预先判断级别,避免构造昂贵日志参数。

示例:

if (logger->should_log(spdlog::level::debug)) { logger->debug("大型数据:{}", dumpLargeData()); }

6.6 程序退出时的日志处理

使用异步日志时,必须保证程序退出前把队列中剩余日志写完。推荐在main函数末尾或进程退出钩子中调用spdlog::shutdown()。在 MFC 程序中,可以在ExitInstance中调用。

BOOL CMfcSpdlogDemoApp::ExitInstance() { spdlog::shutdown(); return CWinApp::ExitInstance(); }

7. 总结与下一步

到这里,spdlog 的下载、编译、基本 API、文件轮转、异步日志、MFC 集成都已经完整过了一遍。核心要点可以归纳为几句话:

  • spdlog 通过 logger + sink + formatter 的组合,把日志格式、输出目标和日志源解耦开,灵活度高。
  • 生产环境中优先使用轮转文件日志,避免单文件无限增长。
  • 异步日志能提升性能,但必须正确 shutdown,否则有丢失风险。
  • MFC 项目集成时需要处理好 Unicode/UTF-8 转换,以及 Windows 宏冲突。
  • 日志中做好脱敏、分级、路径可配置,是工程化的基本前提。

如果继续深入学习,建议按以下顺序研究:

  1. 阅读 spdlog 源码中example/目录的官方示例。
  2. 掌握自定义 sink 与自定义 formatter 的写法。
  3. 熟悉SPDLOG_ACTIVE_LEVEL等编译级宏的使用。
  4. 在异步模式下研究队列饱和时的丢弃策略与回调处理。

日志系统是每个项目的地基之一,早点选一个成熟的库,能帮你省下很多后面排查问题的精力。自己动手编译一个 demo,然后在真实项目中跑一周,你就能感受到 spdlog 和手写日志之间的差距了。希望这篇文章对你有帮助。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 1:42:33

Proval:自托管代码审查代理,让 MR/PR 审查数据留在内网

这次我们来看一个自托管开发工具&#xff1a;Proval。它本质上是一个代码审查代理&#xff08;code review agent&#xff09;&#xff0c;服务端部署在你自己的环境里&#xff0c;然后接入 GitLab、Forgejo、GitHub 三个主流 Git 代码托管平台。Show HN 这类项目通常更适合关注…

作者头像 李华
网站建设 2026/9/4 15:32:42

Joplin笔记应用:5平台一键同步,数据彻底握在自己手里

Joplin笔记应用&#xff1a;5平台一键同步&#xff0c;数据彻底握在自己手里 【免费下载链接】joplin Joplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS. 项目地址: https://gitcode.com/GitHub_Trending/j…

作者头像 李华
网站建设 2026/9/4 14:44:27

如何挑选 Remotion 模板:从 0 到成片的实用指南

如何挑选 Remotion 模板&#xff1a;从 0 到成片的实用指南 【免费下载链接】remotion &#x1f3a5; Make videos programmatically with React 项目地址: https://gitcode.com/GitHub_Trending/re/remotion Remotion 是一款用 React 写视频的工具&#xff0c;官方仓库…

作者头像 李华
网站建设 2026/9/4 14:34:42

从滴滴运维真题看Linux笔试高频考点与故障排查思路

1. 这份真题&#xff1a;一份值得细读的运维能力画像 前两天整理电脑资料&#xff0c;翻出一份2017年滴滴出行秋招运维岗的笔试题目&#xff0c;来回看了几遍&#xff0c;觉得挺有意思。那会儿正值网约车业务快速扩张期&#xff0c;滴滴的运维团队承担着非常大的线上压力&#…

作者头像 李华