Linux体系C++创建环境搭建程序(一)—— gflags/gtest/spdlog 使用指南
一、基础工具安装:
编辑器:
sudo apt-get install vim
编译器:sudo apt-get install gcc g++
调试器:sudo apt-get install gdb
项目构建工具:
sudo apt-get install make cmake
学习链接:Linux开发工具——make/Makefile
传输管理工具:
sudo apt-get install lrzsz
版本控制工具:
sudo apt-get install git
学习链接:企业开发工具git的使用:从入门到高效团队协作
二、gflags框架
简介
gflags 是 Google 开源的一个命令行参数解析与管理的 C++ 库。它的核心功能是让你能够轻松地定义、解析和管理从命令行传递给程序的参数。一个更强大、更系统化的 main(int argc, char** argv) 参数处理方案。
安装:
sudo apt-get install libgflags-dev
特性:
- 全局可访问:一旦在某个文件中定义了一个
flag(标志),它就可以在程序的任何地方(包括其他源文件)直接使用,而无需显式地传递参数。这是它名字中 “g” (global) 的由来。 - 类型安全:支持多种数据类型,如
bool,int32,int64,uint64,double,std::string等。 - 声明式定义:使用简单的宏(如
DEFINE_bool,DEFINE_string等)来定义flag,非常直观。 - 自动生成帮助信息:程序会自动生成
--help信息,列出所有已定义的flag、它们的类型、默认值和描述。 - 灵活性:
flag的值不仅可以通过命令行设置,还可以通过环境变量、特定的配置文件来设置。
示例
#include <gflags/gflags.h>
#include <iostream>
//DEFINE_*:定义并注册一个命令行参数
//参数1:设置命令行参数名称
//参数2:默认值
//参数3:参数说明
DEFINE_string(ip,"127.0.0.1","ip地址,格式:127.0.0.1");
DEFINE_int32(port,7272,"端口号,格式:7272");
DEFINE_bool(debug_enable,true,"是否开启调试模式,格式:true/false");
int main(int argc,char* argv[])
{
//使用ParseCommandLineFlags解析命令行参数并将其赋值给对应的 flag 变量
google::ParseCommandLineFlags(&argc,&argv,true);
//在gflags内部会把传入的name定义为FLAGS_name格式的全局变量
//我们将它输出
std::cout<<FLAGS_ip<<std::endl;
std::cout<<FLAGS_port<<std::endl;
std::cout<<FLAGS_debug_enable<<std::endl;
return 0;
}

