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支持的常见宏类型:

cpp 复制代码
DEFINE_bool
DEFINE_int32
DEFINE_int64
DEFINE_uint64
DEFINE_double
DEFINE_string

简单使用格式:

cpp 复制代码
#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");

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_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源是最新的):

shell 复制代码
sudo apt-get install libspdlogdev

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

  • 以及动态库在/usr/lib/x86_64-linux-gnu目录下。

spdlog 简单使用

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

包含的头文件:

cpp 复制代码
#include<spdlog/spdlog.h>//必须包含的
#include<spdlog/sinks/basic_file_sink.h>//文件输出模式
#include<spdlog/sinks/stdout_color_sinks.h>//控制台输出模式

下面看个同步模式的例子:

cpp 复制代码
#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::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;

    return 0;
}
  • 上面包含了对应同步日志器的控制条输出与文件输出日志器,还有对应的初始化设置。

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

如下:

cpp 复制代码
#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::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;

    return 0;

}

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

1·同步模式:

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

2·异步模式:

  • 明显这里就是多线程了,日志工作线程打印对应日志信息明显要慢一些。

注:

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

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

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

原因:

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

封装思想:

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

代码实现:

cpp 复制代码
#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::level_enum ::trace);
         logger->set_level(spdlog::level::level_enum ::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一起使用,让用户能灵活在运行程序时候通过参数进行选择:

cpp 复制代码
#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("这是一个测试");
}

测试:


  • 对应两种模式输出都是没问题的。

基于spdlog总结

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

总结文本查询小技巧

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

如下:

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

三.gtest介绍使用

可见这篇博文第三段:

传送门

四.本篇小结

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

相关推荐
R&ain18 小时前
C++中的深浅拷贝
开发语言·c++
R&ain18 小时前
C++的内联函数
c++·算法
fpcc19 小时前
跟我学C++中级篇——Linux中文件和链接及重定向
linux·c++
Fcy64820 小时前
C++ set&&map的模拟实现
开发语言·c++·stl
fpcc1 天前
C++编程实践——链式调用的实践
c++
bkspiderx1 天前
C++中的volatile:从原理到实践的全面解析
开发语言·c++·volatile
君义_noip1 天前
信息学奥赛一本通 2134:【25CSPS提高组】道路修复 | 洛谷 P14362 [CSP-S 2025] 道路修复
c++·算法·图论·信息学奥赛·csp-s
liulilittle1 天前
OPENPPP2 Code Analysis One
网络·c++·网络协议·信息与通信·通信
Morwit1 天前
*【力扣hot100】 647. 回文子串
c++·算法·leetcode