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

C++日志输出库:spdlog

spdlog使用

  • 1. 简介
  • 2. 使用
    • 2.1 基础使用示例
  • 3. 核心功能详解
    • 3.1 日志格式化
    • 3.2 文件日志(含滚动日志)
    • 3.3 多日志器与日志分流
    • 3.4 异步日志(提升性能)
    • 3.5 动态调整日志级别
  • 4. 常用函数与变量
    • 4.1 日志记录器logger与输出目标sink函数
      • 日志记录器(Logger)相关函数
      • 输出目标(Sink)相关函数
      • 组合使用 Logger 和 Sink
    • 4.2 常用函数
      • init_thread_pool()
        • 使用步骤
        • 关键配置与注意事项
        • 示例
      • flush_every()
        • 使用方法
        • 适用场景
    • 4.3 参数
      • __VA_ARGS__
        • 核心语法
        • 典型使用场景
        • 总结
  • 5. 注意事项

1. 简介

spdlog 是一个高性能、单头文件(header-only) 的 C++ 日志库,支持多线程、多种日志输出目标(控制台、文件、滚动日志等),且易于集成和扩展。它是目前 C++ 项目中最流行的日志库之一,广泛用于桌面应用、服务器程序和嵌入式开发。

2. 使用

直接下载头文件: 从 spdlog 官网 下载 include/spdlog 目录,复制到项目中,直接包含:

#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"  // 彩色控制台输出

2.1 基础使用示例

简单示例

  • 控制台输出日志信息
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"  // 彩色控制台日志int main() {// 1. 创建彩色控制台日志器(单例模式,名称为 "console")auto console_logger = spdlog::stdout_color_mt("console");// 2. 设置全局日志级别(默认是 info,低于该级别的日志不输出)spdlog::set_level(spdlog::level::trace);  // 输出所有级别日志// 3. 输出不同级别的日志(支持 printf 风格和 C++ 流风格)spdlog::trace("这是 trace 日志(最详细,用于调试)");spdlog::debug("这是 debug 日志(开发调试),整数: {}", 123);  // C++ 流风格spdlog::info("这是 info 日志(常规信息),浮点数: {:.2f}", 3.14);spdlog::warn("这是 warn 日志(警告,非致命错误)");spdlog::error("这是 error 日志(错误,需处理),字符串: {}", "test");spdlog::critical("这是 critical 日志(严重错误,程序可能崩溃)");// 4. 使用指定的日志器(而非全局日志器)console_logger->info("使用 console 日志器输出");// 5. 关闭所有日志器(释放资源,可选)spdlog::shutdown();return 0;
}

输出效果(控制台彩色显示,不同级别日志颜色不同,如 error 为红色,warn 为黄色):

[2024-05-20 15:30:00.123] [trace] [main.cpp:12] 这是 trace 日志(最详细,用于调试)
[2024-05-20 15:30:00.123] [debug] [main.cpp:13] 这是 debug 日志(开发调试),整数: 123
[2024-05-20 15:30:00.123] [info] [main.cpp:14] 这是 info 日志(常规信息),浮点数: 3.14
[2024-05-20 15:30:00.123] [warn] [main.cpp:15] 这是 warn 日志(警告,非致命错误)
[2024-05-20 15:30:00.123] [error] [main.cpp:16] 这是 error 日志(错误,需处理),字符串: test
[2024-05-20 15:30:00.123] [critical] [main.cpp:17] 这是 critical 日志(严重错误,程序可能崩溃)
[2024-05-20 15:30:00.123] [info] [main.cpp:19] 使用 console 日志器输出

3. 核心功能详解

3.1 日志格式化

spdlog 支持自定义日志格式,默认格式包含 时间戳、日志级别、文件名、行号、日志内容。可通过 set_pattern 调整格式:

