gflags+spdlog实战:C++命令行参数与高性能日志的极致搭配行动指南

gflags+spdlog实战:C++命令行参数与高性能日志的极致搭配行动指南

文章目录

在这里插入图片描述

本篇摘要

本文介绍gflags命令行参数解析库(轻量高效、类型安全)与spdlog高性能日志库(同步/异步、多平台),涵盖安装、基础使用及二次封装等帮助C++项目灵活配置与高效日志管理。

一.gflags 介绍及简单使用

简单介绍

Google 开源的命令行参数解析库,用于高效管理程序启动参数(如 --flag=value)。

核心功能:

  • 定义参数:通过宏(如 DEFINE_int32DEFINE_string)声明参数名、默认值和帮助信息。
  • 解析参数:调用 ParseCommandLineFlags 解析 argc/argv,将命令行参数映射到全局变量(如 FLAGS_<name>)。
  • 快速访问:通过全局变量 FLAGS_<flag_name> 直接获取解析后的值。

特点:

  • 轻量高效:专为命令行参数设计,解析速度快,适合 C++ 项目。
  • 类型安全:支持多种数据类型(整型、字符串等),编译期检查。
  • 自动生成帮助:通过 --help 自动输出参数说明(依赖定义时的描述信息)。

对比其他工具:

类似功能库:Boost.Program_options(C++)、Python 的 argparse
gflags 优势:与 Google 生态兼容,API 简洁,适合高性能场景。

安装过程

用源码装gflags库步骤:

  1. 下载源码:git clone https://github.com/gflags/gflags.git
  2. 进入目录:cd gflags/
  3. 新建并进入build文件夹:mkdir build; cd build/
  4. 生成Makefile:cmake ..
  5. 编译:make
  6. 安装:make install
在这里插入图片描述
  • 可以看到对应安装在include目录里,以及动态库在/usr/lib/x86_64-linux-gnu目录下。

gflags简单使用

gflags支持的常见宏类型:

DEFINE_bool DEFINE_int32 DEFINE_int64 DEFINE_uint64 DEFINE_double DEFINE_string 

简单使用格式:

#include<gflags/gflags.h>//使用的时候需要动态链接gflags库#include<iostream>DEFINE_string(ip,"127.0.0.1","服务端ip");DEFINE_int32(port,8080,"服务端监听端口");DEFINE_bool(debug_enable,true,"是否启用调试模式, 格式:true/false");intmain(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;return0;}

google::ParseCommandLineFlags 介绍:

google::ParseCommandLineFlags 是 Google 工具库里的函数,用来处理程序启动时输入的命令行参数。

它接收 main 函数里的 argc(参数个数)和 argv(参数数组),还有个 remove_flags 参数控制后续行为:

  • remove_flagstrue,解析后会从 argv 里删除已识别的“标志和参数”,同时 argc 也会变小;
  • 若为 falseargc 不变,但会把所有“标志”移到 argv 最前面重新排好。

一句话:帮你自动解析命令行里的各种选项/开关,让程序能读懂用户输的参数。

过程大致概括:

  • 输入在对应gflags定义在宏里参数(通过命令行或者配置文件指定或者默认输进去的),此时gflags会内部把它解析出来,对用户提供对应的GFLAGS_变量名访问接口,方便用户获取。
  • 也就是这样做可以不用每次都手动修改代码对应参数变量等,而是通过程序参数方式进行输入,避免了重复编译带来的消耗。

使用方式

1·直接使用默认的参数
在这里插入图片描述
  • 这里就直接拿宏函数中默认的值进行输入到main的参数中解析(这里会把- -将会终止标识的处理)。
2·使用命令行参数
在这里插入图片描述
  • 在命令行启动程序的时候按照对应格式输入。
3·使用配置文件输入
在这里插入图片描述


在这里插入图片描述
  • 这里直接让它从配置文件中读取对应参数即可。

使用参考

gflags提供了一些特殊参数标识:

  • –help:显示文件中所有标识的帮助信息。
  • –helpfull:与–help功能类似,但帮助信息更全面。
  • –helpshort:仅显示当前执行文件里的标志。
  • –helpxml:以xml格式打印,便于处理。
  • –version:打印版本信息,由google::SetVersionString()设定。
  • –flagfile:从指定的文件f中读取命令行参数。

比如这里可以使用对应- -help选项查看对应的gflags设置的对应输入参数如何使用:

二.Spdlog组件介绍及简单使用

简单介绍

spdlog 是一个高性能、超快速、零配置的 C++ 日志库,旨在提供简洁 API 和丰富功能,同时保持高性能的日志记录,支持多种输出目标、格式化选项、线程安全及异步日志记录。(也就是类似之前实现的日志系统项目,同时对比glog是功能更加多的,比如前者支持异步模式但后者不支持等等)

