一.封裝的目的和使用範圍
在上一篇文章中介紹了Easylogging的簡單使用:http://blog.csdn.net/woshichenweixian/article/details/77018452 ,一般情況下,在自己的項目中使用時,可以再對其封裝一下,使其適合自己的使用習慣。這博文章主要介紹一下我在最近的一個項目中對Easylogging的封裝使用,該項目是一個C++後臺服務器,對log系統的使用要求如下:
(1)能區分log等級:debug,trace,info,warn,err等等級
(2)能方便控制某個等級的log輸出與否
(3)能方便輸出:char buf[len] , int buf[lend]等數組,而無需區分數組元素是字符類型還是整數類型
(4)能方便輸出數據包
二.初始化和配置
首先是對Easylogging初始化用一個宏進行封裝,若後期需要添加其他初始化代碼,則只需修改該宏就可以:
#define LogSysInit _INITIALIZE_EASYLOGGINGPP
接着是對Easylogging進行配置,Easylogging的配置和我們最終要使用的log等級有很大關係,所以先介紹一下在該封裝系統中對log等級的定義和使用方式:
對log等級進行封裝和控制,我個人一般對log等級區分如下:
Debug等級:只在在開發階段輸出的log,用於跟蹤開發過程中的調試輸出;
Info等級:輸出程序運行相關的信息,可在上線後選擇是否輸出該等級的log;
Trace等級:輸出一些程序狀態數據,用於跟蹤程序運行狀態,一般在上線後應該一直讓該等級的log輸出,但也可關掉該log。
Warn等級:輸出影響到程序正常運行,但還能繼續運行的數據信息。在上線後也應該一直讓該等級的log輸出。
Error等級:輸出導致程序不能繼續運行,必須馬上終止程序的錯誤信息。在上線後也應該一直讓該等級log輸出。
幾個比較容易混淆的log等級的使用情景如下:
Trace:跟蹤程序的運行分支,例如:
if(condition 1)
{
Log(Trace) << "in condition 1" ;
}
else if(condition 2)
{
Log(Trace) << "in condition 2" ;
}
Warn:不會導致程序必須馬上終止的錯誤:
if(valus == InvalidValus)
{
log(warn) << "valus is invalid:" << valus ;
return ;
}
DoSomeThing() ;
Error:導致程序必須馬上終止的錯誤:
char *buf = new(nothrow) char ;
if(buf == NULL)
{
log(Error) << "new is failed" ;
ExitProcess() ;
}
DoSomeThing() ;
爲了達成上面所說的log等級配置,可以抽象出一個配置接口出來:
#define LogSysCfg(cfgInfo , debugSwitch) LogCfg::initLog(cfgInfo,debugSwitch) ;
LogSysCfg宏傳入兩個配置參數,一個是cfgInfo,用來控制Info和Trace等級的log輸出;一個是debugSwitch,用來控制Debug等級的輸出。真正實現配置的地方在LogCfg類的靜態方法中:
class LogCfg{
public:
LogCfg(){}
/*@param cfgInfo,控制trace,info等級的log輸出:
err和warn等級任何情況下都輸出,trace和info等級的根據配置信息開啓:
0:不開啓trace,info
1:開啓trace,不開info
2:開啓trace和info
@param debugSwitch,控制debug等級的log輸出
*/
static bool initLog(int cfgInfo,int debugSwitch)
{
easyloggingpp::Configurations cfg;
cfg.setToDefault();
std::string cfgStr = GetCfgStr(cfgInfo,debugSwitch) ;
cfg.parseFromText(cfgStr.c_str()) ;
easyloggingpp::Loggers::reconfigureAllLoggers(cfg) ;
cfg.clear() ;
return true ;
}
static bool reCfgLog(){return true ;}
private:
static std::string GetCfgStr(int cfgInfo,int debugSwitch)
{
std::string cfgStr("*ALL:\n \
FORMAT = [%datetime] :%log\n \
ENABLED = false \n \
TO_FILE = true \n \
TO_STANDARD_OUTPUT = false \n \
ROLL_OUT_SIZE = 2097152 \n \
FILENAME = logs/ErrWarnLog.log \n \
*ERROR:\n \
ENABLED = true\n \
*WARNING:\n \
ENABLED = true\n \
*TRACE:\n \
FILENAME = logs/TraceLog.log \n \
ROLL_OUT_SIZE = 10485760 \n \
");
if((1 == cfgInfo) || (2== cfgInfo))
cfgStr += "ENABLED = true\n" ;
cfgStr += " *INFO:\n \
FILENAME = logs/InfoLog.log \n \
ROLL_OUT_SIZE = 10485760 \n \
" ;
if(2 == cfgInfo)
cfgStr += "ENABLED = true \n" ;
cfgStr += " *DEBUG:\n \
FILENAME = logs/DebugLog.log \n \
ROLL_OUT_SIZE = 10485760 \n \
" ;
if(1 == debugSwitch)
cfgStr += "ENABLED = true" ;
return cfgStr ;
}
};
經過上面的配置後,Debug,Info,Trace等級可配置,而Warn和Error等級的log會一直輸出。log文件在當前目錄下的logs文件夾裏面,每個等級的log存放在各自的文件裏面。
三.對log輸出的封裝
我個人的習慣是不直接使用Easylogging給出的宏,而是自己再封一層,因爲若以後需要改動的時候,直接改自己封的宏就好了。一些常見的封裝:
//普通log輸出
#define LogInfo LINFO
#define LogTrace LTRACE
#define LogWarn LWARNING
#define LogError LERROR
#define LogBreak(fmt) \
{ \
LogWarn<<fmt; \
break ; \
} \
//如果斷言失敗,則把該log打印出來,不會結束程序
#define LogAssert(condition) LWARNING_IF(!(condition))<<"[Log Assert]:"
//用於服務端發給客戶端時的log輸出
#define LogSTC LogDebug<<"[S To C]: "
//用於客戶端發給服務端端時的log輸出
#define LogCTS LogDebug<<"[C To S]: "
如果之後不想用Easylogging的LINFO,則只需修改宏就好,例如,把它改成用Easylogging的BINFO。
LogBreak宏可以方便的用來跳出循環之前打印一條log;
LogAssert宏則實現類似於斷言的功能;
LogSTC和LogCTS適用於CS架構中區分服務器發給客戶端huo客戶端發給服務器的消息。
接着是輸出數組的一個封裝:
enum LEVEL{
MY_ERR = 0 ,
MY_Warn ,
MY_Debug ,
MY_Trace ,
MY_Info
};
#define LogInfoDump(info , size,buff) logDump(info , buff , size , LEVEL::MY_Info)
#define LogTraceDump(info , size,buff) logDump(info , buff , size , LEVEL::MY_Trace)
#define LogWarnDump(info , size,buff) logDump(info , buff , size , LEVEL::MY_Warn)
#define LogErrorDump(info , size,buff) logDump(info , buff , size , LEVEL::MY_ERR)
#define LogDebugDump(info , size,buff) logDump(info , buff , size , LEVEL::MY_Debug)
template<class _TYPE>
void logDump(char *info,_TYPE *buff ,int size,LEVEL logLevel)
{
std::string str(info) ;
str += ": " ;
if(buff == NULL || size <=0)
str+="buff is null!" ;
else
{
std::stringstream ss ;
for(int _dumpArrSize = 0; _dumpArrSize < size ; ++_dumpArrSize)
ss << buff[_dumpArrSize] << "," ;
str += ss.str() ;
}
logStrOut(size , str ,logLevel) ;
}
void logStrOut(int size , std::string str , LEVEL logLevel)
{
switch(logLevel){
case MY_ERR:
LogError<<"[Arr Count:"<<size <<"]" <<str ;
break;
case MY_Debug:
LogDebug<<"[Arr Count:"<<size <<"]" <<str ;
break;
case MY_Info:
LogInfo<<"[Arr Count:"<<size <<"]" <<str ;
break;
case MY_Trace:
LogTrace<<"[Arr Count:"<<size <<"]" <<str ;
break;
case MY_Warn:
LogWarn<<"[Arr Count:"<<size <<"]" <<str ;
break;
default:break ;
}
}
char aa =1 ; char arr[3] = {1,2,3} ;
char aa = 1 ;
LogDebug<<"logDebug:" << aa ;
char buf[3] = {1,2,3} ;
LogDebugDump("logdebugdump:",3,buf) ;
本意是要輸出1和1,2,3的,但因爲在easylogging中,log是通過標準輸出流輸出的,所以如果輸出一個char類型數據,是其字符,而不是數值來的。而ASSCII碼的1,2,3對應的字符都是亂碼來的。
下一篇文章《Easylogging的封裝使用二》將講講如何解決這個問題,並講一下如何封裝自己的數據包(struct ,class類型)進行輸出。