当前位置: 首页 > news >正文

《C++ spdlog高性能日志库快速上手》

一、为什么选择 spdlog?

在介绍使用方法之前,先了解一下 spdlog 的核心优势:

  • 极致性能:采用高效的日志处理机制,对主线程影响极小
  • 仅头文件:无需编译链接,直接包含即可使用,简化集成流程
  • 丰富功能:支持多种日志级别、输出目标和格式化选项
  • 线程安全:提供线程安全版本的日志器,适合多线程环境
  • 跨平台:完美支持 Windows、Linux 和 macOS 等主流操作系统
  • 现代化 API:使用 C++11 及以上特性,语法简洁直观

这些特性使 spdlog 成为从小型工具到大型企业级应用的理想选择。

二、第一步:安装 spdlog

spdlog 的安装非常简单,有多种方式可供选择:
方法 1:通过包管理器(推荐)
对于 Linux 或 macOS 用户,可以直接使用系统包管理器安装:

# Ubuntu/Debian
sudo apt-get install libspdlog-dev# macOS (使用Homebrew)
brew install spdlog# Windows (使用vcpkg)
vcpkg install spdlog

方法 2:源码安装
如果需要使用最新版本或特定版本,可以从 GitHub 获取源码:

# 克隆仓库
git clone https://github.com/gabime/spdlog.git# 进入目录
cd spdlog# 创建构建目录
mkdir build && cd build# 编译安装
cmake .. && make -j && sudo make install

方法 3:直接集成到项目
对于小型项目,也可以直接将 spdlog 源码复制到项目中:

  1. 下载 spdlog 源码并解压
  2. 将include目录下的spdlog文件夹复制到你的项目 include 目录
  3. 在代码中直接#include <spdlog/spdlog.h>即可使用

三、入门示例

让我们从一个简单的示例开始,感受 spdlog 的基本用法:

#include <spdlog/spdlog.h>int main() {// 基本日志输出spdlog::info("欢迎使用spdlog日志库!");spdlog::warn("这是一条警告信息");spdlog::error("这是一条错误信息");// 带参数的日志int user_id = 123;std::string user_name = "张三";spdlog::info("用户 {} (ID: {}) 登录成功", user_name, user_id);// 日志级别控制spdlog::set_level(spdlog::level::debug); // 设置最低日志级别为debugspdlog::debug("调试信息:用户登录流程开始");spdlog::set_level(spdlog::level::info);  // 设置最低日志级别为infospdlog::debug("这条调试信息不会被输出");  // 因为级别低于当前设置// 关闭所有日志器spdlog::shutdown();return 0;
}

在这里插入图片描述

把它看成有不同级别的printf输出一样

四、核心功能讲解

1. 日志级别

spdlog 定义了 6 种日志级别,从低到高分别是:

级别功能
trace最详细的日志,通常用于开发调试
debug调试信息,用于开发阶段
info普通信息,记录正常的运行状态
warn警告信息,表示可能存在问题但不影响运行
error错误信息,表示发生了错误
critical严重错误信息,表示发生了致命错误

可以通过spdlog::set_level()设置全局日志级别,也可以为每个日志器单独设置级别。
就像上面代码中一样:

spdlog::set_level(spdlog::level::debug); //设置最低日志级别为debug
spdlog::debug("调试信息:用户登录流程开始"); //也可作为输出工具

2. 输出目标(Sinks)

spdlog 支持将日志输出到多种目标,常见的包括:

目标功能
控制台(带或不带颜色)
文件(单个文件或轮转文件)
系统日志(如 Linux 的 syslog)

下面是一个多目标输出的示例:

#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/rotating_file_sink.h>int main() {// 1. 创建控制台日志器(带颜色)auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();console_sink->set_level(spdlog::level::warn); // 控制台只输出警告及以上级别// 2. 创建基本文件日志器auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log");file_sink->set_level(spdlog::level::info); // 文件输出info及以上级别// 3. 创建轮转文件日志器// 单个文件最大1MB,最多保留3个备份auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("rotating.log", 1024*1024, 3);rotating_sink->set_level(spdlog::level::trace); // 轮转日志记录所有级别// 创建一个多目标日志器spdlog::logger logger("multi_sink_logger", {console_sink, file_sink, rotating_sink});logger.set_level(spdlog::level::trace); // 日志器本身的最低级别// 测试不同级别的日志logger.trace("这是一条trace级别的日志");    // 只输出到轮转日志logger.debug("这是一条debug级别的日志");    // 只输出到轮转日志logger.info("这是一条info级别的日志");      // 输出到文件和轮转日志logger.warn("这是一条warn级别的日志");      // 输出到所有三个目标logger.error("这是一条error级别的日志");    // 输出到所有三个目标logger.critical("这是一条critical级别的日志"); // 输出到所有三个目标spdlog::shutdown();return 0;
}

控制台:
在这里插入图片描述
文件:

[2025-09-16 15:24:22.518] [multi_sink_logger] [info] 这是一条info级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [warning] 这是一条warn级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [error] 这是一条error级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [critical] 这是一条critical级别的日志