// 自定义格式:[时间] [级别] [文件:行号] 内容
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%l] [%s:%#] %v");
// %Y-%m-%d:日期,%H:%M:%S:时间,%l:日志级别(小写),%s:文件名,%#:行号,%v:日志内容

常用格式占位符:

占位符含义示例
%Y-%m-%d日期(年-月-日)2024-05-20
%H:%M:%S.%f时间(时:分:秒.毫秒)15:30:00.123
%l日志级别(小写)trace/debug/info
%L日志级别(大写)TRACE/DEBUG/INFO
%s文件名main.cpp
%#行号12
%v日志内容这是 info 日志
%t线程 ID1234

3.2 文件日志(含滚动日志)

除控制台外,spdlog 支持将日志写入文件,且支持滚动日志(按文件大小或时间切割,避免单个文件过大)。

示例1:普通文件日志

#include "spdlog/sinks/basic_file_sink.h"// 创建文件日志器(日志写入 "app.log",若文件存在则追加)
auto file_logger = spdlog::basic_logger_mt("file_logger", "app.log");
file_logger->info("这是写入文件的日志");

示例2:按大小滚动的日志

#include "spdlog/sinks/rotating_file_sink.h"// 配置:单个文件最大 10MB,最多保留 5 个备份文件(app.log.1, app.log.2, ..., app.log.5)
auto rotating_logger = spdlog::rotating_logger_mt("rotating_logger",    // 日志器名称"app_rotating.log",   // 基础文件名10 * 1024 * 1024,     // 单个文件最大大小(10MB)5                     // 备份文件数量
);
rotating_logger->warn("这是滚动日志的警告信息");

示例3:按时间滚动的日志

#include "spdlog/sinks/daily_file_sink.h"// 配置:每天 00:00 切割日志,最多保留 7 天的日志
auto daily_logger = spdlog::daily_logger_mt("daily_logger",       // 日志器名称"app_daily.log",      // 基础文件名(切割后为 app_daily.log.2024-05-20)0,                    // 切割小时(0 = 凌晨)0                     // 切割分钟(0 = 整点)
);
daily_logger->error("这是按时间滚动的错误日志");

3.3 多日志器与日志分流

spdlog 支持创建多个独立的日志器,分别输出到不同目标(如控制台+文件、不同文件)。

示例:同时输出到控制台和文件

#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"// 1. 创建控制台 sink 和文件 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>("app.log");// 2. 创建多 sink 日志器(同时输出到两个 sink)
std::vector<spdlog::sink_ptr> sinks = {console_sink, file_sink};
auto multi_sink_logger = std::make_shared<spdlog::logger>("multi_logger", sinks.begin(), sinks.end());// 3. 设置日志器为全局日志器(可选)
spdlog::set_default_logger(multi_sink_logger);// 4. 输出日志(同时在控制台和文件中显示)
spdlog::info("同时输出到控制台和文件的日志");

3.4 异步日志(提升性能)

默认情况下,spdlog 使用同步日志(日志输出阻塞主线程)。对于高性能场景(如高并发服务器),可启用异步日志(日志写入后台线程,不阻塞主线程)。

#include "spdlog/async.h"
#include "spdlog/sinks/stdout_color_sinks.h"int main() {// 1. 初始化异步日志(必须在创建日志器前调用)// 配置:队列大小 8192,后台线程 1 个,刷新间隔 3 秒spdlog::init_thread_pool(8192, 1);// 2. 创建异步控制台日志器(_mt 表示多线程安全,_st 表示单线程)auto async_console = spdlog::basic_logger_mt<spdlog::async_factory>("async_console", "async.log");// 3. 输出日志(主线程不阻塞,日志由后台线程写入)async_console->info("这是异步日志,不阻塞主线程");// 4. 关闭异步日志(确保后台线程完成写入,可选)spdlog::shutdown();return 0;
}

3.5 动态调整日志级别

可在运行时动态修改日志级别,方便在生产环境临时开启调试日志(无需重启程序)。

