日誌庫Log
2014年7月1日
2014年9月12日添加QT原生日誌函數
1 概述
在系統集成中,大多數以庫或可執行文件的形式集成(在windows中,dll形式的庫在集成時有很多問題,最著名的就是dll地獄,還有C++導出類等其它問題),集成中的調試信息很難通過程序的中調試程序輸出(如TRACE(),OutputDebugString()等。爲了能夠在運行中獲取調試信息,一般將程序的信息輸出到文件,以日誌的形式進行保存。
常用的日誌庫有log4c(可移植性好,文檔少),log4cpp(使用方便,文檔資源全,最接近log4j,推薦),log4cplus(功能全,複雜),log4qt(可以直接操作QT類)。Qt中可以使用通過的庫,原生的日誌使用qDebug()等的處理函數,將其記錄到文件中作爲日誌。
2 log4c
2.1 庫
官網:http://log4c.sourceforge.net/。
下載源碼編譯,或直接使用預編譯的版本。
2.2 操作
log4c移植於log4j(java),用於日誌輸出的純C庫,操作方法與log4j相同。
log4c通過log4crc文件指定相關的參數。
在程序中使用log4c_init():初始化。log4c_finit():去初始化。來初始化和釋放資源。
log4c_categary_get(name):從rc文件中獲取指定的category。
log4c_categary_log(pCat,type,str):依據rc文件的配置輸出log信息。
2.3 配置
categary:類別,不同的log對象的種類。
在RC文件中指定名稱、優先級和appender。
appender:輸出位置。
在RC文件中指定名稱、類別和layout、rollingpolicy。
layout:輸出格式。
在RC文件中指定名稱和類型。、
rollingpolicy:輸出文件策略。
在RC文件中指定文件的名稱、類型、最大、最小。
2.4 示例
參考:
入門:http://www.cnblogs.com/jyli/archive/2010/02/11/1660606.html
詳細:http://xueqi.iteye.com/blog/1570013
3 log4cpp
3.1 庫
官網:http://log4cpp.sourceforge.net/。
下載源碼編譯,無預編譯的版本。
3.2 編譯
下載後有各種庫的(如:msvc10等)工程文件,使用相關的程序(如:vs2012)打開(debug版對應不同的VC版本需要編譯成不同的文件,但是release版本可以通用),編譯,編譯lig4cpp(動態庫)和libcpplib(靜態庫)。
編譯完成後在工程文件目錄下有debug和release兩個目錄,將相關的lib、dll拷貝出來,源文件下有include目錄,就可以用了。
注意:在使用時需要與客戶程序相同的vc版本。
編譯完成後可以使用doxygen生成幫助文件。
參考:http://blog.csdn.net/kingskyleader/article/details/7320826
http://sogo6.iteye.com/blog/1154315
http://www.ibm.com/developerworks/cn/linux/l-log4cpp/
3.3 原理
log4cpp將所不同的日誌類型視爲不同的類別(Category)。每個日誌可以指定不同的輸出(Appender),輸出的格式使用佈局(Layout)設定。
類別是進行日誌操作的實體類,可以使用派生的子類直接繼承父類。所有類別的父類是rootCategory。類別需要指定輸出的優先級別(Priority),小於此級別的日誌將不輸出。
日誌具有不同的優先級,可以設置日誌輸出的優先級範圍。
在使用配置文件時,將所有的類屬性以鍵值對的方式進行配置。
3.4 方法
3.4.1類別Category:用於標識不同的日誌對象。
1) Category需要指定日誌優先級(Priority,只有大於指定Priority的日誌纔會記錄)和輸出目標(Appender,可以有多個輸出Appender)。
2) Category組織爲樹,子Category繼承父Category的Appender,使用additivity=false,可以重新生成Appender列表。
3) 由於在源程序的不同文件中可能需要使用同一個日誌輸出,所以使用靜態單例的方法,根據不同的名稱,生成不同的類別。
3.4.2輸出Appender:輸出日誌到指定的目標。
1) Appender也是樹形組織的類別,可以繼承。預定義有多種類型(參見源碼),常用的有:consoleAppender(輸出到std::cout),Win32DebugAppender(VC IDE中的輸出窗口==OutputDebugString),FileAppender(文件輸出),RollingFileAppender(循環文件輸出)。
2) Appender需要指定類型及其參數,並設置輸出佈局(Layout)。
3.4.3佈局Layout:設置日誌格式。
Layout需要指定類型及其參數。
3.4.4優先級Priority:設置需要輸出的級別範圍。
NOTSET < DEBUG < INFO < NOTICE < WARN< ERROR < CRIT < ALERT < FATAL = EMER
示例:
usingnamespacelog4cpp;
Appender*myAppender=newFileAppender("myapp","myapp.log");
myAppender->setLayout(newBasicLayout);
Category&myCat=Category::getInstance("mycat");
myCat.setPriority(Priority::DEBUG);
myCat.addAppender(myAppender)
myCat.debug("mycat.myapp.basiclayout.");
3.4.5配置文件
可以直接讀取配置文件,使用配置文件中的參數,生成指定的類,使用時直接獲取類進行輸出,操作方便,配置靈活。
配置參數參見:配置
3.5 操作
3.5.1程序生成
可以利用程序生成各種參數完成日誌記錄。
修改時不方便,僅用於原理演示。
示例:
#include<log4cpp/Category.hh>
#include<log4cpp/FileAppender.hh>
#include<log4cpp/BasicLayout.hh>
#pragmacomment(lib,"log4cpp.lib")
intmain(intargc,char*argv[])
{
usingnamespacelog4cpp;
log4cpp::Layout*pLayout=newlog4cpp::BasicLayout;
Appender*pAppender=newFileAppender("FileAppender","../test.log");
pAppender->setLayout(pLayout);
Category&warn_log= Category::getInstance("mywarn");
warn_log.setPriority(Priority::DEBUG);
warn_log.addAppender(pAppender);
warn_log.debug("%s,%d","hello,log4cpp!",12);
warn_log.shutdown();
return0;
}
3.5.2配置文件生成(推薦)
這是最常用,最方便的方式。
首先在配置文件中生成需要配置,然後加載配置文件,自動生成各種對象,程序只需要記錄日誌。
示例
//log4cpp.property
#註釋
#category
log4cpp.rootCategory=DEBUG,rootAppender
log4cpp.category.mywarn=WARN,simpAppender
log4cpp.category.cfglog=ERROR,debugAppender
#Appender
log4cpp.appender.rootAppender=ConsoleAppender
log4cpp.appender.rootAppender.layout=BasicLayout
log4cpp.appender.simpAppender=FileAppender
log4cpp.appender.simpAppender.layout=BasicLayout
log4cpp.appender.simpAppender.fileName=filelog.log
log4cpp.appender.debugAppender=FileAppender
log4cpp.appender.debugAppender.layout=BasicLayout
log4cpp.appender.debugAppender.fileName=debuglog.log
#Layout
//main.cpp
#include<log4cpp/Category.hh>
#include<log4cpp/PropertyConfigurator.hh>
#pragmacomment(lib,"log4cpp.lib")
#include<log4cpp/FileAppender.hh>
#include<log4cpp/BasicLayout.hh>
intmain(intargc,char*argv[])
{
log4cpp::PropertyConfigurator::configure("../log/log4cpp.property");
log4cpp::Category&cfglog=log4cpp::Category::getInstance("cfglog");
cfglog.debug("cfglog");
cfglog.warn("warnlog");
cfglog.emerg("emerglog");
return0;
}
3.6 配置
注意配置文件中大小寫敏感,尤其是一些屬性,必須按照規定的大小寫,遵守camel命名法。
3.6.1預定義信息
const char* constTimeStampComponent::FORMAT_ISO8601 = "%Y-%m-%d %H:%M:%S,%l";
constchar* const TimeStampComponent::FORMAT_ABSOLUTE = "%H:%M:%S,%l";
constchar* const TimeStampComponent::FORMAT_DATE = "%d %b %Y %H:%M:%S,%l";
constchar* PatternLayout::DEFAULT_CONVERSION_PATTERN = "%m%n";//default
constchar* PatternLayout::SIMPLE_CONVERSION_PATTERN = "%p - %m%n";//simple
constchar* PatternLayout::BASIC_CONVERSION_PATTERN = "%R %p %c %x: %m%n";//basic
constchar* PatternLayout::TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x -%m%n";//ttcc
3.6.2PatternLayout的ConversionPattern轉義字符表
詳見src/patternlayout.cpp。
1) "%",轉義字符,相當於printf中的\。
2) "%m",輸出信息message。
3) "%n",換行。
4) "%c",Category的名稱。
5) "%d",時間戳。
6) "%p",當前記錄行的Priority(僅指本條日誌)。
7) "%r",程序啓動以來的毫秒數(從0起算)。
8) "%R",程序啓動以來的秒數(從1970年起算)。
9) "%t",線程ID。
10) "%u",CPU時間。
11) "%x",nested diagnostic context。NDC爲區分不同線程設計。一個線程一個NDC,目標是獲取不同線程的嵌套關係,相同輸出的情況。
3.6.3RollingFileAppender
回捲文件。指定每個文件大小和文件數目。循環使用。
fileName:文件名。
maxFileSize:單文件最大字節數。
maxBackupIndex:最大文件後綴號(起始文件不存在後綴(後綴爲0,省略),其它文件從1開始)。
示例
#註釋
#category
log4cpp.rootCategory=DEBUG,rootAppender
log4cpp.category.log=NOTSET,debugAppender
#Appender
log4cpp.appender.rootAppender=ConsoleAppender
log4cpp.appender.rootAppender.layout=BasicLayout
log4cpp.appender.debugAppender=RollingFileAppender
log4cpp.appender.debugAppender.fileName=debuglog.log
log4cpp.appender.debugAppender.maxFileSize=1024
log4cpp.appender.debugAppender.maxBackupIndex=2
#Layout
log4cpp.appender.debugAppender.layout=PatternLayout
log4cpp.appender.debugAppender.layout.ConversionPattern=[%t-%p:%d][%r-%u][%x]%m%n
3.6.4參考
NDC:http://blog.csdn.net/mrliu20082009/article/details/7407319
配置:http://ustcfxx.iteye.com/blog/505390
3.7 常用調試示例
//log4cpp.property
#註釋
#root日誌
log4cpp.rootCategory=DEBUG,rootAppender
log4cpp.appender.rootAppender=ConsoleAppender
log4cpp.appender.rootAppender.layout=BasicLayout
#調試信息日誌:log
log4cpp.category.log=NOTSET,debugAppender
log4cpp.appender.debugAppender=RollingFileAppender
log4cpp.appender.debugAppender.fileName=debuglog.log
log4cpp.appender.debugAppender.maxFileSize=102400
log4cpp.appender.debugAppender.maxBackupIndex=2
log4cpp.appender.debugAppender.layout=PatternLayout
log4cpp.appender.debugAppender.layout.ConversionPattern=[%t-%p:%d][%r-%u][%x]%m%n
//main.cpp
#include<log4cpp/Category.hh>
#include<log4cpp/PropertyConfigurator.hh>
#pragmacomment(lib,"log4cpp.lib")
#include<log4cpp\NDC.hh>
intmain(intargc,char*argv[])
{
log4cpp::PropertyConfigurator::configure("../log/log4cpp.property");
log4cpp::Category&cfglog=log4cpp::Category::getInstance("log");
cfglog.debug("debuglog");
cfglog.warn("warnlog");
DWORDdwID=GetCurrentThreadId();
printf("id =%lu\n",dwID);
cfglog.debug(log4cpp::NDC::get());
log4cpp::NDC::push("NDCtest");
for(inti=0;i<1000;i++)
{
cfglog.debug("debug:%d",i);
}
cfglog.emerg("emerglog");
return0;
}
4 log4cplus
與log4cpp基本相同,區別是log4cpp中的category在log4cplus中是logger。
log4cplus較log4cpp更加細緻,支持也更加全面(android、ios),但操作也相應的複雜。
5 log4qt
可以直接操作QT的類,但是自2009以後就沒有更新了。
6 QT原生日誌:qInstallMsgHandler(推薦)
6.1 目的:使用QT方便,快捷,靈活的生成日誌文件。
1) 方便的生成日誌文件。
2) 可以細化爲不同的級別。
3) 可以生成時間,進程id,級別、消息的日誌。
4) 可以生成捲動日誌。
6.2 原理:使用qInstallMsgHandler將QDebug的輸出進行事件處理,然後輸出爲文件。
qInstallMsgHandler()可以將一個回調函數綁定的調試函數,當執行調試函數時會觸發此函數,進行操作(記錄到文件)。
qInstallMsgHandler()是一個全局函數,接受QtMsgHandler類型的函數指針。
QtMsgHandler是一個函數指針類型:typedefvoid (*QtMsgHandler)(QtMsgType,const char *)。
注意:Qt5處理qInstallMessageHandler和QtMessageHandler替代當前函數。
6.3 方法:使用qInstallMsgHandler將QDebug的輸出進行事件處理,然後輸出爲文件。
1) 生成處理函數:QtMsgHandler
2) 註冊處理函數:qInstallMsgHandler
3) 輸出調試信息:qDebug,qFatal….
4) 取消處理函數:qInstallMsgHandler(NULL)
5) 設置消息文件及格式:
內容和格式:只要設置輸出的String就可以了。
時間:QDateTime
線程ID:GetCurrentThreadId()(windows)
級別:qDebug….
消息:輸入參數msg
rollingfile:測試文件大小,然後自動rolling。如果有未滿文件,則順序查找,然後寫入,如果文件已滿,則查找最舊文件寫入。
參考:http://blog.chinaunix.net/uid-20698826-id-4232440.html
6.4 示例
/**
*@file logging.h
*@author[email protected]
*@version1.0
*@date2014-09-1215:26:47
*@brieflogfileprocess
*@details
*createlogfileandconfigthelogformat.
*
*@history
*/
#ifndefLOGGING_H
#defineLOGGING_H
#include<QFile>
#include<QTextStream>
#include<QDebug>
#include<QDateTime>
#include<QThread>
#include<Windows.h>
#include<QFileInfo>
namespacegutang{
namespacelogging{
///createlogfilenamewithnameprefixandindex
QStringcreateLogName(constQString&strName,inti);
///findtheoldlogfiletodeleteandlognew
QStringfindOldestFile(constQString&strName,intiMax);
///getthelogfilewhichcouldbelogged
QFile*getLogFile(constQString&strName,intiMaxSize,intiMax);
///QMsgHanderimplememt
voidmyHandler(QtMsgTypetMsg,constchar*msg);
}
}
#endif//LOGGING_H
/**
*@file logging.cpp
*@author[email protected]
*@version1.0
*@date2014-09-1215:26:47
*@brieflogfileprocess
*@details
*createlogfileandconfigthelogformat.
*
*@history
*/
#include"logging.h"
namespacegutang{
namespacelogging{
staticintS_Current_Index=0;///currentlogfileindex
/**
*@briefcreateLogName
*
*createlogfilename.
*ifindexis0,filenamewillbe:name.log.
*ifindexisnot0,filenamewillbe:name+index.log.
*@paramstrName
*@parami
*@return
*@author[email protected]
*@date2014-09-1215:15:34
*/
QStringcreateLogName(constQString&strName,inti)
{
QStringstrFileName;
if(0==i){
strFileName=QString("%1%2.log").arg(strName).arg("");
}
else{
strFileName=QString("%1%2.log").arg(strName).arg(i);
}
returnstrFileName;
}
/**
*@brieffindOldestFile
*
*findtheoldestlogfilethatshouldbedelete.
*@paramstrName
*@paramiMax
*@return
*@author[email protected]
*@date2014-09-1215:15:34
*/
QStringfindOldestFile(constQString&strName,intiMax)
{
inti=0;
QDateTimedtLast=QDateTime::currentDateTime();
QStringstrLast;
while(i<iMax)
{
QStringstrFileName=createLogName(strName,i);
QFileInfofi(strFileName);
QDateTimedt=fi.lastModified();
if(dtLast>dt){
dtLast=dt;
strLast=strFileName;
}
++i;
}
returnstrLast;
}
/**
*@briefgetLogFile
*
*getthelogfilewhichwillbeloged.
*@paramstrNamefilenameprefix
*@paramiMaxSizemaxfilesizeperlogfile
*@paramiMaxmaxlogfilecount
*@return
*@author[email protected]
*@date2014-09-1215:15:34
*/
QFile*getLogFile(constQString&strName,intiMaxSize,intiMax)
{
QFile*pFileLog=NULL;
//rollingfile
inti=S_Current_Index;
// intiMax=8;
boolbFull=false;
while(NULL==pFileLog)
{
QStringstrFileName;
if(i>=iMax)
{
bFull=true;
//findthelastmodified
strFileName=findOldestFile(strName,iMax);
QFile*pfLog=newQFile(strFileName);
pfLog->open(QIODevice::WriteOnly|QIODevice::Truncate);
pFileLog=pfLog;
break;
}
else{
strFileName=createLogName(strName,i);
}
QFile*pfLog=newQFile(strFileName);
pfLog->open(QIODevice::WriteOnly|QIODevice::Append);
if(pfLog->size()<iMaxSize)
{
pFileLog=pfLog;
break;
}
else
{
pfLog->close();
deletepfLog;
pfLog=NULL;
}
++i;
}
S_Current_Index=i;
returnpFileLog;
}
/**
*@briefmyHandler
*@paramtMsg
*@parammsg
*@author[email protected]
*@date2014-09-1215:15:34
*/
voidmyHandler(QtMsgTypetMsg,constchar*msg)
{
///
///howto?
///1.ifanyfileisnotfull,sequecelywritelog.
///2.ifallfilesarefull,findtheoldestoneandwritelog.
///
//filesetting
intiMaxFileSize=1024*1204;//maxfilesize=10M
intiMaxCount=8;//maxfilecount
QStringstrName("mylog");//logfilenameprefix
QStringstrLog("[%1-%2%3]%4\r\n");//logformat:[time-prioritythreadid]msg
//time
QDateTimet=QDateTime::currentDateTime();
strLog=strLog.arg(t.toString("yyyy-MM-ddhh:mm:ss.zzz"));
//priority
switch(tMsg){
caseQtDebugMsg:
strLog=strLog.arg("Debug");
break;
caseQtWarningMsg:
strLog=strLog.arg("Warning");
break;
caseQtCriticalMsg:
strLog=strLog.arg("Critiacl");
break;
caseQtFatalMsg:
strLog=strLog.arg("Fatal");
break;
default:
strLog=strLog.arg("Unknown:");
break;
}
//threadid
DWORDdwId=::GetCurrentThreadId();
strLog=strLog.arg(dwId);
//msg
strLog=strLog.arg(msg);
//savetofile
QFile*pFileLog=NULL;
// //singlefile
// QStringstrFileName=QString("%1%2.log").arg(strName).arg(i);
// QFile*pfLog=newQFile(strFileName);
// pFileLog=pfLog;
// pFileLog->open(QIODevice::WriteOnly|QIODevice::Append);
pFileLog=getLogFile(strName,iMaxFileSize,iMaxCount);
if(NULL==pFileLog){
return;
}
QTextStreamtsLog(pFileLog);
tsLog<<strLog;
pFileLog->close();
deletepFileLog;
pFileLog=NULL;
}
}
}