gflags 与 spdlog 实战:C++ 命令行参数与高性能日志搭配
介绍 gflags 命令行参数解析库与 spdlog 高性能日志库在 C++ 项目中的集成应用。涵盖 gflags 的参数定义、解析方式(命令行、配置文件)及 spdlog 的同步/异步模式配置。通过二次封装示例展示了如何结合两者实现灵活的运行模式切换与日志管理,并提供了 grep 文本查询技巧辅助开发。

介绍 gflags 命令行参数解析库与 spdlog 高性能日志库在 C++ 项目中的集成应用。涵盖 gflags 的参数定义、解析方式(命令行、配置文件)及 spdlog 的同步/异步模式配置。通过二次封装示例展示了如何结合两者实现灵活的运行模式切换与日志管理,并提供了 grep 文本查询技巧辅助开发。

Google 开源的命令行参数解析库,用于高效管理程序启动参数(如 --flag=value)。
核心功能:
DEFINE_int32、DEFINE_string)声明参数名、默认值和帮助信息。ParseCommandLineFlags 解析 argc/argv,将命令行参数映射到全局变量(如 FLAGS_<name>)。FLAGS_<flag_name> 直接获取解析后的值。特点:
--help 自动输出参数说明(依赖定义时的描述信息)。对比其他工具:
类似功能库:Boost.Program_options(C++)、Python 的 argparse;
gflags 优势:与 Google 生态兼容,API 简洁,适合高性能场景。
用源码装 gflags 库步骤:
git clone https://github.com/gflags/gflags.gitcd gflags/mkdir build; cd build/cmake ..makemake install/usr/lib/x86_64-linux-gnu 目录下。gflags 支持的常见宏类型:
DEFINE_bool DEFINE_int32 DEFINE_int64 DEFINE_uint64 DEFINE_double DEFINE_string
简单使用格式:
#include <gflags/gflags.h>
#include <iostream>
DEFINE_string(ip, "127.0.0.1", "服务端 ip");
DEFINE_int32(port, 8080, "服务端监听端口");
DEFINE_bool(debug_enable, true, "是否启用调试模式,格式:true/false");
int main(int argc, char* argv[]) {
google::ParseCommandLineFlags(&argc, &argv, true);
// 解析完 flag 后,将 flag 从 argv 中移除,相应减少 argc 的值
std::cout << FLAGS_ip << std::endl;
std::cout << FLAGS_port << std::endl;
std::cout << FLAGS_debug_enable << std::endl;
return 0;
}
google::ParseCommandLineFlags 介绍:google::ParseCommandLineFlags 是 Google 工具库里的函数,用来处理程序启动时输入的命令行参数。
它接收 main 函数里的 argc(参数个数)和 argv(参数数组),还有个 remove_flags 参数控制后续行为:
remove_flags 为 true,解析后会从 argv 里删除已识别的'标志和参数',同时 argc 也会变小;false,argc 不变,但会把所有'标志'移到 argv 最前面重新排好。一句话:帮你自动解析命令行里的各种选项/开关,让程序能读懂用户输的参数。
过程大致概括:
这里就直接拿宏函数中默认的值进行输入到 main 的参数中解析(这里会把 -- 将会终止标识的处理)。
在命令行启动程序的时候按照对应格式输入。
这里直接让它从配置文件中读取对应参数即可。
gflags 提供了一些特殊参数标识:
比如这里可以使用对应 - -help 选项查看对应的 gflags 设置的对应输入参数如何使用。
spdlog 是一个高性能、超快速、零配置的 C++ 日志库,旨在提供简洁 API 和丰富功能,同时保持高性能的日志记录,支持多种输出目标、格式化选项、线程安全及异步日志记录。(也就是类似之前实现的日志系统项目,同时对比 glog 是功能更加多的,比如前者支持异步模式但后者不支持等等)
特点介绍:
对应命令进行安装即可(保证 apt 或者 yum 源是最新的):
sudo apt-get install libspdlog-dev
可以看到对应安装好后的头文件集合:
/usr/lib/x86_64-linux-gnu 目录下。下面认识下常见的基础的使用方法:
包含的头文件:
#include <spdlog/spdlog.h> // 必须包含的
#include <spdlog/sinks/basic_file_sink.h> // 文件输出模式
#include <spdlog/sinks/stdout_color_sinks.h>// 控制台输出模式
下面看个同步模式的例子:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <iostream>
#include <chrono>
int main() {
using namespace std::literals;
// 设置刷新时间间隔
spdlog::flush_every(1s);
// 刷新策略等级
spdlog::flush_on(spdlog::level::trace);
// 设置日志输出等级(全局,但后面可以根据日志器输出的时候进行修改)
spdlog::set_level(spdlog::level::debug);
// 启动对应日志器:
auto logger1 = spdlog::stdout_color_mt("test_log1");
// 多线程,模版默认同步打印
auto logger2 = spdlog::basic_logger_mt("test_log2", "sync.log", true);
// 多线程,模版默认同步打印,覆盖式追加文件(truncate)
// 自定义输出格式:
logger1->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");
logger2->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");
// {} 用来占位
// stdout:
logger1->trace("halo {}", 1111);
logger1->debug("halo {}", 1111);
logger1->info("halo {}", 1111);
logger1->warn("halo {}", 1111);
logger1->error("halo {}", 1111);
logger1->critical("halo {}", 1111);
// 文件
logger2->trace("halo {}", 1111);
logger2->debug("halo {}", 1111);
logger2->info("halo {}", 1111);
logger2->warn("halo {}", 1111);
logger2->error("halo {}", 1111);
logger2->critical("halo {}", 1111);
std::cout << "完成日志输出" << std::endl;
return 0;
}
对应的异步模式仅仅是修改对应的创建日志器的模版函数的模版即可(默认模版是同步日志器):
如下:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/async.h>
#include <iostream>
#include <chrono>
int main() {
using namespace std::literals;
// 设置刷新时间间隔
spdlog::flush_every(1s);
// 刷新策略等级
spdlog::flush_on(spdlog::level::trace);
// 设置日志输出等级(全局,但后面可以根据日志器输出的时候进行修改)
spdlog::set_level(spdlog::level::debug);
// 启动对应日志器:
auto logger1 = spdlog::stdout_color_mt<spdlog::async_factory>("test_log1");
// 多线程
auto logger2 = spdlog::basic_logger_mt<spdlog::async_factory>("test_log2", "async.log", true);
// 多线程
// 自定义输出格式:
logger1->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");
logger2->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");
// {} 用来占位
// stdout:
logger1->trace("halo {}", 1111);
logger1->debug("halo {}", 1111);
logger1->info("halo {}", 1111);
logger1->warn("halo {}", 1111);
logger1->error("halo {}", 1111);
logger1->critical("halo {}", 1111);
// 文件
logger2->trace("halo {}", 1111);
logger2->debug("halo {}", 1111);
logger2->info("halo {}", 1111);
logger2->warn("halo {}", 1111);
logger2->error("halo {}", 1111);
logger2->critical("halo {}", 1111);
std::cout << "完成日志输出" << std::endl;
return 0;
}
注:
原因:
封装思想:
封装全局接口供用户创建与初始化日志器,用户只需要对对应函数根据输入参数的不同模式完成调用即可,包含初始化接口接收运行模式、输出文件名、输出日志等级等参数,用宏封装日志输出接口,加入文件名行号输出。
代码实现:
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/async.h>
#include <iostream>
#include <chrono>
#include <string>
// 使用的时候一定要在开头调用 init_logger 这个函数完成日志器初始化,否则段错误!!!!!!!!!
std::shared_ptr<spdlog::logger> logger;
// mode - 运行模式:true-发布模式(等级自定义);false 调试模式 (默认全都是最低等级如刷新策略等级,日志等级)
void init_logger(bool mode, const std::string &filename, int32_t level) {
if (mode == 1) {
logger = spdlog::basic_logger_mt("default_logger", filename.c_str());
// 下面也可以全局设置,但是每个日志器对象都可以单独给自己设置模式(全局设置的话默认每种日志器的模式都是一样的,如果单独不修改的话)
logger->flush_on((spdlog::level::level_enum)level);
// 都是 32 位整型,直接强转
logger->set_level((spdlog::level::level_enum)level);
// 设置日志输出等级(全局,但后面可以根据日志器输出的时候进行修改)
} else {
// 下面也可以全局设置,但是每个日志器对象都可以单独给自己设置模式(全局设置的话默认每种日志器的模式都是一样的,如果单独不修改的话)
logger = spdlog::stdout_color_mt("default_logger");
logger->flush_on(spdlog::level::trace);
logger->set_level(spdlog::level::trace);
// 设置日志输出等级(全局,但后面可以根据日志器输出的时候进行修改)
}
logger->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");
}
// 使用{}进行占位
#define LOG_TRACE(fmt,...) logger->trace(std::string("[{}:{}]") + fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt,...) logger->debug(std::string("[{}:{}]") + fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt ,...) logger->info(std::string("[{}:{}]") + fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt ,...) logger->warn(std::string("[{}:{}]") + fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(fmt,...) logger->error(std::string("[{}:{}]") + fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_FATAL(fmt,...) logger->critical(std::string("[{}:{}]") + fmt, __FILE__, __LINE__, ##__VA_ARGS__)
这里不难发现和之前对应实现的日志系统项目模式是相似的。
这里可以看到同步的控制台输出和文件输出都是没问题的。
此时可以配合对应的 gflags 一起使用,让用户能灵活在运行程序时候通过参数进行选择:
#include "log.hpp"
#include <gflags/gflags.h>
DEFINE_bool(run_mode, false, "程序的运行模式,false-调试;true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
int main(int argc, char* argv[]) {
google::ParseCommandLineFlags(&argc, &argv, 1);
init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
LOG_DEBUG("你好:{}", "1");
LOG_INFO("你好:{}", "2");
LOG_WARN("你好:{}", "3");
LOG_ERROR("你好:{}", "4");
LOG_FATAL("你好:{}", "5");
LOG_DEBUG("这是一个测试");
}
grep -R '文本内容' 路径/文件
如下:
当前目录的文件指定包含'sync'这个文本的行打印出来(包括文件名 + 对应行,然后把对应文本标记出来)。gtest 作为单元测试框架,其详细用法可参考官方文档或相关技术社区资源。
本文带你了解了 gflags(解析命令行参数,支持多类型、自动生成帮助)与 spdlog(高性能日志,支持同步/异步、多输出目标),通过安装、基础用法及封装示例,展示两者在 C++ 项目中的价值。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online