// 初始级别为 info(仅输出 info 及以上)
spdlog::set_level(spdlog::level::info);
spdlog::debug("这行日志不会输出(级别低于 info)");// 动态调整为 debug(输出 debug 及以上)
spdlog::set_level(spdlog::level::debug);
spdlog::debug("这行日志会输出(级别已调整为 debug)");// 按日志器单独调整级别(不影响全局)
auto file_logger = spdlog::basic_logger_mt("file_logger", "app.log");
file_logger->set_level(spdlog::level::warn);  // 仅输出 warn 及以上
file_logger->info("这行日志不会写入文件(级别低于 warn)");
file_logger->warn("这行日志会写入文件(级别为 warn)");

4. 常用函数与变量

4.1 日志记录器logger与输出目标sink函数

spdlog 库中,日志记录器(Logger)和输出目标(Sink)是两个核心概念,分别对应不同的功能函数。以下是常用的相关函数分类列举:

日志记录器(Logger)相关函数

日志记录器是日志的入口,负责接收日志消息并转发到关联的 Sink。

    1. 创建日志记录器
    • 函数后缀 _mt 表示多线程安全(multi-thread),_st 表示单线程(single-thread,如 stdout_logger_st)。
    • 自定义日志器需手动传入 Sink,灵活度更高。
函数原型功能描述
std::shared_ptr<logger> spdlog::stdout_logger_mt(const std::string& name)创建多线程安全的控制台(stdout)日志器
std::shared_ptr<logger> spdlog::stderr_logger_mt(const std::string& name)创建多线程安全的错误控制台(stderr)日志器
std::shared_ptr<logger> spdlog::stdout_color_logger_mt(const std::string& name)创建多线程安全的彩色控制台日志器
std::shared_ptr<logger> spdlog::basic_logger_mt(const std::string& name, const filename_t& filename)创建多线程安全的基本文件日志器(日志追加到文件)
std::shared_ptr<logger> spdlog::rotating_logger_mt(const std::string& name, const filename_t& filename, size_t max_size, size_t max_files)创建多线程安全的滚动文件日志器(按大小切割)
std::shared_ptr<logger> spdlog::daily_logger_mt(const std::string& name, const filename_t& filename, int hour = 0, int minute = 0)创建多线程安全的每日滚动日志器(按时间切割)
std::shared_ptr<logger> spdlog::logger(const std::string& name, sink_ptr single_sink)自定义日志器(关联单个 Sink)
std::shared_ptr<logger> spdlog::logger(const std::string& name, sinks_init_list sinks)自定义日志器(关联多个 Sink)
    1. 日志记录器管理
函数原型功能描述
std::shared_ptr<logger> spdlog::get(const std::string& name)根据名称获取已创建的日志器
void spdlog::set_default_logger(std::shared_ptr<logger> logger)设置全局默认日志器(用于 spdlog::info() 等全局函数)
void spdlog::drop(const std::string& name)销毁指定名称的日志器
void spdlog::drop_all()销毁所有日志器
void spdlog::shutdown()销毁所有日志器并释放资源(建议程序退出前调用)
    1. 日志记录器配置
