Linux系统C++开发环境搭建工具(一)—— gflags/gtest/spdlog 使用指南
文章目录
- 一、基础工具安装:
- 二、gflags框架
- 简介
- 示例
- 三、gtest
- 简介
- 示例
- 四、spdlog
- 简介
- 示例
- 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;
}
测试结果:
非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!