日志和输出是一个服务器中必不可少的东西,它可以完整的记录服务器中关键的数据,用日志的形式保存起来。这样不仅可以给产品的升级优化提供准确的服务信息,还可以在数据崩溃的时候,作为版本还原的依据,一个好的日志系统,可以大大的提高产品优化效率,我们这里将利用前几天掌握的模板进行开发
发一下我的代码把:
链接:https://pan.baidu.com/s/13-wE2H_IY35kDP89swXTvQ
提取码:kviq
这个类的主要作用是传入任意参数,都可以得到对应的字符串,并且使用“<>”符号当作变量替代符,代码如下:
#ifndef X_LOG_FUN #define X_LOG_FUN #includenamespace 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 //数字类型 std::string logGetString(T t) { return std::to_string(t); } //定义一个插入参数的参数 //使用到了变长函数模板 void insertValue(int index, std::string& str) { //递归结束 return; } template 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 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/目录的路径。代码如下
#include "XLogFun.h" #include "../XThread/XThread.h" #include "../common/def.h" #include#include 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 void errorLog(std::string str, Args... args) { insertLog(LOG_ERROR, str, args...); } template void warnLog(std::string str, Args... args) { insertLog(LOG_WARN, str, args...); } template void debugLog(std::string str, Args... args) { insertLog(LOG_DEBUG, str, args...); } template 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 m_queLogs; };
#include "XLog.h" #include③封装一个输出类XPrint//------------------------------------------- //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; }
这里可以做一个输出类,从而在流程中摒弃cout。这样也可以统一管理我们的输出,比如可以加上“在线上版本去掉输出”的限制,再或者特殊的输出指定不同的颜色,代码如下:
#ifndef X_PRINT_H #define X_PRINT_H #include "XLogFun.h" #include④优化一下Makefile文件class XPrint { public: static XPrint& me() { static XPrint me; return me; } public: //错误输出 红色 template void errorPrint(std::string str, Args... args) { using namespace x_log_fun; XLogFun::me().getLogInfo(str, args...); printf(" 33[31m %sn 33[0m",str.c_str()); } //警告输出 黄色 template void warnPrint(std::string str, Args... args) { using namespace x_log_fun; XLogFun::me().getLogInfo(str, args...); printf(" 33[33m %sn 33[0m",str.c_str()); } //调试输出 绿色 template void debugPrint(std::string str, Args... args) { using namespace x_log_fun; XLogFun::me().getLogInfo(str, args...); printf(" 33[32m %sn 33[0m",str.c_str()); } }; #endif
这里有很多文件都是在.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
#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) {
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.总结
做到这就算是把日志类封装好了,后面肯定还需要继续升级和优化。这里封装的时候碰到了一些坑,比如写模板的时候,碰到了很多链接时报错的问题,还有就是一些头文件重复包含的问题,感觉自己的修炼之路还有非常远的路要走啊。