函数原型(logger 成员函数)功能描述
void set_level(level::level_enum log_level)设置日志级别(如 level::debug
void set_pattern(const std::string& pattern, pattern_time_type time_type = pattern_time_type::local)设置日志格式(如 "%Y-%m-%d %H:%M:%S [%l] %v"
void set_formatter(std::unique_ptr<formatter> formatter)设置自定义日志格式化器
void flush_on(level::level_enum log_level)当日志级别达到指定值时自动刷新(如 flush_on(level::err)
void flush()手动刷新日志(立即写入输出目标)

输出目标(Sink)相关函数

Sink 是日志的输出目标(如控制台、文件),日志记录器需关联一个或多个 Sink 才能输出日志。

    1. 常用 Sink 创建
    • Sink 同样有 _mt(多线程)和 _st(单线程)版本,需与日志器线程安全类型匹配。
函数/类功能描述
std::make_shared<sinks::stdout_sink_mt>()多线程安全的标准输出(stdout)Sink
std::make_shared<sinks::stderr_sink_mt>()多线程安全的标准错误(stderr)Sink
std::make_shared<sinks::stdout_color_sink_mt>()多线程安全的彩色控制台 Sink
std::make_shared<sinks::basic_file_sink_mt>(const filename_t& filename, bool truncate = false)多线程安全的基本文件 Sink(truncate=true 表示覆盖文件)
std::make_shared<sinks::rotating_file_sink_mt>(const filename_t& base_filename, size_t max_size, size_t max_files, bool truncate = false)多线程安全的滚动文件 Sink(按大小切割)
std::make_shared<sinks::daily_file_sink_mt>(const filename_t& base_filename, int hour, int minute, bool truncate = false)多线程安全的每日滚动文件 Sink(按时间切割)
std::make_shared<sinks::syslog_sink_mt>(const std::string& ident, int option = 0, int facility = LOG_USER)多线程安全的系统日志 Sink(如 Linux syslog)
    1. Sink 配置(成员函数)
函数原型功能描述
void set_level(level::level_enum log_level)设置当前 Sink 的日志级别(仅处理该级别及以上的日志)
void set_formatter(std::unique_ptr<formatter> formatter)为当前 Sink 设置独立的日志格式(覆盖日志器的全局格式)
void flush()手动刷新该 Sink 的日志缓存

组合使用 Logger 和 Sink

#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sink.h"
#include "spdlog/sinks/basic_file_sink.h"int main() {// 1. 创建两个 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>("app.log");// 2. 为 Sink 单独设置日志级别(可选)console_sink->set_level(spdlog::level::info);  // 控制台只输出 info 及以上file_sink->set_level(spdlog::level::debug);    // 文件输出 debug 及以上// 3. 创建日志器并关联两个 Sinkspdlog::sinks_init_list sinks = {console_sink, file_sink};auto logger = std::make_shared<spdlog::logger>("multi_sink_logger", sinks);// 4. 配置日志器logger->set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");  // 全局格式logger->set_level(spdlog::level::debug);  // 日志器最低级别// 5. 输出日志(同时到控制台和文件)logger->debug("debug 日志(仅文件输出)");logger->info("info 日志(控制台和文件均输出)");return 0;
}

总结

  • Logger 函数:负责创建、管理日志记录器,以及配置日志级别、格式等全局属性。
  • Sink 函数:负责创建具体的输出目标(控制台/文件等),并支持独立配置级别和格式。
  • 灵活组合 Logger 和 Sink 可实现复杂的日志需求(如多目标输出、分级过滤)。

4.2 常用函数

init_thread_pool()

在 spdlog 日志库中,init_thread_pool() 是用于初始化异步日志线程池的函数,主要用于支持异步日志模式(将日志写入操作放入后台线程执行,避免阻塞主线程)。以下是其详细说明和使用方法:
异步日志原理:普通同步日志会在调用日志函数(如 info()error())时直接写入输出目标(控制台/文件),可能阻塞主线程;异步日志则将日志消息先存入内存队列,由后台线程异步处理写入,主线程可立即返回。
init_thread_pool():负责创建后台线程和消息队列,为所有异步日志器提供底层支持。必须在创建第一个异步日志器前调用。

void spdlog::init_thread_pool(size_t queue_size, size_t thread_count = 1);
  • queue_size:日志消息队列的最大容量(单位:条)。若队列满,新日志会根据策略处理(默认阻塞或丢弃,取决于配置)。
  • thread_count:后台线程数量(默认 1,通常无需修改,多线程可能引发锁竞争)。
使用步骤

1. 初始化线程池
在程序启动时(创建任何异步日志器前)调用 init_thread_pool()

#include "spdlog/async.h"       // 异步日志核心头文件
#include "spdlog/sinks/stdout_color_sinks.h"int main() {// 初始化异步线程池:队列容量 8192 条,1 个后台线程spdlog::init_thread_pool(8192, 1);// 后续创建异步日志器...return 0;
}

2. 创建异步日志器
使用 spdlog::async_factoryspdlog::async_factory_st 创建异步日志器(_mt 表示多线程安全,_st 表示单线程):

// 创建异步控制台日志器(多线程安全)
auto async_console = spdlog::stdout_color_logger_mt<spdlog::async_factory>("async_console");// 创建异步滚动文件日志器
auto async_file = spdlog::rotating_logger_mt<spdlog::async_factory>("async_file",       // 日志器名称"logs/async.log",   // 日志文件路径1024 * 1024 * 5,    // 单个文件大小(5MB)3                   // 备份文件数量
);

3. 使用异步日志器
调用日志函数的方式与同步日志器一致,但内部会通过线程池异步处理:

async_console->info("这是一条异步日志(不会阻塞主线程)");
async_file->error("错误代码: {}", 404);

4. 程序退出时清理
调用 spdlog::shutdown() 确保后台线程完成所有日志写入并释放资源:

// 程序退出前
spdlog::shutdown();
关键配置与注意事项

1. 队列大小选择

  • 过小可能导致队列满(日志丢失或阻塞),过大会浪费内存。
  • 建议值:中小型程序 4096~8192,高并发程序 16384~65536

2. 线程数量

  • 通常设为 1 即可:多线程写入同一目标(如文件)会引入锁竞争,反而降低性能。
  • 特殊场景(如多个独立日志文件)可适当增加,但需测试性能影响。

3. 日志满队列策略
默认情况下,队列满时会阻塞主线程直到有空闲位置。可通过 set_async_mode() 自定义策略:

// 初始化线程池时指定满队列策略(非阻塞,丢弃新日志)
spdlog::init_thread_pool(8192, 1);
spdlog::set_async_mode(spdlog::async_overflow_policy::overrun_oldest);
// overrun_oldest:丢弃最旧的日志;block:阻塞(默认)

4. 与同步日志器的共存

  • 异步线程池初始化后,仍可创建同步日志器(不影响)。
  • 同步日志器的写入操作会直接执行,不受线程池控制。

5. 线程安全

  • 异步日志器默认线程安全(_mt 后缀),可在多线程中放心使用。
  • 单线程场景可使用 _st 后缀的日志器(性能略高,但不线程安全)。
示例
#include "spdlog/async.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include <thread>
#include <chrono>void async_log_demo() {// 初始化异步线程池spdlog::init_thread_pool(8192, 1);// 设置队列满时策略:覆盖最旧日志spdlog::set_async_mode(spdlog::async_overflow_policy::overrun_oldest);// 创建异步控制台日志器auto console = spdlog::stdout_color_logger_mt<spdlog::async_factory>("console");// 创建异步文件日志器auto file_logger = spdlog::rotating_logger_mt<spdlog::async_factory>("file_logger", "logs/async.log", 1024*1024*5, 3);// 多线程输出日志(模拟高并发)auto log_task = [&](int thread_id) {for (int i = 0; i < 100; ++i) {console->info("线程 {}: 第 {} 条日志", thread_id, i);file_logger->debug("线程 {}: 调试信息 {}", thread_id, i);}};std::thread t1(log_task, 1);std::thread t2(log_task, 2);t1.join();t2.join();// 关闭日志器,确保所有日志写入完成spdlog::shutdown();
}int main() {async_log_demo();return 0;
}

