spdlog实战:日志分级、滚动与性能调优

📚 系列:C++ 工程实践 第 02 篇 ⏱ 阅读约 9 分钟
📌 一句话总结

本文用「先跑通 → 再拆解 → 后封装」三步讲透 spdlog,读完后你能:① 5 分钟跑通异步彩色控制台日志;② 掌握 7 个级别、pattern 格式与多种 sink;③ 拿到一份可直接落地的 Logger 封装(单例 + 宏 + 双 sink + 异步)。

文章目录

    • [1. 为什么需要日志:日志是排障的第一现场](#1. 为什么需要日志:日志是排障的第一现场)
    • [2. spdlog 是什么:高性能是它的第一标签](#2. spdlog 是什么:高性能是它的第一标签)
    • [3. 环境准备与最小示例:先让日志跑起来](#3. 环境准备与最小示例:先让日志跑起来)
      • [3.1 安装](#3.1 安装)
      • [3.2 最小示例:stdout.cc](#3.2 最小示例:stdout.cc)
      • [3.3 逐行拆解](#3.3 逐行拆解)
    • [4. 日志级别与格式化:让日志有「可读性」](#4. 日志级别与格式化:让日志有「可读性」)
      • [4.1 七个级别](#4.1 七个级别)
      • [4.2 格式化 pattern](#4.2 格式化 pattern)
    • [5. Sink 与异步日志:一份日志,多个去处](#5. Sink 与异步日志:一份日志,多个去处)
      • [5.1 常用 sink](#5.1 常用 sink)
      • [5.2 多 sink 组合](#5.2 多 sink 组合)
      • [5.3 异步日志](#5.3 异步日志)
    • [6. 二次封装:把 spdlog 变成项目里「敢直接用」的组件](#6. 二次封装:把 spdlog 变成项目里「敢直接用」的组件)
      • [6.1 接口:logger.h](#6.1 接口:logger.h)
      • [6.2 实现:logger.cc](#6.2 实现:logger.cc)
      • [6.3 使用:main.cc](#6.3 使用:main.cc)
      • [6.4 编译](#6.4 编译)
      • [6.5 几个设计细节](#6.5 几个设计细节)
    • [7. 落地与总结:日志策略比 API 更重要](#7. 落地与总结:日志策略比 API 更重要)

1. 为什么需要日志:日志是排障的第一现场

线上出问题时,你能依赖的东西只有三样:监控指标、链路追踪、日志。前两个告诉你「哪里不对」,日志告诉你「为什么不对」。

  • 调试:本地断点能解决的事,到了生产就只能靠日志。
  • 排查:用户说「下单失败」,日志里能看到入参、下游返回、耗时。
  • 监控:把 error 日志接进告警,比等用户投诉早半小时。

常见 C++ 日志库怎么选:

库 依赖方式 性能 异步 上手成本 适合场景
spdlog header-only 或编译库 高 原生支持 低 大多数 C++ 项目
glog 需编译 中 无(要自己包) 中 Google 系老项目
log4cpp 需编译 较低 有限 中 传统企业项目
Boost.Log 依赖 Boost 中 支持 高 已重度使用 Boost 的项目

选 spdlog 的理由很直接:头文件即用、fmt 风格格式化、内置异步、sink 可插拔。小工具不需要额外依赖,大服务也扛得住。
ℹ️ 版本说明

本文以 spdlog 1.x 为例(1.9 及以上均可编译)。不同发行版打包方式不同,链接参数见第 3 节。

2. spdlog 是什么:高性能是它的第一标签

spdlog 是一个 header-only 的 C++ 日志库(也可以编译成静态/动态库使用),主打「快」和「够用」。

特点 说明 对你的价值
高性能 异步 + 无锁队列 + 编译期格式检查 高频日志不拖慢业务
易用 spdlog::info("x={}", 1) 一行输出 不用自己拼字符串
多 sink 控制台、文件、轮转、按天、网络等 一份日志多处落地
异步日志 后台线程写盘,业务线程只入队 高并发场景友好
线程安全 _mt 后缀的 sink/logger 支持多线程 不用自己加锁
格式化 基于 fmt,{} 做参数替换 + pattern 输出统一、可读

它的结构可以一句话概括:logger 负责收日志,sink 负责写到哪里,异步队列负责削峰。
#mermaid-svg-7MwLFGKk3t0qik1D{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7MwLFGKk3t0qik1D .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7MwLFGKk3t0qik1D .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7MwLFGKk3t0qik1D .error-icon{fill:#552222;}#mermaid-svg-7MwLFGKk3t0qik1D .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7MwLFGKk3t0qik1D .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7MwLFGKk3t0qik1D .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7MwLFGKk3t0qik1D .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7MwLFGKk3t0qik1D .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7MwLFGKk3t0qik1D .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7MwLFGKk3t0qik1D .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7MwLFGKk3t0qik1D .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7MwLFGKk3t0qik1D .marker.cross{stroke:#333333;}#mermaid-svg-7MwLFGKk3t0qik1D svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7MwLFGKk3t0qik1D p{margin:0;}#mermaid-svg-7MwLFGKk3t0qik1D .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7MwLFGKk3t0qik1D .cluster-label text{fill:#333;}#mermaid-svg-7MwLFGKk3t0qik1D .cluster-label span{color:#333;}#mermaid-svg-7MwLFGKk3t0qik1D .cluster-label span p{background-color:transparent;}#mermaid-svg-7MwLFGKk3t0qik1D .label text,#mermaid-svg-7MwLFGKk3t0qik1D span{fill:#333;color:#333;}#mermaid-svg-7MwLFGKk3t0qik1D .node rect,#mermaid-svg-7MwLFGKk3t0qik1D .node circle,#mermaid-svg-7MwLFGKk3t0qik1D .node ellipse,#mermaid-svg-7MwLFGKk3t0qik1D .node polygon,#mermaid-svg-7MwLFGKk3t0qik1D .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7MwLFGKk3t0qik1D .rough-node .label text,#mermaid-svg-7MwLFGKk3t0qik1D .node .label text,#mermaid-svg-7MwLFGKk3t0qik1D .image-shape .label,#mermaid-svg-7MwLFGKk3t0qik1D .icon-shape .label{text-anchor:middle;}#mermaid-svg-7MwLFGKk3t0qik1D .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7MwLFGKk3t0qik1D .rough-node .label,#mermaid-svg-7MwLFGKk3t0qik1D .node .label,#mermaid-svg-7MwLFGKk3t0qik1D .image-shape .label,#mermaid-svg-7MwLFGKk3t0qik1D .icon-shape .label{text-align:center;}#mermaid-svg-7MwLFGKk3t0qik1D .node.clickable{cursor:pointer;}#mermaid-svg-7MwLFGKk3t0qik1D .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7MwLFGKk3t0qik1D .arrowheadPath{fill:#333333;}#mermaid-svg-7MwLFGKk3t0qik1D .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7MwLFGKk3t0qik1D .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7MwLFGKk3t0qik1D .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7MwLFGKk3t0qik1D .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7MwLFGKk3t0qik1D .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7MwLFGKk3t0qik1D .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7MwLFGKk3t0qik1D .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7MwLFGKk3t0qik1D .cluster text{fill:#333;}#mermaid-svg-7MwLFGKk3t0qik1D .cluster span{color:#333;}#mermaid-svg-7MwLFGKk3t0qik1D div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7MwLFGKk3t0qik1D .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7MwLFGKk3t0qik1D rect.text{fill:none;stroke-width:0;}#mermaid-svg-7MwLFGKk3t0qik1D .icon-shape,#mermaid-svg-7MwLFGKk3t0qik1D .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7MwLFGKk3t0qik1D .icon-shape p,#mermaid-svg-7MwLFGKk3t0qik1D .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7MwLFGKk3t0qik1D .icon-shape .label rect,#mermaid-svg-7MwLFGKk3t0qik1D .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7MwLFGKk3t0qik1D .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7MwLFGKk3t0qik1D .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7MwLFGKk3t0qik1D :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务代码
logger
console sink
rotating file sink
async queue
后台线程写盘

图 1:spdlog 的 logger / sink / 异步队列关系

适用场景:后端服务、客户端、日志采集、压测工具、需要长期保留现场的任何程序。

3. 环境准备与最小示例:先让日志跑起来

3.1 安装

系统 / 方式 命令
Ubuntu / Debian sudo apt install libspdlog-dev
CentOS / RHEL sudo yum install spdlog-devel
macOS brew install spdlog
Windows vcpkg install spdlog
header-only 下载源码,把头文件目录加进 include 路径即可

CMake 工程里最省事:find_package(spdlog CONFIG REQUIRED),然后链接 spdlog::spdlog 目标。

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(log_demo CXX)

set(CMAKE_CXX_STANDARD 17)

find_package(spdlog CONFIG REQUIRED)

add_executable(log_demo main.cc)
target_link_libraries(log_demo PRIVATE spdlog::spdlog)

不用 CMake 时直接 g++:

bash 复制代码
g++ -std=c++17 main.cc -o log_demo -lspdlog -lfmt -pthread

⚠️ 注意

发行版的 spdlog 常依赖外部 fmt,需要加 -lfmt;如果链接报找不到 fmt,说明该版本内置 fmt,去掉 -lfmt 即可。header-only 模式则不需要链接 spdlog。

3.2 最小示例:stdout.cc

先看一个「能跑」的版本,它把异步、彩色控制台、级别、格式全用上了:

cpp 复制代码
// stdout.cc
#include <spdlog/async.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>

int main() {
    spdlog::init_thread_pool(8192, 1);                                      // ① 异步线程池
    auto logger = spdlog::stdout_color_mt<spdlog::async_factory>("stdout_logger"); // ② 异步彩色控制台
    logger->set_level(spdlog::level::err);                                  // ③ 只输出 err 及以上
    logger->set_pattern("[%H:%M:%S][%-7l]: %v");                            // ④ 自定义格式

    for (int i = 0; i < 3; ++i) {
        logger->error("hello world - {}", i);                               // ⑤ 输出日志
    }

    spdlog::shutdown();                                                     // ⑥ 退出前刷盘
    return 0;
}

编译运行:

bash 复制代码
g++ -std=c++17 stdout.cc -o stdout_demo -lspdlog -lfmt -pthread
./stdout_demo

输出:

text 复制代码
[21:35:02][error  ]: hello world - 0
[21:35:02][error  ]: hello world - 1
[21:35:02][error  ]: hello world - 2

3.3 逐行拆解

行 代码 作用 不写会怎样
① init_thread_pool(8192, 1) 建 8192 长度的异步队列 + 1 个后台线程 有默认池也能跑,但队列大小不可控
② stdout_color_mt<async_factory> 创建异步 + 多线程安全的彩色控制台 logger 默认创建同步 logger,性能差一截
③ set_level(level::err) 只放行 err / critical 默认 info,会刷屏
④ set_pattern("[%H:%M:%S][%-7l]: %v") 时间 + 级别左对齐 + 内容 使用默认格式,信息少且不好对齐
⑤ logger->error("...{}", i) fmt 风格格式化输出 手写 sprintf 容易越界
⑥ spdlog::shutdown() 等待队列写完、释放资源 异步模式下最后几条日志可能丢

✅ 最佳实践

正式项目里 shutdown 放进程退出路径上(或 RAII 守卫),保证异步队列刷干净再退出。

4. 日志级别与格式化:让日志有「可读性」

4.1 七个级别

spdlog 的级别从低到高,设置某个级别后,低于它的日志会被直接丢弃:

级别 典型内容 建议开启环境
trace 函数进出、变量快照 仅本地排障,短时间开
debug 分支细节、配置加载 开发 / 预发
info 服务启动、关键业务节点 生产默认
warn 可恢复异常、慢查询 生产
err 错误,功能受影响 生产 + 告警
critical 致命错误,服务不可用 生产 + 告警
off 全部关闭 压测、性能测试

示例里 set_level(spdlog::level::err),意味着 info / debug / warn 都不会输出------这是演示「级别过滤」,生产里通常默认 info,排查时临时调到 debug。

cpp 复制代码
logger->set_level(spdlog::level::debug);   // 临时排查,运行时可改
logger->info("now you can see me");

4.2 格式化 pattern

pattern 决定日志长什么样,常用字段:

字段 含义 示例输出
%Y-%m-%d 日期 2026-10-19
%H:%M:%S 时分秒 21:35:02
%e 毫秒 481
%l 级别名 error
%-7l 级别名左对齐、宽度 7 error
%v 日志内容 hello world - 0
%t 线程 id 12345
%n logger 名 stdout_logger

三种常用格式,按场景选:

cpp 复制代码
// 1) 极简控制台:看时间、级别、内容
logger->set_pattern("[%H:%M:%S][%-7l]: %v");

// 2) 生产文件:精确到毫秒 + 线程 + 级别
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%l] %v");

// 3) 多服务:把 logger 名也带上
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%t] [%l] %v");

[%Y-%m-%d %H:%M:%S.%e] [%t] [%l] %v 的输出长这样:

text 复制代码
[2026-10-19 21:35:02.481] [12345] [info] service started, pid=1234
[2026-10-19 21:35:02.482] [12345] [warn] slow query: 1200 ms
[2026-10-19 21:35:02.483] [12345] [error] connect mysql failed, retry=3

5. Sink 与异步日志:一份日志,多个去处

5.1 常用 sink

sink 写到哪里 关键参数
stdout_color_sink_mt 控制台(彩色) 无
basic_file_sink_mt 单个文件追加 文件名、是否截断
rotating_file_sink_mt 按大小轮转 文件名、单文件大小、保留个数
daily_file_sink_mt 按天轮转 文件名、时、分
null_sink_mt 直接丢弃 压测屏蔽日志用

每个 sink 可以单独设级别,实现「控制台少、文件全」:

cpp 复制代码
auto console = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console->set_level(spdlog::level::warn);   // 控制台只看 warn 及以上

auto file = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
    "logs/app.log", 20 * 1024 * 1024, 5);  // 20MB 一个,保留 5 个
file->set_level(spdlog::level::trace);     // 文件记全量

5.2 多 sink 组合

把多个 sink 塞进同一个 logger:

cpp 复制代码
std::vector<spdlog::sink_ptr> sinks{console, file};
auto logger = std::make_shared<spdlog::logger>("app", sinks.begin(), sinks.end());
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%l] %v");
spdlog::register_logger(logger);   // 注册后可用 spdlog::get("app")

5.3 异步日志

同步日志在业务线程里直接格式化 + 写盘,日志一多就卡业务;异步日志把「写」交给后台线程:

cpp 复制代码
spdlog::init_thread_pool(8192, 1);   // 队列长度 8192,1 个后台线程

auto logger = std::make_shared<spdlog::async_logger>(
    "app",
    sinks.begin(), sinks.end(),
    spdlog::thread_pool(),
    spdlog::async_overflow_policy::block);   // 队列满时阻塞,不丢日志

logger->flush_on(spdlog::level::err);        // 错误日志立即刷盘

队列满时怎么办,取决于业务:

策略 行为 适合
block 阻塞业务线程,等队列有空位 业务日志,不能丢
overrun_oldest 覆盖最旧的日志 高频指标,允许丢一部分
discard_new 丢弃新日志 尽力而为的调试日志

🚫 不要无脑选 block

异步队列设得太小、又用 block,高并发下业务线程会被日志反压拖住。先估算峰值日志量,再决定队列大小和溢出策略。

6. 二次封装:把 spdlog 变成项目里「敢直接用」的组件

直接在每个文件里写 spdlog::info(...) 的问题:sink 配置散落、格式不统一、换库要全项目改。做一层薄封装就能解决。

设计目标:

目标 做法
统一入口 单例 Logger + LOG_* 宏
双通道 控制台 warn 以上 + 文件 trace 以上
异步 init_thread_pool + async_logger
格式统一 初始化时 set_pattern 一次
可配置 Init(service, log_dir, level)
安全退出 提供 Shutdown() 刷盘
未初始化兜底 返回默认 logger,避免空指针

6.1 接口:logger.h

cpp 复制代码
#pragma once

#include <memory>
#include <string>
#include <spdlog/spdlog.h>

namespace cm {

class Logger {
public:
    static Logger& Instance();

    // service 用于文件名,log_dir 用于落盘目录
    void Init(const std::string& service,
              const std::string& log_dir,
              spdlog::level::level_enum level = spdlog::level::info);

    // 未初始化时返回默认 logger,保证 LOG_* 不会空指针
    std::shared_ptr<spdlog::logger> Get() const;

    void Shutdown();

private:
    Logger() = default;
    std::shared_ptr<spdlog::logger> logger_;
};

}  // namespace cm

#define CM_LOG_TRACE(...)    ::cm::Logger::Instance().Get()->trace(__VA_ARGS__)
#define CM_LOG_DEBUG(...)    ::cm::Logger::Instance().Get()->debug(__VA_ARGS__)
#define CM_LOG_INFO(...)     ::cm::Logger::Instance().Get()->info(__VA_ARGS__)
#define CM_LOG_WARN(...)     ::cm::Logger::Instance().Get()->warn(__VA_ARGS__)
#define CM_LOG_ERROR(...)    ::cm::Logger::Instance().Get()->error(__VA_ARGS__)
#define CM_LOG_CRITICAL(...) ::cm::Logger::Instance().Get()->critical(__VA_ARGS__)

6.2 实现:logger.cc

cpp 复制代码
#include "logger.h"

#include <spdlog/async.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>

#include <vector>

namespace cm {

Logger& Logger::Instance() {
    static Logger instance;
    return instance;
}

void Logger::Init(const std::string& service,
                  const std::string& log_dir,
                  spdlog::level::level_enum level) {
    spdlog::init_thread_pool(8192, 1);   // 队列 + 后台线程

    auto console = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console->set_level(spdlog::level::warn);   // 控制台只看 warn 以上

    auto file = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
        log_dir + "/" + service + ".log", 20 * 1024 * 1024, 5);
    file->set_level(spdlog::level::trace);     // 文件记全量

    std::vector<spdlog::sink_ptr> sinks{console, file};
    logger_ = std::make_shared<spdlog::async_logger>(
        service, sinks.begin(), sinks.end(),
        spdlog::thread_pool(), spdlog::async_overflow_policy::block);

    logger_->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%^%l%$] %v");
    logger_->set_level(level);
    logger_->flush_on(spdlog::level::err);   // 错误立即刷盘

    spdlog::register_logger(logger_);
}

std::shared_ptr<spdlog::logger> Logger::Get() const {
    return logger_ ? logger_ : spdlog::default_logger();
}

void Logger::Shutdown() {
    if (logger_) {
        logger_->flush();
    }
    spdlog::shutdown();
}

}  // namespace cm

6.3 使用:main.cc

cpp 复制代码
#include "logger.h"

int main() {
    cm::Logger::Instance().Init("order_service", "./logs", spdlog::level::debug);

    CM_LOG_INFO("service started, pid={}", 1234);
    CM_LOG_DEBUG("load config from {}", "./conf/app.conf");
    CM_LOG_WARN("slow query: {} ms", 1200);
    CM_LOG_ERROR("connect mysql failed, retry={}", 3);

    cm::Logger::Instance().Shutdown();   // 退出前刷盘
    return 0;
}

控制台只会看到 warn 及以上:

text 复制代码
[2026-10-19 21:35:02.481] [12345] [warning] slow query: 1200 ms
[2026-10-19 21:35:02.482] [12345] [error] connect mysql failed, retry=3

文件 logs/order_service.log 则包含 info / debug 全量,20MB 轮转、保留 5 个。

6.4 编译

bash 复制代码
g++ -std=c++17 -Iinclude main.cc src/logger.cc -o app -lspdlog -lfmt -pthread

6.5 几个设计细节

ℹ️ 为什么用异步 logger + 单例

单例保证全局只有一个日志器、一套配置;异步 logger 让业务线程只做入队。两者叠加,日志代码对业务几乎零侵入。

  • 初始化顺序 :Logger::Init 要放在 main 开头、其他组件初始化之前;没初始化时宏走默认 logger(输出到控制台),不会崩。
  • 退出顺序 :Shutdown 放在所有业务线程 join 之后,避免线程还在写日志时 logger 被销毁。
  • 运行时调级别 :spdlog::set_level / logger->set_level 是线程安全的,适合做「临时打开 debug」的运维开关。
  • 编译期裁剪 :发布版可以定义 SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO,配合 SPDLOG_LOGGER_DEBUG(...) 宏把 debug 日志在编译期去掉。

7. 落地与总结:日志策略比 API 更重要

API 会用了,项目里更容易翻车的是策略:

策略 建议
默认级别 生产 info,排查时临时 debug
文件轮转 单文件 20~50MB,保留 5~10 个
刷盘 flush_on(err),重要业务 flush_on(info)
脱敏 密码、token、身份证、手机号一律不打印
性能 高频路径避免 trace/debug;大对象不要直接打
保留 至少留 7 天,方便回溯

⚠️ 三个最常见的事故

① 忘了 shutdown,最后几条错误日志丢了;② 文件不轮转,把磁盘写满;③ 把密码打进了日志,安全审计直接不过。
📝 小结

① 先用起来 :stdout.cc 那几行就是最小可用集合------异步、级别、格式、shutdown。

② 再做封装 :单例 + 宏 + 双 sink + 异步,业务只依赖 CM_LOG_*,底层随时可换。

③ 最后定策略:级别、轮转、刷盘、脱敏,比记住多少 API 更重要。
🔜 下一篇

《gtest 实战:从断言到测试夹具,给 C++ 项目补上单元测试》------顺手把日志组件也补上单元测试。关注专栏不迷路。
🔗 参考

1 spdlog GitHub 仓库:https://github.com/gabime/spdlog

2 spdlog 官方 wiki:https://github.com/gabime/spdlog/wiki

3 fmt 格式化库:https://github.com/fmtlib/fmt

相关推荐
GeW13 小时前
新质生产力路线图Linux、数据库、国产化替代一次讲透
linux
aningx15 小时前
cachyos 从 GRUB 切换到 rEFInd
linux
第404号工位17 小时前
40块钱二手瘦客户端,如何跑满家庭物联网中枢?(拒绝Docker爆盘实录)
linux·架构·node.js
骑着蜗牛撵大象32718 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
..Dauntless..19 小时前
【Linux】一切皆文件和缓冲区
linux·运维·服务器
QMCY_jason19 小时前
x86 ubuntu2204 无gui 安装 openeuler 2203 aarch64版本
linux·运维·服务器
重生之小比特19 小时前
【Linux系统】12 进程程序替换 自定义Shell 命令行解释器
linux·运维·服务器
weixin_3077791319 小时前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
AI wins19 小时前
c++新增頭文件
c++