1.前言
日志和输出是一个服务器中必不可少的东西,它可以完整的记录服务器中关键的数据,用日志的形式保存起来。这样不仅可以给产品的升级优化提供准确的服务信息,还可以在数据崩溃的时候,作为版本还原的依据,一个好的日志系统,可以大大的提高产品优化效率,我们这里将利用前几天掌握的模板进行开发
发一下我的代码把:
链接:https://pan.baidu.com/s/13-wE2H_IY35kDP89swXTvQ
提取码:kviq
2.开发
①封装一个根据参数获取string的类
这个类的主要作用是传入任意参数,都可以得到对应的字符串,并且使用“<>”符号当作变量替代符,代码如下:
/**********************************************************
* Author : 谢名聪
* Email : 869408604@qq.com
* Last modified : 2022-04-22 15:25
* Filename : XLogFun.h
* Description : 根据参数获得string
* *******************************************************/
#ifndef X_LOG_FUN
#define X_LOG_FUN
#include <iostream>
namespace x_log_fun {
class XLogFun
{
public:
static XLogFun& me() {
static XLogFun me;
return me;
}
public:
//模板的特化,处理不能通过to_string转换的变量
std::string logGetString(const char* s);
std::string logGetString(char c);
std::string logGetString(std::string str);
template<typename T>
//数字类型
std::string logGetString(T t)
{
return std::to_string(t);
}
//定义一个插入参数的参数
//使用到了变长函数模板
void insertValue(int index, std::string& str)
{
//递归结束
return;
}
template <typename T, typename... Args>
void insertValue(int index, std::string& str, T t, Args...args)
{
while (index + 1 < str.size()) {
if (str[index] == '<' && str[index + 1] == '>' ) {
str = str.substr(0, index) + logGetString(t) + str.substr(index + 2, str.size() - 1);
insertValue(index++, str, args...);
return;
}
index++;
}
insertValue(0, str);
}
//返回函数
template<typename... Args>
void getLogInfo(std::string& str, Args... args)
{
return insertValue(0, str, args...);
}
};
}
#endif
#include "XLogFun.h"
//------------------------------------------------
//类函数的半特化不能放在类内实现
//放在类内实现在链接的时候会报错
//
//普通函数的模板函数不能放在类外实现
//放在类外实现在链接的时候会报错
//-----------------------------------------------
//定义一个转换字符串的函数模板
//数字类型的转换 int32_t int64_t uint32_t uint64_t
namespace x_log_fun {
//半数模板的半特化,const char* 类型的转换
std::string XLogFun::logGetString(const char* s)
{
return std::string(s);
}
//char类型
std::string XLogFun::logGetString(char c)
{
std::string str(1, c);
return str;
}
//std::string;类型
std::string XLogFun::logGetString(std::string str)
{
return str;
}
}
//----------------------------------------------
//后续可以根据自己的需求,不断增强自己的log类,比如引入json,proto的打印
②封装XLog类,用于创建日志文件并写入数据
这个类也是用了一个单例,毕竟特定的日志文件只需要一个,这样不用梳理输出的先后顺序,这里需要在配置文件中加入log/目录的路径。代码如下
/**********************************************************
* Author : 谢名聪
* Email : 869408604@qq.com
* Last modified : 2022-04-22 10:36
* Filename : XLog.h
* Description :将打印内容写入指定的文件,参数替代符为<>
* 例如LOG_DEBUG("aaa<>",1) 则写入[DEBUG——时间] aaa1
* *******************************************************/
#include "XLogFun.h"
#include "../XThread/XThread.h"
#include "../common/def.h"
#include <fstream>
#include <queue>
struct LogData
{
uint32_t logType;
std::string logMsg;
};
class XLog:public XThread
{
public:
static XLog& me()
{
static XLog me;
return me;
}
public:
virtual bool exec() override;
public:
//配置文件目录
bool init(std::string path);
public:
template<typename... Args>
void errorLog(std::string str, Args... args) {
insertLog(LOG_ERROR, str, args...);
}
template<typename... Args>
void warnLog(std::string str, Args... args) {
insertLog(LOG_WARN, str, args...);
}
template<typename... Args>
void debugLog(std::string str, Args... args) {
insertLog(LOG_DEBUG, str, args...);
}
template<typename... Args>
void insertLog(uint32_t logType, std::string info, Args... args)
{
using namespace x_log_fun;
LogData data;
data.logType = logType;
XLogFun::me().getLogInfo(info, args...);
data.logMsg = info;
m_queLogs.push(data);
}
void writeLog(LogData info);
private:
std::ofstream m_ofs;
std::queue<LogData> m_queLogs;
};
#include "XLog.h"
#include <time.h>
//-------------------------------------------
//XLog类函数的实现
//------------------------------------------
//打印当前时间
std::string currentTimeToStr(void){
char tmp[64];
time_t t = time(NULL);
tm *_tm = localtime(&t);
int year = _tm->tm_year+1900;
int month = _tm->tm_mon+1;
int date = _tm->tm_mday;
int hh = _tm->tm_hour;
int mm = _tm->tm_min;
int ss = _tm->tm_sec;
sprintf(tmp,"%04d-%02d-%02d-%02d-%02d-%02d", year,month,date,hh,mm,ss);
return std::string(tmp);
}
bool XLog::init(std::string path)
{
std::string file = path + "/LOG_" + currentTimeToStr();
m_ofs.open(file, std::ios::out);
return true;
}
bool XLog::exec()
{
while(status()) {
while (m_queLogs.size()) {
auto info = m_queLogs.front();
m_queLogs.pop();
writeLog(info);
}
}
//关闭文件
m_ofs.close();
return true;
}
void XLog::writeLog(LogData info)
{
std::string strInfo;
if (info.logType == LOG_ERROR) {
strInfo = "[ERROR_" + currentTimeToStr() + "]";
} else if (info.logType == LOG_WARN) {
strInfo = "[WARN_" + currentTimeToStr() + "]";
} else if (info.logType == LOG_DEBUG) {
strInfo = "[DEBUG_" + currentTimeToStr() + "]";
}
strInfo += info.logMsg;
m_ofs << strInfo << std::endl;
}
③封装一个输出类XPrint
这里可以做一个输出类,从而在流程中摒弃cout。这样也可以统一管理我们的输出,比如可以加上“在线上版本去掉输出”的限制,再或者特殊的输出指定不同的颜色,代码如下:
/**********************************************************
* Author : 谢名聪
* Email : 869408604@qq.com
* Last modified : 2022-04-23 08:07
* Filename : XPrint.h
* Description : 写一个自己的打印类,去替代cout,主要可以
* 增加颜色的控制,用醒目的颜色来标出自己的打印,便于找问题
* *******************************************************/
#ifndef X_PRINT_H
#define X_PRINT_H
#include "XLogFun.h"
#include<iostream>
class XPrint
{
public:
static XPrint& me()
{
static XPrint me;
return me;
}
public:
//错误输出 红色
template<typename... Args>
void errorPrint(std::string str, Args... args) {
using namespace x_log_fun;
XLogFun::me().getLogInfo(str, args...);
printf("\033[31m %s\n\033[0m",str.c_str());
}
//警告输出 黄色
template<typename... Args>
void warnPrint(std::string str, Args... args) {
using namespace x_log_fun;
XLogFun::me().getLogInfo(str, args...);
printf("\033[33m %s\n\033[0m",str.c_str());
}
//调试输出 绿色
template<typename... Args>
void debugPrint(std::string str, Args... args) {
using namespace x_log_fun;
XLogFun::me().getLogInfo(str, args...);
printf("\033[32m %s\n\033[0m",str.c_str());
}
};
#endif
④优化一下Makefile文件
这里有很多文件都是在.h中实现的,但是我们的Makefile中,并没有关联.h,这里就需要做出一些修改了。
cc=g++
cc_flags=\
-std=c++11 \
-I../include \
-MMD
ln_flags=\
-L../lib -lprotobuf \
-L../lib -lmysqlclient \
-lpthread \
-lm \
-ldl \
obj=\
../proto/src/User.pb.o \
XInclude/XConfig/XConfig.o \
XInclude/XMysql/XMysql.o \
XInclude/XThread/XThread.o \
XInclude/XLog/XLog.o \
XInclude/XLog/XLogFun.o \
XProcess/XProcess.o \
Process.o \
main.o \
target=process
$(target) : $(obj)
$(cc) $(ln_flags) $(obj) -o $(target)
%.o : %.cpp
$(cc) $(cc_flags) -c $< -o $@
%.o: %.cc
$(cc) $(cc_flags) -c $< -o $@
-include $(obj:.o=.d)
clean:
rm -f $(obj) $(obj:.o=.d) $(obj:.o=.d*) $(target) core.* log/*
⑥修改一下run.sh文件
创建一个log目录保存日志
ulimit -c 10000
export LD_LIBRARY_PATH=../lib/
mkdir log
./process config.ini
⑦修改一下config.ini文件
加入log_path的配置
##代表注释后面的内容
#中括号内是配置的类型
[mysql]
type=1
port= 3306
host =127.0.0.1
pwd = 123456
user=root
db=test
[server]
name=xprocess
type=1
port=10000
ip=127.0.0.1
id=1
log_path=log
⑧定义一个宏,让代码简洁一些
在XInclude/common/def.h中加入宏定义
/**********************************************************
* Author : 谢名聪
* Email : 869408604@qq.com
* Last modified : 2022-04-21 11:44
* Filename : def.h
* Description : 一些通用的宏定义,类的定义放在这里
* *******************************************************/
#ifndef BASE_DEF_H
#define BASE_DEF_H
//------------------------------------------
//日志类型
#define LOG_ERROR 1
#define LOG_WARN 2
#define LOG_DEBUG 3
#define SYS_ERROR_LOG XLog::me().errorLog
#define SYS_WARN_LOG XLog::me().warnLog
#define SYS_DEBUG_LOG XLog::me().debugLog
//--------------------------------------------
//输出
#define SYS_ERROR_PRINT XPrint::me().errorPrint
#define SYS_WARN_PRINT XPrint::me().warnPrint
#define SYS_DEBUG_PRINT XPrint::me().debugPrint
#endif
⑨在Process类中加入加载代码
bool Process::startAsynTools()
{
//开启日志
XLog::me().run();
for (auto tool : m_asynTools) {
tool->run();
}
return true;
}
bool Process::stopAsynTools()
{
for (auto tool : m_asynTools) {
tool->stop();
}
//关闭日志
XLog::me().stop();
return true;
}
bool Process::initLog(std::string path)
{
if (!XLog::me().init(path)) {
SYS_ERROR_PRINT("log init error");
return false;
}
return true;
}
⑩在XProcess类中写一个测试例子
bool XProcess::initProcess()
{
//加载数据库
std::string host = getConfigValue("mysql", "host");
std::string port = getConfigValue("mysql", "port");
std::string user = getConfigValue("mysql", "user");
std::string pwd = getConfigValue("mysql", "pwd");
std::string db = getConfigValue("mysql", "db");
std::string sqlType = getConfigValue("mysql", "type");
uint32_t type = std::atoi(sqlType.c_str());
if (!addMysqlServer(type, host.c_str(), port.c_str(), user.c_str(), pwd.c_str(), db.c_str())) {
SYS_ERROR_PRINT("addMysqlServer error");
return false;
}
//加载异步数据库
if (!addAsynMysqlServer(type, host.c_str(), port.c_str(), user.c_str(), pwd.c_str(), db.c_str())) {
SYS_ERROR_PRINT("addAsynMysqlServer error");
return false;
}
//加载服务名字和类型
std::string serverName = getConfigValue("server", "name");
std::string serverType = getConfigValue("server", "type");
std::string serverId = getConfigValue("server", "id");
m_name = serverName;
m_type = std::atoi(serverType.c_str());
m_id = std::atoi(serverId.c_str());
//加载配置
std::string path = getConfigValue("server", "log_path");
if (!initLog(path)) {
SYS_ERROR_PRINT("init log error");
return false;
}
return true;
}
bool XProcess::startProcess()
{
while (m_stop == false) {
/*
//获取系统时间戳
time_t timeReal;
time(&timeReal);
timeReal = timeReal + 8 * 3600;
tm* t = gmtime(&timeReal);
printf("%d-%02d-%02d %02d:%02d:%02d\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
*/
/*
std::string sql = "select * from user_data;";
SYS_DEBUG_PRINT("exeAsynSql():<>",sql);
exeAsynSql(1, sql.c_str(), std::bind(&XProcess::testAsynSql, this, std::placeholders::_1));
*/
SYS_ERROR_LOG("error日志 <> <> ",100,1);
SYS_WARN_LOG("Warn日志 <> <>", 200,'c');
SYS_DEBUG_LOG("Debug日志 <>", "ssssssssss");
SYS_ERROR_PRINT("error日志 <> <> ",100,1);
SYS_WARN_PRINT("Warn日志 <> <>", 200,'c');
SYS_DEBUG_PRINT("Debug日志 <>", "ssssssssss");
sleep(1);
};
return true;
}
3.看看效果吧
4.总结
做到这就算是把日志类封装好了,后面肯定还需要继续升级和优化。这里封装的时候碰到了一些坑,比如写模板的时候,碰到了很多链接时报错的问题,还有就是一些头文件重复包含的问题,感觉自己的修炼之路还有非常远的路要走啊。