查看帮助信息:
通过配置文件传入参数:
三、gtest
简介
gtest 的全称是 Google Test,是由 Google 开发的一个跨平台的 C++ 单元测试框架,gtest 的核心目的是进行 单元测试。单元测试是指对软件中的最小可测试单元(通常是函数、类的方法)进行检查和验证。
安装:
sudo apt-get install libgtest-dev
断言是测试的基石,用于验证条件是否成立。gtest 提供了两类主要的断言宏:
ASSERT_ 系列:当断言失败时,立即终止当前测试函数。
例如:ASSERT_EQ(5, Add(2, 3)); // 如果不等,测试立即停止。
EXPECT_ 系列:当断言失败时,报告错误但继续执行当前测试函数中的后续断言。
例如:EXPECT_TRUE(IsPrime(11)); // 如果不是 true,报告错误但继续。
这种区分让你可以选择是遇到致命错误就停止,还是收集一个测试函数中的所有错误。
示例
#include <gtest/gtest.h>
#include <iostream>
int Add(int num1,int num2)
{
return num1+num2;
}
TEST(测试名称1,加法测试用例)
{
ASSERT_EQ(Add(1,1),2);//相等
ASSERT_LT(Add(2,3),8);//小于
}
TEST(测试名称2,字符串比较测试)
{
std::string str = "linux";
ASSERT_EQ(str,"linux");
EXPECT_EQ(str,"Linux");
ASSERT_EQ(str,"hello");
EXPECT_EQ(str,"Hello");
}
int main(int argc,char* argv[])
{
//单元测试框架初始化
testing::InitGoogleTest(&argc,argv);
//开始所有单元测试
return RUN_ALL_TESTS();
}
效果:
四、spdlog
简介
spdlog是一个轻量、高效和易用的特点,被广泛应用于各种 C++ 项目中,在小型工具到大型商业应用都能看到它的身影。
特点:
- 高性能:
spdlog专为速度而设计,即使在高负载情况下也能保持良好的性能。 - 零配置:无需复杂的配置,只需包含头文件即可在项目中使用。
- 异步日志:支持异步日志记录,减少对主线程的影响。
- 格式化:支持自定义日志消息的格式化,包括时间戳、线程 ID、日志级别等。
- 多平台:跨平台兼容,支持
Windows、Linux、macOS等操作系统。 - 丰富的 API:提供丰富的日志级别和操作符重载,方便记录各种类型的日志。
安装:
sudo apt-get install libspdlog-dev
日志等级枚举spdlog 使用枚举 spdlog::level::level_enum 来定义日志级别,这决定了日志信息的重要性。级别从高到低(从最不重要到最重要)排列如下:
#include <spdlog/spdlog.h>
// 等级枚举值
spdlog::level::trace; // 跟踪信息,最详细的调试信息
spdlog::level::debug; // 调试信息,用于开发阶段
spdlog::level::info; // 一般信息,用于报告程序正常运行状态
spdlog::level::warn; // 警告信息,表示可能有问题,但程序仍能运行
spdlog::level::err; // 错误信息,表示程序执行中发生了错误
spdlog::level::critical; // 严重错误信息,可能导致程序崩溃
spdlog::level::off; // 关闭所有日志输出
日志记录器类spdlog::logger 是 spdlog 的核心类,负责接收日志消息并将其分发到一个或多个“落地类”进行处理。
异步日志记录器类
对于高性能要求的场景,spdlog 提供了异步日志记录器。
工作原理:
- 前端线程(业务线程)在调用日志函数(如
info(),error())时,并不会立即执行耗时的 I/O 操作(如写入文件)。而是将日志消息放入一个内存队列(环形缓冲区)。 - 后端有一个专用的工作线程,不断从队列中取出消息,并批量地分发给各个
sink进行实际的输出。
日志记录器工厂类
spdlog::registry 是一个单例类,它充当了日志记录器的工厂和仓库。
主要职责:
- 创建和存储 logger:当你使用
spdlog::create<>或spdlog::stdout_color_mt等工厂函数时,这些函数内部会通过registry来创建logger并存储起来。 - 按名称检索 logger:你可以通过
spdlog::get("logger_name")从registry中获取之前创建的logger。 - 全局配置:可以通过
registry设置全局的日志级别、格式模式等,这些设置会自动应用到所有已注册的logger上(除非logger有自己的特定设置)。
落地类sink(落地类)是实际负责将日志消息写入到特定目标的对象。spdlog::logger 本身不处理输出,而是将消息传递给其拥有的所有 sink。
全局接口spdlog 提供了一组非常方便的全局函数,用于快速记录日志,这些函数使用一个默认的全局 logger。
常用全局函数:
spdlog::set_level(level): 设置默认logger的级别。spdlog::flush_every(std::chrono):设置刷新策略,如每秒刷新spdlog::flush_on(spdlog::level::level_enum):遇到debug以上等级的日志立即刷新spdlog::trace(),debug(),info(),warn(),error(),critical(): 使用相应级别记录一条消息。spdlog::get("name"): 通过名称获取一个已注册的logger。spdlog::drop("name"): 从注册表中移除一个logger。spdlog::drop_all(): 移除所有logger。spdlog::shutdown(): 释放所有资源并关闭日志系统。
spdlog支持自定义日志输出格式,常用格式占位符:
%L:日志级别的简短表示。%l:日志级别的全称。%v或%@:实际的日志消息本身。这是最重要的占位符,代表你调用spdlog::info("Hello")中的"Hello"。%t:线程 ID。用于区分不同线程输出的日志,在多线程程序中非常有用。%P:进程 ID。%n:日志器的名称。当你使用多个命名日志器时,用于区分来源。%a:星期的缩写名称。%A:星期的全称。%b:月份的缩写名称。%B:月份的全称。%c:标准的日期时间字符串。%Y:四位数的年份。%m:两位数的月份 (01-12)。%d:两位数的日期 (01-31)。%H:24小时制的小时 (00-23)。%M:分钟 (00-59)。%S:秒 (00-59)。%e:毫秒 (000-999)。%f:微秒 (000000-999999)。%o:来源信息(文件名:行号,函数名)。需要编译时定义宏SPDLOG_USE_STD_FORMAT或特定编译器支持。
示例
#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
int main()
{
//设置全局刷新策略
//每秒刷新
spdlog::flush_every(std::chrono::seconds(1));
//遇到debug以上等级的日志立即刷新
//spdlog::flush_on(spdlog::level::level_enum::debug);
//设置全局日志输出等级
spdlog::set_level(spdlog::level::level_enum::trace);
//创建同步日志
auto log1 = spdlog::stdout_color_mt("default-logger");//参数为日志器的名称
//创建异步日志
init_thread_pool(3000,1)//初始化日志输出线程配置,设置日志队列容量最大为3000条,分配1条线程
auto log2 = spdlog::stdout_color_mt<spdlog::async_factory>("sync-logger");
//创建同步日志输出到文件
auto log3 = spdlog::basic_logger_mt("file-logger","test.log");
//设置日志输出格式
log1->set_pattern("[%H:%M:%S][%t][%-8l - %v] ");
log2->set_pattern("[%H:%M:%S][%t][%-8l - %v] ");
log3->set_pattern("[%H:%M:%S][%t][%-8l - %v] ");
//日志输出
log2->trace("我是异步日志");
log2->debug("hellp {}","log");//{}是占位符,不用我们指定输出的什么类型,像auto一样能自动检测
log2->info("hello {}",2+3);
log1->trace("我是同步日志");
log1->debug("hellp {}","log");
log1->info("hello {}",2+3);
log3->trace("我是同步日志输出到文件");
log3->debug("hellp {}","log");
log3->info("hello {}",2+3);
return 0;
}
编译时需要带选项连接库:-lspdlog,-lfmt
结果:
可以发现在代码逻辑上异步日志的是在前执行的,但是在整个程序执行完后才在后面输出。执行异步输出语句后,只是放在内存中,没有落盘操作,而用额外的线程池来处理。
spdlog二次封装
原因:
- 避免单例的锁冲突,因此直接创建全局的线程安全的日志器进行使用
- 因为日志输出没有文件名行号,因此使用宏进行二次封装输出日志的文件名和行号
- 封装出一个初始化接口,便于使用:调试模式则输出到标准输出,否则输出到文件中
思想:
封装出一个全局接口,用户进行日志器的创建与初始化
- 参数1:运行模式/调试模式 – true/false
- 参数2:输出文件名 – 用于发布模式
- 参数3:输出日志等级 – 用于发布模式
对日志输出的接口,进行宏的封装,加入文件名行号的输出
#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
std::shared_ptr<spdlog::logger> default_logger;
void init_logger(bool mode,std::string file,int32_t level)
{
if(mode == false)
{
//调试模式
default_logger = spdlog::stdout_color_mt("default-logger");
default_logger->set_level(spdlog::level::trace);
default_logger->flush_on(spdlog::level::trace);
}
else
{
//发布模式
default_logger = spdlog::basic_logger_mt("default-logger",file);
default_logger->set_level((spdlog::level::level_enum)level);
default_logger->flush_on((spdlog::level::level_enum)level);
}
}
#define LOG_TRACE(format,...) default_logger->trace(std::string("[{}:{}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define LOG_DEBUG(format,...) default_logger->debug(std::string("[{}:{}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define LOG_INFO(format,...) default_logger->info(std::string("[{}:{}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define LOG_WARN(format,...) default_logger->warn(std::string("[{}:{}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
#define LOG_ERR(format,...) default_logger->error(std::string("[{}:{}]")+format,__FILE__,__LINE__,##__VA_ARGS__)
测试文件:
#include "logger.hpp"
#include <gflags/gflags.h>
DEFINE_bool(mode,false,"程序的运行模式:false--debug模式(默认模式,true--发布模式");
DEFINE_string(file,"","在发布模式下日志的输出文件");
DEFINE_int32(level,0,"在发布模式下的日志输出等级");
int main(int argc,char* argv[])
{
google::ParseCommandLineFlags(&argc,&argv,true);
init_logger(FLAGS_mode,FLAGS_file,FLAGS_level);
LOG_TRACE("hello");
LOG_DEBUG("hello {}","linux");
LOG_INFO("hello info");
LOG_WARN("hello warn");
LOG_ERR("hello err");
return 0;
}
测试结果:
非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!

浙公网安备 33010602011771号