详解C++ 高性能日志库 spdlog

Source

一、概述

spdlog 是一个开源、高性能、跨平台的C++日志库,基于C++11实现,设计目标是兼顾易用性与极致性能 。其核心特点包括:

  • 高性能: 核心设计目标,异步模式下可达数百万条/秒的日志吞吐量。
  • 极简 API: 提供类似 spdlog::info("Hello {}!", "world") 的直观接口。
  • 高度可扩展: 支持文件(带轮转)、控制台、系统日志、TCP 等多种输出目标 (Sinks)。
  • 异步模式: 内置高性能异步日志,避免 I/O 阻塞主线程。
  • 多线程安全: 所有组件均设计为线程安全。
  • 灵活的格式化: 基于出色的 fmt 库,支持丰富格式定制。
  • 轻量级: 纯头文件模式可选,易于集成。
  • 活跃社区: 持续维护更新,文档完善。

二、安装与配置:快速集成

安装方式
  1. 包管理器 (推荐):

    # vcpkg
    vcpkg install spdlog
    
    # Conan
    conan install spdlog/1.x.x
    
  2. 源码集成:

    • 克隆仓库:git clone https://github.com/gabime/spdlog.git
    • 复制 include/spdlog 到项目头文件目录
    • (可选) 编译并链接 spdlog 库 (SPDLOG_COMPILED_LIB)
CMake 集成 (推荐)
find_package(spdlog REQUIRED) # 使用包管理器时
# 或
add_subdirectory(external/spdlog) # 源码集成时

target_link_libraries(YourTarget PRIVATE spdlog::spdlog)
基本使用
#include <spdlog/spdlog.h>

int main() {
    
      
    // 设置全局日志级别 (debug, info, warn, error, critical)
    spdlog::set_level(spdlog::level::debug); 

    // 输出日志
    spdlog::debug("This is a debug message");
    spdlog::info("Welcome to spdlog! {}", "Easy formatting");
    spdlog::error("Something went wrong: {}", 42);
}

三、核心功能

1. 日志级别管理
spdlog::set_level(spdlog::level::warn); // 全局级别

auto logger = spdlog::get("my_logger");
logger->set_level(spdlog::level::debug); // 特定记录器级别

if (logger->should_log(spdlog::level::info)) {
    
       ... } // 条件检查
2. 记录器 (Loggers)
  • 创建记录器:
    // 基础控制台记录器
    auto console_logger = spdlog::stdout_color_mt("console"); 
    
    // 基础文件记录器 (覆盖)
    auto file_logger = spdlog::basic_logger_mt("file_logger", "logs/basic.txt");
    
    // 创建自定义 sink 组合的记录器
    std::vector<spdlog::sink_ptr> sinks;
    sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
    sinks.push_back(std::make_shared<spdlog::sinks::daily_file_sink_mt>("daily.log", 23, 59));
    auto combined_logger = std::make_shared<spdlog::logger>("combined", begin(sinks), end(sinks));
    spdlog::register_logger(combined_logger);
    
3. Sinks (输出目标)
  • 常用 Sinks:
    • stdout_color_sink_mt / stderr_color_sink_mt (带颜色控制台)
    • basic_file_sink_mt (基础文件)
    • rotating_file_sink_mt (按大小轮转文件)
    • daily_file_sink_mt (按时间轮转文件)
    • syslog_sink (Unix 系统日志)
    • msvc_sink_mt (Windows 调试输出)
    • tcp_sink_mt (网络输出)
  • Sink 配置:
    auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
        "rotating.log", 1024 * 1024 * 5, 3); // 最大 5MB,保留 3 个备份
    sink->set_level(spdlog::level::warn); // 设置该 sink 的过滤级别
    
4. 格式化 (Formatting)
  • 自定义格式:
    // 全局格式
    spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] %v"); 
    
    // 记录器特定格式
    console_logger->set_pattern("%^[%H:%M:%S] [%l]%$ %v"); // %^/%$ 控制颜色范围
    
    // 格式说明符示例:
    // %Y: 年, %m: 月, %d: 日, %H: 时, %M: 分, %S: 秒, %e: 毫秒
    // %n: 记录器名称, %l: 级别, %v: 实际消息, %t: 线程 ID
    
5. 异步日志 (关键性能特性)
// 创建异步记录器 (带队列大小和线程数)
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>(
    "async_file", "logs/async_log.txt"); 

// 全局设置异步线程池参数 (通常在程序开始)
spdlog::init_thread_pool(8192, 1); // 队列大小 8192,1 个工作线程
auto async_logger = std::make_shared<spdlog::async_logger>(
    "async_log", sink, spdlog::thread_pool(), spdlog::async_overflow_policy::block);
spdlog::register_logger(async_logger);
  • 溢出策略: block (阻塞生产者) 或 overrun_oldest (丢弃最旧消息)。
6. 多线程支持
  • 所有 _mt 后缀的 Sinks 和 Loggers 都是线程安全的。
  • 无需额外同步即可在多线程环境中使用。
