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

Spdlog 进阶:日志基本控制、日志格式控制、异步记录器

日志基本控制

日志等级

怎么确定一条日志的等级?

  • 主观记录:注重为什么?记录这条日志有明确的目的
  • 客观记录:注重记什么?(为什么在前说明偏主观,记什么在前说明偏客观)
trace
  • 记什么:记录基于(技术)实现的步骤信息(通常偏重记录正确路径)
  • 为什么:清楚当前代码的工作机制(总有一天,自己也会忘记这代码是干什么的)
debug
  • 为什么:为了抓住那个 bug
  • 记什么:为了找出错误,想怎么记就怎么记(问题解决后,它们都会被删除)
info
  • 记什么:记录基于业务的过程信息(里程碑,关键节点,状态等)
  • 为什么:帮助发现系统是否正常,同时延缓维护人员退化到必须看trace的时刻
warn
  • 为什么:系统的每一笔业务都还能正确执行,但是出现反常信息
  • 记什么:不影响系统每一笔业务正确执行的反常信息
err
  • 为什么:某一笔业务运行出错了
  • 记什么:业务执行出错信息
critical
  • 记什么:能让系统某些功能直接罢工的事
  • 为什么:这个错误要是出现,系统必然出现大量错误,甚至崩溃

两级控制

记录器拥有一个控制级别,通过logger->level()查看。如果要记录的日志级别低于设置的级别,这条日志就会被抛弃;如果大于等于设置的级别,日志会进入后续的槽。

每个槽都有独立的最小级别控制Sink->level(),规则和记录器一致。

记录器和槽都可以通过:

  • level():查看日志级别
  • set_level():修改日志级别

默认规则:新建的槽,默认使用最低级别 (trace)。如果设置set_level(level::off),相当于关闭该记录器 / 槽,拦截所有日志。