轮转日志:

[2025-09-16 15:24:22.518] [multi_sink_logger] [trace] 这是一条trace级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [debug] 这是一条debug级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [info] 这是一条info级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [warning] 这是一条warn级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [error] 这是一条error级别的日志
[2025-09-16 15:24:22.518] [multi_sink_logger] [critical] 这是一条critical级别的日志

3. 日志格式化

spdlog 允许你自定义日志的输出格式,包括时间、日志级别、文件名、行号等信息:

格式标记解释
[%Y-%m-%d %H:%M:%S.%e]输出日期时间,精确到毫秒。%Y 是四位年份,%m 是月份,%d 是日期,%H 是小时(24 小时制),%M 是分钟,%S 是秒,%e 是毫秒。
[%n]输出日志器名称。在 spdlog 中,每个日志器可以被命名,方便区分不同模块或功能的日志。
[%^%l%$]输出日志级别,且带颜色。%l 表示日志级别(如 info、warn、error 等);%^ 和 %$ 是颜色控制标记,spdlog 会根据不同日志级别自动赋予不同颜色(比如 error 级日志通常显示为红色)。
[%s:%#]输出文件名和行号。%s 是产生日志的源文件名称,%# 是该行日志对应的代码行号,便于快速定位日志产生的位置。
%v输出日志消息内容,也就是你通过 logger->info(“消息内容”) 等接口传递的具体日志文本。
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>int main()
{// 设置全局的刷新策略// 每秒刷新spdlog::flush_every(std::chrono::seconds(1));// 遇到debug以上等级的日志立即刷新spdlog::flush_on(spdlog::level::level_enum::debug);// 设置全局的日志输出等级 -- 无所谓 -- 每个日志器可以独立进行设置spdlog::set_level(spdlog::level::level_enum::debug);// 创建同步日志器(标准输出/文件) -- 工厂接口默认创建的就是同步日志器auto logger = spdlog::basic_logger_mt("file-logger", "sync.log");// 设置日志器的刷新策略,以及设置日志器的输出等级// 如果以设置全局就不需要//  logger->flush_on(spdlog::level::level_enum::debug);//  logger->set_level(spdlog::level::level_enum::debug);// 设置日志输出格式logger->set_pattern("[%n][%H:%M:%S][%7l] %v");// 进行简单的日志输出logger->trace("你好! ", "小明");logger->debug("你好! ", "小明");logger->info("你好! ", "小明");logger->warn("你好! ", "小明");logger->error("你好! ", "小明");spdlog::shutdown();return 0;
}

运行后,你将看到sync.log的日志输出:

[file-logger][16:03:24][  debug] 你好! 
[file-logger][16:03:24][   info] 你好! 
[file-logger][16:03:24][warning] 你好! 
[file-logger][16:03:24][  error] 你好! 

4. 异步日志

对于高性能要求的场景,spdlog 提供了异步日志功能,可以避免日志 IO 操作阻塞主线程:

#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>int main() {// 初始化异步日志设置// 队列大小为8192,1个后台线程处理日志spdlog::init_thread_pool(8192, 1);// 创建异步日志器auto async_file_logger = spdlog::basic_logger_mt<spdlog::async_factory>("async_logger", "async_log.log");// 使用异步日志器for (int i = 0; i < 100; ++i) {async_file_logger->info("异步日志消息 {}", i);}// 异步日志需要手动刷新或等待后台线程处理// shutdown()会等待所有日志处理完成spdlog::shutdown();return 0;
}

async_log.log中的一部分:
在这里插入图片描述

五、实际项目中的最佳实践

1.对spdlog进行二次封装

  • 通过 LOG_INFO 等宏直接输出日志,无需每次手动调用 logger->info()
  • 隐藏了 spdlog 的底层接口,新开发者无需深入学习 spdlog 即可上手
  • 在多人协作项目中,封装能强制统一日志格式、输出目标和级别定义
  • 如果未来需要替换日志库(如从 spdlog 换成其他库),封装层可以隔离底层实现
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>
#include <iostream>// mode - 运行模式: true-发布模式; false调试模式
// 定义命名空间 bite_im
namespace bite_im{
std::shared_ptr<spdlog::logger> g_default_logger;// 初始化日志器
// 参数:
// - mode: 运行模式(false=调试模式,true=发布模式)
// - file: 发布模式下的日志文件路径
// - level: 日志输出级别
void init_logger(bool mode, const std::string &file, int32_t level)
{if (mode == false) {// 调试模式:输出到带颜色的控制台,最低日志级别(trace)g_default_logger = spdlog::stdout_color_mt("default-logger");g_default_logger->set_level(spdlog::level::level_enum::trace);g_default_logger->flush_on(spdlog::level::level_enum::trace);}else {// 发布模式:输出到文件,日志级别由参数指定g_default_logger = spdlog::basic_logger_mt("default-logger", file);g_default_logger->set_level((spdlog::level::level_enum)level);g_default_logger->flush_on((spdlog::level::level_enum)level);}// 设置日志格式:[日志器名称][时间][线程ID][级别]消息内容g_default_logger->set_pattern("[%n][%H:%M:%S][%t][%-8l]%v");
}// 日志宏定义,自动添加文件名和行号
#define LOG_TRACE(format, ...) bite_im::g_default_logger->trace(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(format, ...) bite_im::g_default_logger->debug(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(format, ...)  bite_im::g_default_logger->info(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(format, ...)  bite_im::g_default_logger->warn(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) bite_im::g_default_logger->error(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_FATAL(format, ...) bite_im::g_default_logger->critical(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
}

## 是令牌连接符,主要作用是将宏中的两个令牌(token)拼接成一个新的令牌。在你提供的日志宏中,##VA_ARGS 的作用是处理可变参数为空的情况,避免编译错误。

(format, …) 是 C++ 可变参数宏的语法,专门用于处理 “日志格式字符串 + 动态参数” 的场景,核心作用是让日志宏既能接收固定的格式字符串,又能灵活接收任意数量、任意类型的额外参数。

主函数使用:

#include "logger.hpp"int main() {// 初始化调试模式日志bite_im::init_logger(false, "", 0);// 输出不同级别的日志LOG_INFO("应用程序启动");LOG_DEBUG("用户ID: {}", 12345);LOG_WARN("磁盘空间不足");LOG_ERROR("连接数据库失败");return 0;
}

