v2.1 命令行参数更新

1. gflags库引入

戳我去gflags的介绍博客

gflagsGoogle开发的一个开源库,用于C++应用程序中命令行参数的声明、定义和解析。gflags库提供了一种简单的方式来添加解析文档化命令行标志(flags),使得程序可以根据不同的运行时配置进行调整。

有了gflags后,我们可以统一地使用命令行参数或者统一的配置文件方便地配置服务端的相关参数

2. 删除JSON配置文件

相比于命令行参数的配置文件,JSON文件写起来相对繁琐,且不够灵活,因此删除了JSON文件,全面使用命令行参数替代

3. 在main.cpp中定义命令行参数

因为如果要解析命令行参数,就必须使用google::ParseCommandLineFlags(&argc, &argv, true);解析main函数的传参,所以直接把命令行参数的定义写在main.cpp的全局区中

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <gflags/gflags.h>

DEFINE_string(host,"0.0.0.0","服务器监听的源ip地址范围");
DEFINE_uint32(port,80,"服务器的监听端口");

DEFINE_string(db_user,"root","数据库的连接用户");
DEFINE_string(db_password,"root_password","数据库用户的密码");
DEFINE_string(db_host,"localhost","数据库服务所在的ip");
DEFINE_string(db_port,"3306","数据库服务开放的端口");
DEFINE_string(db_database,"db","数据库中存储数据的数据库");

DEFINE_string(log_logfile,"./log/logfile","日志落地文件及路径");
DEFINE_bool(log_release_mode,false,"设置是否为发布模式输出日志,\ntrue:过滤低等级日志,输出到文件\nfalse:输出所有日志,输出到标准输出");
DEFINE_int32(log_release_level,spdlog::level::warn,"设置release模式下过滤的日志等级");

4. 修改HTTPServer和DatabaseClient的接口,使之能被显示初始化

部分修改代码接口如下(下面的代码含v2.2更新内容,由后文介绍)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
btyGoose::HTTPServer::HTTPServer()
{

}

void btyGoose::HTTPServer::initDB(const string&_user,const string&_password,const string&_host,const string&_port,const string&_database)
{
// LOG_TRACE("HTTPServer: 开始初始化db");
db.init(_user,_password,_host,_port,_database);
db.start();
}

void btyGoose::HTTPServer::init(const std::string& _host,const uint32_t _port)
{
host = _host;
port = _port;
initTestAPI();
initAccountAPI();
initConsumerAPI();
initMerchantAPI();
initAdminAPI();
LOG_DEBUG("HTTPServer初始化完成");
}

void btyGoose::HTTPServer::start()
{
LOG_DEBUG("HTTPServer开始监听{}:{}",host,port);
cout <<svr.listen(host, port);
}

DatabaseClient::DatabaseClient()
{

}

void DatabaseClient::init(const string&_user,const string&_password,const string&_host,const string&_port,const string&_database)
{
// LOG_TRACE("数据库开始初始化参数");
user = _user;
password = _password;
host = _host;
port = _port;
database = _database;
LOG_DEBUG("数据库初始化参数:\n\tuser:{}\n\tpassword:{}\n\thost:{}\n\tport:{}\n\tdatabase:{}",user,password,host,port,database);
}

void DatabaseClient::start()
{
try {
sql::mysql::MySQL_Driver *driver = sql::mysql::get_mysql_driver_instance();
// cout<< << "tcp://"+host+":"+port << "\nuser: " << user <<"\npassword: "<<password<<"\n";
LOG_INFO("开始连接数据库 tcp://{}:{}\n\tuser:{}",host,port,user);
con = driver->connect("tcp://"+host+":"+port, user, password);
con->setSchema("BeautyGoose");
stmt = con->createStatement();
} catch (sql::SQLException &e) {
LOG_FATAL( "数据库连接失败喵!\n\t错误码: {}\n\t信息:",e.getErrorCode(),e.what());
exit(-1);
}
}

修改main()函数体内容

1
2
3
4
5
6
7
8
9
10
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc,&argv,true);//解析命令行参数
btyGoose::init_logger(FLAGS_log_release_mode,FLAGS_log_logfile,FLAGS_log_release_level);//初始化日志器
LOG_DEBUG("全局日志器初始化成功");
btyGoose::HTTPServer::getInstance()->initDB(FLAGS_db_user,FLAGS_db_password,FLAGS_db_host,FLAGS_db_port,FLAGS_db_database);
btyGoose::HTTPServer::getInstance()->init(FLAGS_host,FLAGS_port);
btyGoose::HTTPServer::getInstance()->start();
return 0;
}

v2.2 日志输出更新

戳我去spdlog的介绍博客

1. spdlog日志库引入

spdlog 是一个高性能超快速零配置C++日志库,它旨在提供简洁的API和丰富的功能,同时保持高性能的日志记录。它支持多种输出目标格式化选项线程安全以及异步日志记录

2. spdlog二次封装

因为spdlog的日志输出对文件名和行号并不是很友好,以及因为spdlog本身实现了线程安全,如果使用默认日志器每次进行单例获取,效率会有降低,因此进行二次封装,简化使用。

同时因为多次包含头文件,如果声明和实现都写在.h文件中时,就会造成重复定义的严重问题,所以尽管二次封装的内容不多,还是采用了声明与实现分离地写在不同文件中。

我们主要实现如下几点功能:

  1. 日志的初始化封装接口
  2. 日志的输出接口封装宏
  3. 仅使用唯一的全局日志器对象

logger.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>
#include <iostream>

namespace btyGoose{
extern std::shared_ptr<spdlog::logger> g_default_logger;
void init_logger(bool mode, const std::string &file, int32_t level);

#define LOG_TRACE(format, ...) btyGoose::g_default_logger->trace(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(format, ...) btyGoose::g_default_logger->debug(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(format, ...) btyGoose::g_default_logger->info(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(format, ...) btyGoose::g_default_logger->warn(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) btyGoose::g_default_logger->error(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_FATAL(format, ...) btyGoose::g_default_logger->critical(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
}

logger.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "logger.hpp"

namespace btyGoose{
std::shared_ptr<spdlog::logger> g_default_logger;
void init_logger(bool mode, const std::string &file, int32_t level)
{
if (mode == false) {
//如果是调试模式,则创建标准输出日志器,输出等级为最低
g_default_logger = spdlog::stdout_color_mt("default-logger");
g_default_logger->set_level(spdlog::level::level_enum::trace);
g_default_logger->flush_on(spdlog::level::level_enum::trace);
g_default_logger->set_pattern("[%n][%H:%M:%S][%t]%^[%-8l]%$%v");//标记颜色的起始位置
}else {
//否则是发布模式,则创建文件输出日志器,输出等级根据参数而定
g_default_logger = spdlog::basic_logger_mt("default-logger", file);
g_default_logger->set_level((spdlog::level::level_enum)level);
g_default_logger->flush_on((spdlog::level::level_enum)level);
g_default_logger->set_pattern("[%n][%H:%M:%S][%t][%-8l]%v");
}

}
}

3. 替换原本的日志输出方式为二次封装的宏函数输出日志

原本HTTPServer类和DatabaseClient类的日志输出仅支持使用标准输出和标准错误输出的简单输出,因此我们需要使用封装好的宏函数将其替换,使服务端能够更加规范、高可读、高效率地输出日志

相关改动的文件:

  • HTTPServer.cpp
  • DatabaseClient.cpp