特点介绍:

  • 特点 - 高性能:spdlog 专为速度设计,高负载时也能保持良好性能。
  • 特点 - 零配置:无需复杂配置,包含头文件就可在项目中使用。
  • 特点 - 异步日志:支持异步日志记录,减少对主线程影响。
  • 特点 - 格式化:支持自定义日志消息格式化,如时间戳、线程 ID、日志级别等。
  • 特点 - 多平台:跨平台兼容,支持 Windows、Linux、macOS 等操作系统。
  • 特点 - 丰富的 API:提供丰富日志级别和操作符重载,方便记录各类日志。

安装过程

对应命令进行安装即可(保证apt或者yum源是最新的):

sudoapt-getinstall libspdlogdev 

可以看到对应安装好后的头文件集合:

在这里插入图片描述
  • 以及动态库在/usr/lib/x86_64-linux-gnu目录下。

spdlog 简单使用

下面认识下常见的基础的使用方法:

包含的头文件:

#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>intmain(){usingnamespace std::literals;// 设置刷新时间间隔 spdlog::flush_every(1s);// 刷新策略等级 spdlog::flush_on(spdlog::level::level_enum ::trace);// 设置日志输出等级(全局,但后面可以根据日志器输出的时候进行修改) spdlog::set_level(spdlog::level::level_enum ::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;return0;}
  • 上面包含了对应同步日志器的控制条输出与文件输出日志器,还有对应的初始化设置。

对应的异步模式仅仅是修改对应的创建日志器的模版函数的模版即可(默认模版是同步日志器):

如下:

#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>intmain(){usingnamespace std::literals;//设置刷新时间间隔 spdlog::flush_every(1s);// 刷新策略等级 spdlog::flush_on(spdlog::level::level_enum ::trace);//设置日志输出等级(全局,但后面可以根据日志器输出的时候进行修改) spdlog::set_level(spdlog::level::level_enum ::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;return0;}

下面来对比下对应打印出来的效果差异:

1·同步模式:

在这里插入图片描述
  • 可以看出这里只有一个线程完成的,打印日志的时候需要阻塞。

2·异步模式:

在这里插入图片描述
  • 明显这里就是多线程了,日志工作线程打印对应日志信息明显要慢一些。

注:

  • 这里使用的时候除了手动链接对应的spdlog库还需要链接对应的格式库即fmt。

基于spdlog使用的二次封装(默认同步日志器)

  • 为什么还需要进行封装,不直接拿来用?

原因:

  • 避免单例锁冲突,创建全局线程安全日志器。
  • 日志输出无文件名行号,用宏二次封装输出相关信息。
  • 封装初始化接口,调试模式输出到标准输出,否则输出到文件。

封装思想:

封装全局接口供用户创建与初始化日志器,用户只需要对对应函数根据输入参数的不同模式完成调用即可,包含初始化接口接收运行模式、输出文件名、输出日志等级等参数,用宏封装日志输出接口,加入文件名行号输出

代码实现:

#pragmaonce #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调试模式(默认全都是最低等级如刷新策略等级,日志等级)voidinit_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::level_enum ::trace); logger->set_level(spdlog::level::level_enum ::trace);//设置日志输出等级(全局,但后面可以根据日志器输出的时候进行修改)} logger->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");}//使用{}进行占位#defineLOG_TRACE(fmt,...) logger->trace(std::string("[{}:{}] -->")+fmt,__FILE__,__LINE__,##__VA_ARGS__)#defineLOG_DEBUG(fmt,...) logger->debug(std::string("[{}:{}] -->")+ fmt,__FILE__,__LINE__,##__VA_ARGS__)#defineLOG_INFO(fmt ,...) logger->info(std::string("[{}:{}] -->")+ fmt,__FILE__,__LINE__,##__VA_ARGS__)#defineLOG_WARN(fmt ,...) logger->warn(std::string("[{}:{}] -->")+ fmt,__FILE__,__LINE__,##__VA_ARGS__)#defineLOG_ERROR(fmt,...) logger->error(std::string("[{}:{}] -->")+ fmt,__FILE__,__LINE__,##__VA_ARGS__)#defineLOG_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,"发布模式下,用于指定日志输出等级");intmain(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("这是一个测试");}

测试:

在这里插入图片描述


在这里插入图片描述
  • 对应两种模式输出都是没问题的。

基于spdlog总结

  1. 功能强大:提供丰富日志功能,满足多样需求。
  2. 使用简单:API 简单,开发者易上手,快速实现日志记录。
  3. 性能高效:具备高性能日志记录能力。
  4. 代码友好:保持代码清晰,利于维护。
  5. 环境适用:开发和生产环境都能稳定高效服务。

总结文本查询小技巧

grep -R '文本内容' 路径/文件 
  • ​​grep​​: 是一个强大的文本搜索工具,用于在文件中查找匹配指定模式的行。
  • -R​​: 是 grep命令的一个选项,代表“递归(recursive)”。使用该选项时,grep会递归地搜索指定目录及其所有子目录中的文件内容。
  • 要查的文本内容: 是你要搜索的模式,意味着 grep会在文件/目录中查找包含 这个文本内容的行。
  • 路径/文件: 要查找的路径或者文件范围。