7. 刷新策略
logger->flush_on(spdlog::level::err); // 遇到错误级别立即刷新
spdlog::flush_every(std::chrono::seconds(5)); // 定期刷新所有记录器
logger->flush(); // 手动强制刷新
8. 错误处理
// 注册全局错误处理器
spdlog::set_error_handler([](const std::string &msg) {
    
      
    std::cerr << "spdlog error: " << msg << std::endl;
});

// 特定 sink 的错误处理 (如文件打开失败)
auto sink = std::make_shared<my_file_sink>();
sink->set_error_handler([](const std::string &msg) {
    
       ... });

四、应用案例:实战演示

案例 1:基础控制台 + 文件日志
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>

int main() {
    
      
    try {
    
      
        auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
        console_sink->set_level(spdlog::level::info);

        auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log", true);
        file_sink->set_level(spdlog::level::debug);

        spdlog::logger logger("main", {
    
      console_sink, file_sink});
        logger.set_level(spdlog::level::debug);

        logger.info("Application started");
        logger.debug("Debug details: {}", 42);
        logger.warn("Low disk space!");

    } catch (const spdlog::spdlog_ex &ex) {
    
      
        std::cerr << "Log init failed: " << ex.what() << std::endl;
    }
    return 0;
}
案例 2:带轮转的异步日志系统
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>

int main() {
    
      
    spdlog::init_thread_pool(32768, 2); // 大队列,2个线程

    auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
        "logs/rotating.log", 1024 * 1024 * 50, 5); // 50MB, 5 files

    std::vector<spdlog::sink_ptr> sinks{
    
      stdout_sink, rotating_sink};
    auto async_logger = std::make_shared<spdlog::async_logger>(
        "async_system", sinks.begin(), sinks.end(),
        spdlog::thread_pool(), spdlog::async_overflow_policy::block);
    async_logger->set_level(spdlog::level::info);
    spdlog::register_logger(async_logger);

    // 使用异步记录器
    SPDLOG_LOGGER_INFO(async_logger, "Async logging is running!");
    async_logger->warn("This is a warning via async logger");

    spdlog::shutdown(); // 确保程序退出前刷新所有日志
    return 0;
}
案例 3:单例全局日志管理器 (常用模式)
// LoggerManager.h
#pragma once
#include <spdlog/spdlog.h>
#include <memory>

class LoggerManager {
    
      
public:
    static spdlog::logger& getLogger() {
    
      
        static auto logger = []() -> std::shared_ptr<spdlog::logger> {
    
      
            auto logger = spdlog::stdout_color_mt("system");
            logger->set_level(spdlog::level::debug);
            logger->set_pattern("[%Y-%m-%d %T.%e] [%^%l%$] [%n] %v");
            return logger;
        }();
        return *logger;
    }
};

// 便捷宏
#define LOG_TRACE(...)   SPDLOG_LOGGER_TRACE(LoggerManager::getLogger(), __VA_ARGS__)
#define LOG_INFO(...)    SPDLOG_LOGGER_INFO(LoggerManager::getLogger(), __VA_ARGS__)
#define LOG_ERROR(...)   SPDLOG_LOGGER_ERROR(LoggerManager::getLogger(), __VA_ARGS__)

// Usage in app.cpp
#include "LoggerManager.h"
int main() {
    
      
    LOG_INFO("Application initialized");
    LOG_ERROR("Critical error code: {}", 500);
}

五、进阶

1. 性能优化建议
  • 优先使用异步日志: 对性能敏感场景至关重要。
  • 避免同步日志 + 低级别刷盘: 如频繁的 flush()flush_on(low_level)
  • 合理设置队列大小: 防止异步模式因队列满而阻塞。
  • 谨慎使用全局锁: 默认线程安全有代价,极高并发场景可评估自定义无锁 sink。
2. 高级特性
  • 自定义 Sink: 继承 spdlog::sinks::base_sink 实现任何输出目标。
  • 自定义格式化器: 使用 fmt 库的强大功能定制字段。
  • 日志过滤: 通过 set_level() 或自定义 sink::should_log() 实现复杂过滤。
  • 单头文件模式: 定义 SPDLOG_HEADER_ONLY 且不链接库 (方便但可能增加编译时间)。
  • 二进制格式: 使用 spdlog::to_hex() 输出二进制数据。
3. 常见陷阱
  • 未调用 spdlog::shutdown() 程序退出时可能丢失缓冲区日志 (RAII 通常能处理)。
  • Sink 生命周期管理: 确保记录器使用的 sink 在其整个生命周期内有效。
  • 格式字符串错误: fmt 语法错误会导致运行时异常。
  • 过度日志: 即使异步日志,过量日志仍会消耗内存和 CPU。
4. 资源

六、总结

spdlog 将 C++ 日志记录提升到了新的高度。它完美平衡了 性能、易用性和功能性。无论是小型工具还是大型分布式系统,spdlog 都能提供可靠、高效的日志解决方案。其清晰的 API 设计、强大的异步能力、灵活的配置选项以及活跃的社区支持,使其成为现代 C++ 项目中日志组件的不二之选。

提示: 实际项目中,建议结合具体需求配置日志级别、轮转策略和输出目标,避免过度记录影响性能。善用异步模式,让应用飞起来!