flush_every()

spdlog::flush_every() 是 spdlog 库中用于配置定时自动刷新日志的函数,主要用于异步日志模式,确保日志消息在指定时间间隔内被强制写入输出目标(如文件),避免因程序崩溃导致日志丢失。
在异步日志模式中,日志消息先存入内存队列,由后台线程异步处理。默认情况下,后台线程会在队列有消息时尽快处理,但极端情况下(如日志量极少),消息可能长时间停留在内存中。
flush_every() 用于设置最大间隔时间,无论队列是否有消息,后台线程都会每隔指定时间自动刷新一次日志缓存,将所有未写入的消息强制输出到目标(如文件)。

void spdlog::flush_every(std::chrono::duration<int64_t, std::nano> interval);
  • interval:自动刷新的时间间隔,需使用 C++ 标准库的 std::chrono 时间单位(如 std::chrono::seconds(3) 表示 3 秒)。
使用方法

需在初始化异步线程池后创建异步日志器前调用:

#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
#include <chrono>  // 用于时间单位int main() {// 1. 初始化异步线程池spdlog::init_thread_pool(8192, 1);// 2. 设置定时刷新:每 5 秒自动刷新一次spdlog::flush_every(std::chrono::seconds(5));// 3. 创建异步日志器(写入文件)auto async_file_logger = spdlog::basic_logger_mt<spdlog::async_factory>("async_file", "logs/auto_flush.log");// 4. 输出日志(即使日志量少,也会每 5 秒自动写入文件)async_file_logger->info("这条日志会在 5 秒内被刷新到文件");// 模拟程序运行(超过 5 秒,确保刷新生效)std::this_thread::sleep_for(std::chrono::seconds(10));// 5. 程序退出前关闭日志器spdlog::shutdown();return 0;
}
  1. 仅适用于异步日志flush_every() 是异步日志线程池的配置,对同步日志器无效。
  2. 补充刷新机制:与 logger->flush_on(level)(按日志级别刷新)不冲突,二者可同时生效:
    • flush_on(level):当日志级别达到指定值(如 error)时立即刷新。
    • flush_every(interval):无论级别,定时强制刷新。
  3. 全局生效:一旦设置,对所有后续创建的异步日志器均有效。