如下:

  • 这里会按照指定的当前目录进行查询,然后递归式的把当前目录的文件指定包含'sync'这个文本的行打印出来(包括文件名+对应行,然后把对应文本标记出来)。

三.gtest介绍使用

可见这篇博文第三段:

传送门

四.本篇小结

本文带你了解了gflags(解析命令行参数,支持多类型、自动生成帮助)与spdlog(高性能日志,支持同步/异步、多输出目标),通过安装、基础用法及封装示例,展示两者在C++项目中的价值。

Read more

Flutter 三方库 shelf_modular 的鸿蒙化适配指南 - 掌控服务器路由资产、精密模块治理实战、鸿蒙级服务端专家

Flutter 三方库 shelf_modular 的鸿蒙化适配指南 - 掌控服务器路由资产、精密模块治理实战、鸿蒙级服务端专家

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 shelf_modular 的鸿蒙化适配指南 - 掌控服务器路由资产、精密模块治理实战、鸿蒙级服务端专家 在鸿蒙跨平台应用执行高级服务端管理与多维 Shelf 路由资产指控(如构建一个支持全场景秒级交互的鸿蒙大型全量后端服务中枢、处理海量 API Route Payloads 的语义认领或是实现一个具备极致指控能力的资产管理后台路由审计中心)时,如果仅仅依赖官方的基础 Shelf 处理器或者是极其繁琐的手动路由映射,极易在处理“由于模块嵌套导致的资产认领偏移”、“高频服务请求下的认领假死”或“由于多语言环境导致的符号解析冲突死结”时陷入研发代码服务端逻辑崩溃死循环。如果你追求的是一种完全对齐现代模块化标准、支持全量高度可定制路由(Modular-driven Backend)且具备极致指控确定性的方案。今天我们要深度解析的 shelf_modular——一个专注于解决“服务端资产标准化认领与模块化解耦”痛点的顶级工具库,正是帮你打造“鸿蒙超

By Ne0inhk
Flutter 三方库 wallet_connect 的鸿蒙化适配指南 - 实现 Web3 钱包协议连接、支持 DApp 授权登录与跨链交易签名实战

Flutter 三方库 wallet_connect 的鸿蒙化适配指南 - 实现 Web3 钱包协议连接、支持 DApp 授权登录与跨链交易签名实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 wallet_connect 的鸿蒙化适配指南 - 实现 Web3 钱包协议连接、支持 DApp 授权登录与跨链交易签名实战 前言 在进行 Flutter for OpenHarmony 的去中心化应用(DApp)或加密货币钱包开发时,支持标准的 WalletConnect 协议是链接用户钱包的关键。wallet_connect 是该协议的 Dart 实现,它能让你的鸿蒙 App 安全地与 MetaMask、Trust Wallet 等钱包建立双向加密连接。本文将探讨如何在鸿蒙系统下构建安全、稳定的 Web3 授权流程。 一、原理解析 / 概念介绍 1.1 基础原理

By Ne0inhk

【保姆级教程】从零部署宇树 Unitree 机器人 ROS 2 环境 (Go2/B2/H1) (Humble + 真实硬件)

摘要 本文为希望在ROS 2 (Humble) 环境下开发宇树 (Unitree) 机器人(支持 Go2, B2, H1)的开发者提供了一篇详尽的、从零开始的部署指南。我们将首先在 Ubuntu 22.04 上安装 ROS 2 Humble,然后重点讲解如何配置 unitree_ros2 功能包,实现 ROS 2 节点与机器人底层 DDS 系统的直接通信。本教程基于官方文档,并针对 Humble 环境进行了优化,可跳过 Foxy 版本复杂的 CycloneDDS 编译步骤。 核心环境: * 操作系统: Ubuntu 22.04 (Jammy) * ROS 2 版本: Humble

By Ne0inhk
Flash Table实测:JAI赋能低代码开发,重塑企业级应用构建范式

Flash Table实测:JAI赋能低代码开发,重塑企业级应用构建范式

目录 * 🔍 引言 * 1.1 什么是Flash Table * 1.2 低代码平台的进化与FlashTable的革新 * ✨FlashTable背景:为什么需要新一代低代码平台? * 2.1 传统开发的痛点 * 2.2 低代码平台的局限 * 2.3 FlashTable的差异化定位 * 💻 FlashTable安装:Docker部署&Jar包部署 * 3.1 基础环境要求 * 3.2 Docker部署(推荐方案) * 3.3 Jar包部署(无Docker环境) * 3.4 常见问题 * 📚FlashTable功能深度评测:从案例看真实能力 * 4.1 数据孤岛?FlashTable 自动化匹配字段 * 4.2 FlashTable复杂表单的开发挑战 * 4.3

By Ne0inhk