工具函数

  • level::to_string_view():得到级别枚举值名称
  • level::to_short_c_str():得到大写单字母名称(如err会得到E

动态控制

优点:支持程序运行时调整日志级别缺点:不输出的日志,仍然会耗费一定资源

使用规范:

  1. sink加入logger之前,先设置它的级别(默认最低级别:trace
  2. 用常量定义sink的加入顺序,后续如确实需要调整级别,使用该常量为下标
  3. sink加入logger后,如需关闭其输出,使用set_level(off),而非删除
//日志级别调整 void func6() { int const IDX_CONSOLE_SINK = 0;//控制台槽的下标 int const IDX_FILE_SINK = 1;//文件槽的下标 //使用工厂方法创建全新的日志记录器(默认自带一个对应功能的槽) auto levelsLogger = spdlog::stdout_color_mt("LevelsLogger"); //创建文件槽 auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log/levels.txt"); //修改文件槽的级别 fileSink->set_level(spdlog::level::warn); //加入到记录器 levelsLogger->sinks().push_back(fileSink); //查看记录器的级别 levelsLogger->info("LevelsLogger 记录器级别 :{}",spdlog::level::to_string_view(levelsLogger->level())); //查看各个槽的级别 for(auto sink:levelsLogger->sinks()) { levelsLogger->info(spdlog::level::to_short_c_str(sink->level())); } levelsLogger->info("this is info "); levelsLogger->debug("this is debug"); //调整记录器的级别 levelsLogger->set_level(spdlog::level::debug); levelsLogger->debug("本记录器等级已经调整为debug"); levelsLogger->warn("debug 和 info 级别日志不会输出到文件"); levelsLogger->sinks()[IDX_FILE_SINK]->set_level(spdlog::level::debug); levelsLogger->debug("文件也能看到bug了"); }

//levels.txt内容 [2026-05-21 09:33:25.329] [LevelsLogger] [warning] debug 和 info 级别日志不会输出到文件 [2026-05-21 09:33:25.329] [LevelsLogger] [debug] 文件也能看到bug了

日志格式控制

学习 spdlog 中最简单的格式控制(模式匹配):

1.使用{}作为占位符,简单好用

spdlog::info("服务器{}:{}开始监听","1.1.1.1",8080); //[2026-01-01 00:01:11:111][info] 服务器1.1.1.1:8080开始监听

2.自定义输出内容匹配模式(pattern)

logger 或 sink ->set_pattern("格式指定串"); 例:logger->set_pattern("[%Y年%m月%d日 %H:%M:%S]- 【%l】 %n::%^%v%$");

格式符说明:

  • %Y-%m-%d %H:%M:%S:日期和时间
  • %l:日志级别
  • %^ %$:设置颜色,仅对控制台有效;spdlog 1.15.2 版本仅支持使用一次
  • %n:记录器名称(建议使用业务 / 层次名称)
  • %v:原始日志内容
//修改Pattern void func7() { spdlog::info("原有格式"); spdlog::info("服务器已经在{}:{}开始监听","36.2.2.16",8080); spdlog::info("开始修改 Pattern"); //创建带颜色的控制台日志记录器 auto mainLogger = spdlog::stdout_color_mt("主站"); //自定义格式 mainLogger->set_pattern("[%Y年%m月%d日 %H:%M:%S]-%^ 【%l】 %n::%v%$"); //创建文件sink auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log/pattern.txt"); fileSink->set_pattern("[%Y-%m-%d %H:%M:%S] >%l< [%n] %v"); mainLogger->sinks().push_back(fileSink); //调整为最低级别 mainLogger->set_level(spdlog::level::trace); //替换全局默认记录器 spdlog::set_default_logger(mainLogger); spdlog::info("自定义格式起作用了"); spdlog::info("服务器已经在{}:{}开始监听","36.2.2.16",8080); spdlog::warn("这是一个warn"); spdlog::error("服务器无法连接数据库"); spdlog::critical("严重错误"); spdlog::trace("这是一个trace"); }

//pattern.txt内容 [2026-05-21 11:31:51] >info< [主站] 自定义格式起作用了 [2026-05-21 11:31:51] >info< [主站] 服务器已经在36.2.2.16:8080开始监听 [2026-05-21 11:31:51] >warning< [主站] 这是一个warn [2026-05-21 11:31:51] >error< [主站] 服务器无法连接数据库 [2026-05-21 11:31:51] >critical< [主站] 严重错误 [2026-05-21 11:31:51] >trace< [主站] 这是一个trace

异步记录器

同步日志

  1. 同步≠立即写入:虽是同步模式,目标存在缓冲区,未灌满时日志未落地,仍可能丢失;
  2. 业务线程阻塞:同步模式下,必须等待日志写入完成,才能继续执行业务逻辑。

由于缓冲区的存在,同步日志的性能足以支撑大多数业务系统。重要日志可使用:

  • flush():强制刷新缓冲区
  • spdlog::flush_every(时长)定期强制刷新(内部创建线程,编译需加-pthread
//flush缓冲区 void func8() { auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log/flush.txt"); spdlog::default_logger()->sinks().push_back(fileSink); spdlog::info("写入日志,对比屏幕输出flush.txt内容"); spdlog::default_logger()->flush(); spdlog::info("已经强制刷新日志缓冲区"); }

//flush_every缓冲区 void func9() { //每三秒强制清空一次 spdlog::flush_every(std::chrono::seconds(3)); auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log/flush_every.txt"); spdlog::default_logger()->sinks().push_back(fileSink); spdlog::info("写入日志,对比屏幕输出flush_every.txt内容,并等待3秒"); spdlog::info("写入日志,对比屏幕输出flush_every.txt内容,并等待2秒"); spdlog::info("写入日志,对比屏幕输出flush_every.txt内容,并等待1秒"); }


异步日志

机制:主线程将日志任务放入队列,交由后台工作线程异步执行,支持多线程处理。可配置:线程数、队列上限、溢出策略。

#include <spdlog/async.h> // step 1: 初始化异步线程池:队列大小、后台线程数 spdlog::init_thread_pool(8192, 1); // 参数:异步任务队列上限(8K条),线程数(1~1000) // step 2: 定义异步工厂,指定溢出策略 using factory = spdlog::async_factory_impl<spdlog::async_overflow_policy::block>; // step 3: 创建异步日志记录器 auto asyncColorLogger = spdlog::stdout_color_mt<factory>("惟一名称");

任务溢出策略

策略行为说明常用程度
block阻塞等待,直到队列有空位(会卡业务线程)常用
discard_new直接丢弃当前最新的日志相对少用
overrun_oldest保留新日志,丢弃队列中最旧的日志相对多用

预定义工厂(直接使用,无需自定义)

  • async_factory:对应block策略
  • async_factory_nonblock:对应overrun_oldest策略
//异步日志记录器 void func10() { spdlog::init_thread_pool(1000,1); //创建带颜色的异步控制台记录器 auto asyncColorLogger = spdlog::stdout_color_mt<spdlog::async_factory>("AsyncLogger"); //创建文件槽 auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log/async_file.txt"); asyncColorLogger->sinks().push_back(fileSink); spdlog::set_default_logger(asyncColorLogger); spdlog::info("异步记录器,它有{}个槽",spdlog::default_logger()->sinks().size()); }
//async_file.txt内容 [2026-05-21 14:11:16.053] [AsyncLogger] [info] 异步记录器,它有2个槽

异步 vs 同步 优缺点

异步日志的短板:① 日志记录延迟更大(线程切换 + 队列调度);② 程序意外退出时,丢失日志更多(队列 + 系统缓冲区);③ 占用更多内存(存储队列中的日志任务);④ 参数配置更复杂(队列大小 / 线程数 / 溢出策略)。

异步日志的优势:高并发场景下,对业务线程的性能影响降低10 倍

(原笔记:GameServer-Learning/00-Notes/C++/spdlog at main · maomianbaobumoyu/GameServer-Learning)

http://www.jsqmd.com/news/880528/

相关文章:

  • [SpringBoot 对象存储实战]:预签名 URL 直传 OSS 全流程设计与实现
  • Codex CLI高危漏洞CVE-2025-61260深度解析与工程化防御
  • DeepSeek接入codex app使用
  • 模块化触觉显示系统:气动软体机器人与信息论的创新结合
  • 2026宜宾装修公司推荐:宜宾装修公司哪家好/宜宾装修公司电话/宜宾装饰公司哪家好/宜宾装饰公司排行榜/宜宾装饰公司电话/选择指南 - 优质品牌商家
  • 深度专栏 | 撕碎“手工浪漫”:精品可可的硬核工业底色与绝对复现
  • 阴阳师智能自动化脚本:5个步骤实现游戏任务全托管
  • 手机HTTPS抓包全链路解析:从代理配置到SSL Pinning绕过
  • 开源项目推荐:ORIGIN AI Workspace —— 一键部署你的私有 AI 工作站
  • 2026年至今,河北扁钢走线架厂商实力与选择逻辑剖析 - 2026年企业推荐榜
  • Keil单用户许可证(LIC)更新与多设备管理指南
  • ImprovWifi 跨平台传输层设计:把协议层做薄,把宿主层做稳
  • FPG平台:信息透明度建设的深度解析
  • Android 全栈体系 150 讲 - 49 深度完整版 Android 常用设计模式 + 架构模式 源码剖析、业务落地、面试精讲
  • LeetCode 每日一题笔记 日期:2026.05.22 题目:33. 搜索旋转排序数组
  • 智能控制 第六章——集成智能控制系统
  • 基于SpringBoot+用户画像的商品个性化推荐毕业设计
  • Wireshark与FTK Imager电子证据采集实战指南
  • 从零开始单细胞分析:手把手教你用Scanpy复现PBMC3K教程(附避坑指南)
  • FPG平台:行业前景下的战略定位评估
  • 2026年当下常德卫生间防水公司实力盘点:优家房屋修缮中心为何备受青睐? - 2026年企业推荐榜
  • 2026年免费图片去水印保姆级教程:不用下载软件,微信小程序一步搞定
  • 渗透测试工具认知地图:从工作流理解工具本质
  • SpringBoot+Vue校车管理信息系统源码+论文
  • 首发!美团开源最强数字人 LongCat 1.5:性能狂飙15倍,8步闪电成片!
  • 基于Simulink的四开关buck-boost变换器闭环仿真模型
  • 四川钢板生产厂家名录|2026 年 5 月行情走势与价格预测 - 四川盛世钢联营销中心
  • 保姆级教程:在AirSim中用Python实现四旋翼的实时避障(附完整代码与避坑点)
  • SpringBoot+Vue实验室研究生信息管理系统源码+论文
  • 2026年Q2四川消防维修维保品牌名录及选型指南:成都消防维修口碑/消防技术服务/消防改造公司/消防改造多少钱/选择指南 - 优质品牌商家