适用场景
  • 低频率日志场景:如程序长时间运行但日志量极少(如每隔几分钟一条),避免日志长时间滞留内存。
  • 高可靠性需求:如金融交易、系统监控,确保日志及时写入磁盘,即使程序意外崩溃也能保留大部分日志。
  • 文件日志优化:减少磁盘 I/O 次数的同时,平衡日志实时性(避免频繁刷新影响性能,也避免太久不刷新导致丢失)。

注意事项

  1. 时间间隔选择

    • 过短(如 100ms)会增加磁盘 I/O 频率,影响性能。
    • 过长(如 1 小时)可能导致崩溃时丢失大量日志。
    • 建议值:普通场景 3~30 秒,关键场景 1~5 秒。
  2. 与手动刷新配合:仍可通过 logger->flush() 手动强制刷新,不受定时刷新影响。

  3. 线程池依赖:必须在 init_thread_pool() 之后调用,否则配置不生效。

spdlog::flush_every() 是异步日志模式下保障日志可靠性的重要函数,通过定时自动刷新机制,平衡了性能与日志完整性。在对日志实时性有要求的场景(如长期运行的服务程序),建议结合业务需求设置合理的刷新间隔。

4.3 参数

VA_ARGS

__VA_ARGS__ 是 C/C++ 中的一个预处理器宏(Preprocessor Macro),用于在可变参数宏(Variadic Macros) 中表示“所有传入的可变参数”。它允许定义能够接收不确定数量参数的宏,是实现灵活日志打印、函数包装等功能的常用工具。
可变参数宏:指定义时参数数量不固定的宏,语法上通过 ...(省略号)声明可变参数部分。
__VA_ARGS__:在宏的展开体中,__VA_ARGS__ 会被直接替换为调用宏时传入的所有可变参数
适用场景:日志打印(如 LOG("错误码: %d, 信息: %s", 404, "文件不存在"))、函数调用包装、批量代码生成等。

核心语法
  1. 基础可变参数宏定义
// 定义格式:宏名(固定参数, ...) -> 展开体中用 __VA_ARGS__ 代替可变参数
#define 宏名(固定参数, ...) 展开逻辑中使用 __VA_ARGS__
  1. 无固定参数的可变参数宏
    若宏没有固定参数,直接用 ... 声明,且 __VA_ARGS__ 需紧跟在 ## 后(避免空参数时的语法错误,C99 及以上支持):
// 正确:无固定参数的可变参数宏
#define LOG(...) printf(__VA_ARGS__)
典型使用场景
  • 场景1:简单日志打印(最常用)
    通过 __VA_ARGS__ 实现支持任意格式的日志输出,无需重复写 printf 的格式控制逻辑:
#include <stdio.h>// 定义日志宏:打印时间(固定前缀)+ 可变参数(日志内容)
#define LOG_INFO(...) \printf("[INFO] %s:%d: ", __FILE__, __LINE__);  // 固定前缀(文件名、行号)\printf(__VA_ARGS__);                           // 可变参数(日志内容)\printf("\n")                                    // 换行int main() {int user_id = 123;char* username = "Alice";// 调用宏:传入 2 个可变参数(格式字符串 + 具体值)LOG_INFO("用户登录: id=%d, name=%s", user_id, username);// 调用宏:传入 1 个可变参数(仅字符串)LOG_INFO("程序启动成功");return 0;
}

展开结果(假设代码在 main.c 的第 15、18 行):

// 第15行调用展开为:
printf("[INFO] main.c:15: "); printf("用户登录: id=%d, name=%s", 123, "Alice"); printf("\n");// 第18行调用展开为:
printf("[INFO] main.c:18: "); printf("程序启动成功"); printf("\n");

运行输出

[INFO] main.c:15: 用户登录: id=123, name=Alice
[INFO] main.c:18: 程序启动成功
  • 场景2:函数包装(简化复杂调用)
    用宏包装带多个参数的函数,减少重复代码。例如包装 printf 并添加错误检查:
#include <stdio.h>
#include <errno.h>// 包装 printf,打印错误信息(若返回值 <0,提示错误码)
#define SAFE_PRINTF(...) \do { \int ret = printf(__VA_ARGS__);  // 用 __VA_ARGS__ 传递所有参数给 printf \if (ret < 0) { \fprintf(stderr, "打印失败!错误码: %d\n", errno); \} \} while(0)  // do-while(0) 确保宏在条件语句中正常展开int main() {SAFE_PRINTF("Hello, %s!\n", "World");  // 正常调用SAFE_PRINTF("数值: %d, 浮点数: %.2f\n", 100, 3.14);  // 多参数调用return 0;
}
  • 场景3:支持空参数(##__VA_ARGS__
    若宏可能被无参数调用(如 LOG("仅提示")),直接用 __VA_ARGS__ 可能导致语法错误(如多余的逗号)。此时需用 ##(宏连接符)消除空参数的影响:
// 错误示例:无参数调用时会展开为 printf("日志: " ); (多余逗号)
#define LOG_BAD(msg, ...) printf("日志: " msg, __VA_ARGS__)// 正确示例:用 ##__VA_ARGS__ 消除空参数的逗号
#define LOG_GOOD(msg, ...) printf("日志: " msg, ##__VA_ARGS__)int main() {// LOG_BAD("测试");  // 错误:展开为 printf("日志: " "测试", ); (语法错误)LOG_GOOD("测试");     // 正确:展开为 printf("日志: " "测试");LOG_GOOD("用户: %s", "Bob");  // 正确:展开为 printf("日志: " "用户: %s", "Bob");return 0;
}
  • ## 的作用:当 __VA_ARGS__ 为空时,## 会自动删除前面的逗号,避免语法错误。
  • 兼容性##__VA_ARGS__ 是 GNU 扩展(GCC、Clang 支持),C99 标准不直接支持,但主流编译器(包括 VS2019+)均兼容。
总结

__VA_ARGS__ 是 C/C++ 预处理器的核心特性,通过可变参数宏实现“一次定义,多参数调用”,尤其适合日志打印、函数包装等场景。使用时需注意:

  1. ##__VA_ARGS__ 处理空参数,避免语法错误;
  2. 结合预定义宏(__FILE____LINE__)增强调试信息;
  3. C++ 中优先选择可变参数模板,确保类型安全。

5. 注意事项

  1. 线程安全后缀

    • 日志器创建函数的后缀 _mt(multi-thread)表示多线程安全,_st(single-thread)表示单线程。多线程环境必须使用 _mt 后缀,否则可能导致数据竞争。
    • 示例:stdout_color_mt()(多线程安全控制台日志器)、basic_file_sink_st()(单线程文件日志器)。
  2. 日志器生命周期

    • 避免在日志器销毁后继续使用(如全局日志器被 shutdown 后)。
    • 建议在程序退出前调用 spdlog::shutdown(),确保所有日志(尤其是异步日志)被写入目标。
  3. 字符编码

    • Windows 下默认使用 ANSI 编码,若需输出 Unicode 字符(如中文),需使用宽字符版本的 sink(如 spdlog::sinks::stdout_color_sink_wmt),并配合 L"中文日志" 格式:
      auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_wmt>();
      auto logger = std::make_shared<spdlog::logger>("unicode_logger", console_sink);
      logger->info(L"这是中文日志(宽字符)");
      
  4. 性能优化

    • 异步日志适合高并发场景,但会增加少量内存开销(队列和后台线程)。
    • 避免在日志中输出大量数据(如大字符串、二进制数据),以免影响性能。