在这里插入图片描述

2. 线程安全考虑

spdlog 提供了两种日志器:
多线程版本:使用_mt后缀的创建函数(如stdout_color_mt)
单线程版本:使用_st后缀的创建函数(如stdout_color_st)

// 1. 创建控制台日志器(带颜色)auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();

在多线程环境中,务必使用多线程版本的日志器,以确保线程安全。


文章转载自:

http://ImyIyDqW.tssmk.cn
http://M2DOsEis.tssmk.cn
http://DjMRT60v.tssmk.cn
http://TSs8tyJD.tssmk.cn
http://fIaNUOQQ.tssmk.cn
http://A6agPCHP.tssmk.cn
http://XAvLXxJF.tssmk.cn
http://pivqfApc.tssmk.cn
http://JgaIgNfN.tssmk.cn
http://Nbh6ScWX.tssmk.cn
http://r9R24wPc.tssmk.cn
http://Vs8ifyor.tssmk.cn
http://QgFFY7Kb.tssmk.cn
http://5zzR9gd2.tssmk.cn
http://VE47wR6X.tssmk.cn
http://KTRNe5zt.tssmk.cn
http://rLZeR4ro.tssmk.cn
http://cvqquKNZ.tssmk.cn
http://ELstdnqQ.tssmk.cn
http://PRVelvZg.tssmk.cn
http://UcX8VO8y.tssmk.cn
http://l5UPwonT.tssmk.cn
http://gjsu9knA.tssmk.cn
http://yXeEw08p.tssmk.cn
http://KjkQcb3B.tssmk.cn
http://f7oAFLrp.tssmk.cn
http://G4BIraUW.tssmk.cn
http://duofzZZ8.tssmk.cn
http://xHIUbcvE.tssmk.cn
http://ljbmzB8q.tssmk.cn
http://www.dtcms.com/a/387205.html

相关文章:

  • 代码随想录学习(二)——二分查找
  • 【代码随想录day 27】 力扣 53. 最大子序和
  • Zynq开发实践(SDK之第一个纯PS工程)
  • 【Spring生态】Spring Cloud
  • HarmonyOS应用拉起系列(三):如何直接拉起腾讯/百度/高德地图进行导航
  • Redis的主从库与切片集群机制
  • 打工人日报#20250916
  • WASM逆向
  • 如何计算最大公约数和最小公倍数
  • 我们设计时间戳的更新时间的时候通常将字段类型设置为int或者bigint 这样能避免2038的问题吗
  • 超越“防被告”:2025跨境电商IPR战略赋能与品牌升值之道
  • Scrapy进阶:POST请求模拟登录实战与管道的使用
  • Zabbix 7.0 配置钉钉告警
  • 知识拓展-智能体和数字人
  • 飞牛NAS部署影视站MooncakeTV
  • yolov8 和OPENCV 自带的目标检测模型 对比
  • 课前练习题-20250916-复习题
  • 基于Transformer-卷积神经网络和度量元学习的高压断路器小样本机械故障诊断
  • 基于 Rust 的 IoT 平台基础功能设计(一)
  • vue+typescript+node的前端项目
  • catkin工程和CMakelist.txt的基本使用
  • 使用 MyCat 实现 MySQL 主从读写分离
  • Visual Studio 函数头显示引用个数
  • 【毕业设计选题】大数据技术专业毕业设计选题指南指南:python(2026 届)
  • 组播实验指导
  • Easylogger与RTT结合使用 Easylogger在FreeRTOS下实现异步输出
  • 【openFeign详细介绍】
  • 抖音私信评论互动消息通知监听自动获取,通过qq机器人转发到qq来通知
  • 19.删除链表的倒数第N个节点(双指针妙用)
  • MySQL笔记5