文章转载自:

http://JZwNDnm8.gfqjf.cn
http://po2jWVWu.gfqjf.cn
http://qXdBIW6g.gfqjf.cn
http://hQ8bVP1x.gfqjf.cn
http://JMnp89OT.gfqjf.cn
http://HYLRgQGa.gfqjf.cn
http://nII5afch.gfqjf.cn
http://YMmarIj3.gfqjf.cn
http://oYp96rhm.gfqjf.cn
http://99s8iR6e.gfqjf.cn
http://fLVz3pCr.gfqjf.cn
http://8LTCZK8W.gfqjf.cn
http://IVWWvXET.gfqjf.cn
http://2zPKzdfV.gfqjf.cn
http://uq4hV9Nl.gfqjf.cn
http://iiyCEF8p.gfqjf.cn
http://M8xH9Dg3.gfqjf.cn
http://Z1JqhwQo.gfqjf.cn
http://4fL2mbhf.gfqjf.cn
http://gXYILYDZ.gfqjf.cn
http://CQ415lI7.gfqjf.cn
http://69xcBdlg.gfqjf.cn
http://2OXxObw5.gfqjf.cn
http://izxWZkJj.gfqjf.cn
http://fztIdyVf.gfqjf.cn
http://tT5NAwZR.gfqjf.cn
http://gTA5r4Yu.gfqjf.cn
http://lZZHtqet.gfqjf.cn
http://XnDUF1Sz.gfqjf.cn
http://3iOmNWzz.gfqjf.cn
http://www.dtcms.com/a/379834.html

相关文章:

  • 企业数字化转型案例:Heinzel集团SAP S/4HANA系统升级完成
  • 企业能源管理供电供水数据采集监测管理解决方案
  • React 进阶
  • ES相关问题汇总
  • 为什么Cesium不使用vue或者react,而是 保留 Knockout
  • Mysql杂志(十五)——公用表达式CTE
  • Javascript忘记了,好像又想起来了一点?
  • AI + 制造:NebulaAI 场景实践来了!
  • mosdns缓存dns服务器配置记录
  • android14 硬键盘ESC改BACK按键返回无效问题
  • 代码随想录算法训练营第62天 | Floyd 算法精讲、A * 算法精讲 (A star算法)、最短路算法总结篇、图论总结
  • 教程:用免费 Google Translate API 在 VSCode 中实现中文注释自动翻译英文
  • 数据储存方式
  • Java生态圈核心组件深度解析:Spring技术栈与分布式系统实战
  • 解决Ubuntu中apt-get -y安装时弹出交互提示的问题
  • 硅基计划3.0 Map类Set类
  • Ubuntu20.04手动安装中文输入法
  • 算法训练营DAY60 第十一章:图论part11
  • java 反射Class类/加载类/创建对象及方法
  • RL【9】:Policy Gradient
  • Java短链接生成服务实战指南
  • JAVA Web —— A / 网页开发基础
  • TensorFlow深度学习实战:从零开始构建你的第一个神经网络
  • Keepalived 负载均衡
  • 智能文档处理业务,应该选择大模型还是OCR专用小模型?
  • 《Redis核心机制解析》
  • Netty 在 API 网关中的应用篇(请求转发、限流、路由、负载均衡)
  • 金蝶云星空插件开发记录(一)
  • Knockout-ES5 入门教程
  • 基于 Art_DAQ、InfluxDB 和 PyQt 的传感器数据采集、存储与可视化