C++編程之自定義日誌類 ——log4cpp使用詳解
log4cpp簡介與安裝
log4cpp是一個開源的C++日誌管理庫,可以通過它來記錄程序運行過程中產生的各種信息。也可以進行再包裝實現個人自定義的日誌類。
log4cpp安裝
- 下載:
wget https://nchc.dl.sourceforge.net/project/log4cpp/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-1.1.3.tar.gz
- 解包
tar zxvf log4cpp-1.1.3.tar.gz
cd log4cpp
./configure --with-pthreads
./configure
make
make install
- 添加環境變量
vim /etc/profile.d/log4cpp.sh
在文件中添加:
LD_LIBRARY_PATH=:$LD_LIBRARY_PATH:/usr/local/lib export LD_LIBRARY_PATH
修改文件權限
chmod a+x /etc/profile.d/log4cpp.sh
ldconfig -v
安裝好後, 在編譯源碼文件時要加上-llog4cpp -lpthread
來鏈接動態庫
log4cpp簡單介紹
log4cpp庫中主要分爲三大類:Category(種類)、Appender(附加目的地)和Layout(佈局)。
- category類是日誌記錄的主要執行類,它負責寫日誌
- appender類用來指明目的地,即日誌要寫到什麼地方去
- layout類指明日誌輸出的格式
應用時的大致流程:
- 定義一個layout類對象,確定輸出日誌信息的格式
- 定義一個appender類對象,確定日誌輸出到什麼地方,然後把layout對象用setlayout方法綁定一下。
- 定義一個catergory對象,與appender類對象綁定
- 調用catergory對象進行寫日誌
簡單示例
#include <iostream>
#include <log4cpp/Category.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/BasicLayout.hh>
#include <log4cpp/BasicLayout.hh>
int main()
{
//1. 初始化一個layout對象
log4cpp::Layout* layout = new log4cpp::BasicLayout();
// 2. 初始化一個appender 對象
log4cpp::Appender* appender = new log4cpp::FileAppender("FileAppender","./test_log4cpp1.log");
// 3. 把layout對象附着在appender對象上
appender->setLayout(layout);
// 4. 實例化一個category對象
log4cpp::Category& warn_log = log4cpp::Category::getInstance("mywarn");
// 5. 把appender對象附到category上
warn_log.setAppender(appender);
// 6. 設置category的優先級,低於此優先級的日誌不被記錄
warn_log.setPriority(log4cpp::Priority::WARN);
// 記錄一些日誌
warn_log.info("Program info which cannot be wirten");
warn_log.debug("This debug message will fail to write");
warn_log.alert("Alert info");
// 其他記錄日誌方式
warn_log.log(log4cpp::Priority::WARN, "This will be a logged warning");
log4cpp::Priority::PriorityLevel priority;
bool this_is_critical = true;
if(this_is_critical)
{
priority = log4cpp::Priority::CRIT;
}
else
{
priority = log4cpp::Priority::DEBUG;
}
warn_log.log(priority,"Importance depends on context");
// 清理所有資源
log4cpp::Category::shutdown();
return 0;
}
可以看到整套流程下來還是有點複雜的,可以在後續包裝成一個自定義的日誌類進行日誌記錄。
layout佈局——日誌輸出格式
layout對象規定了日誌輸出的內容格式,創建後需要和appender對象綁定生效。
需要注意的是,一個佈局對象只能綁定一個appender對象。
比較常用的佈局有兩種:log4cpp::BasicLayout
和log4cpp::PatternLayout
log4cpp::BasicLayout
log4cpp::BasicLayout是最簡單的佈局,輸出時間戳,消息優先級和消息內容。
示例代碼如下:
#include<iostream>
#include"log4cpp/Category.hh"
#include"log4cpp/OstreamAppender.hh"
#include"log4cpp/BasicLayout.hh"
#include"log4cpp/Priority.hh"
using namespace std;
int main(int argc,char* argv[])
{
log4cpp::OstreamAppender* osAppender=newlog4cpp::OstreamAppender("osAppender",&cout);
osAppender->setLayout(newlog4cpp::BasicLayout());
log4cpp::Category& root =log4cpp::Category::getRoot();
root.addAppender(osAppender);
root.setPriority(log4cpp::Priority::DEBUG);
root.error("Hello log4cpp in aError Message!");
root.warn("Hello log4cpp in aWarning Message!");
log4cpp::Category::shutdown();
return 0;
}
輸出的日誌格式如下:
1248337987 ERROR : Hello log4cppin a Error Message!
1248337987 WARN : Hello log4cppin a Warning Message!
log4cpp::PatternLayout
log4cpp::PatternLayout佈局支持通過類似printf函數的格式控制符的方式自定義輸出的信息和內容。通過使用以下函數進行設置:
log4cpp::PatternLayout::setConversionPattern (conststd::string& conversionPattern) ;
該函數接收的參數爲格式控制字符串,其中符號含義如下:
%c: 記錄日誌的category對象名稱;
%d: 日期;日期可以進一步的設置格式,用花括號包圍,例如%d{%H:%M:%S,%l} 或者 %d{%d %m %Y%H:%M:%S,%l}。如果不設置具體日期格式,則如下默認格式被使用“Wed Jan 02 02:03:55 1980”。日期的格式符號與ANSI C函數strftime中的一致。但增加了一個格式符號%l,表示毫秒,佔三個十進制位。
%m: 要輸出的日誌消息字符串;
%n 換行符,會根據平臺的不同而不同,但對於用戶透明;
%p 優先級,warn,debug,info等待;
%r 自從layout被創建後的毫秒數;
%R 從1970年1月1日0時開始到目前爲止的秒數;
%u 進程開始到目前爲止的時鐘週期數;
%x NDC
代碼示例:
MyLog::screenLayout = new log4cpp::PatternLayout();
screenLayout->setConversionPattern("%d{%Y/%m/%d,%H:%M:%S} -- %p %c: %m%n");
上述代碼表示日誌記錄的信息依次是“日期(年月日時分秒)-- 優先級 catgory:消息 換行”
appender
appender對象指定日誌輸出到什麼地方去,創建後需要和category對象綁定才能生效。
一個apender只能和一個category對象綁定,但是一個category對象可以有多個appnder,可以輸出到多個位置。
常用的appender類如下:
log4cpp::FileAppender // 輸出到文件
log4cpp::RollingFileAppender // 輸出到回捲文件,即當文件到達某個大小後回捲
log4cpp::OstreamAppender // 輸出到一個ostream類
log4cpp::StringQueueAppender // 輸出到內存隊列
log4cpp::FileAppender
構造函數如下:
FileAppender(conststd::string& name, conststd::string& fileName, bool append = true, mode_tmode = 00644);
一般僅使用前兩個參數,即“名稱”( FileAppender對象的名稱)和“日誌文件名”(要寫入日誌的文件名)。第三個參數指示是否在日誌文件寫滿後繼續記入日誌,還是清空原日誌文件再記錄。第四個參數說明文件的打開方式。
FileAppender對象創建完畢後,調用成員函數setLayout來綁定一個佈局對象。
log4cpp::RollingFileAppender
RollingFileAppender對象會在文件長度到達指定值時循環記錄日誌,文件長度不會超過指定值。
構造函數如下:
RollingFileAppender(const std::string&name, const std::string&fileName,size_tmaxFileSize =10*1024*1024, unsigned intmaxBackupIndex = 1,boolappend = true, mode_t mode =00644);
該函數與FileAppender的創建函數很類似,但是多了兩個參數:maxFileSize指出了回滾文件的最大值;maxBackupIndex指出了回滾文件所用的備份文件的最大個數。所謂備份文件,是用來保存回滾文件中因爲空間不足未能記錄的日誌,備份文件的大小僅比回滾文件的最大值大1kb。
log4cpp::OstreamAppender
log4cpp::OstreamAppender 對象可以將日誌信息輸出到指定的流類中:
構造方法如下:
log4cpp::OstreamAppender* osAppender = newlog4cpp::OstreamAppender("osAppender", &cout);
第一個參數是OstreamAppender對象的名稱,第二個參數指定它關聯的流的指針。
log4cpp::StringQueueAppender
log4cpp::StringQueueAppender 可以將日誌信息保存到內存隊列中,在程序運行結束後再進行處理,主要用於記錄多線程程序或者實時程序的日誌,防止輸出操作引起IO中斷導致線程掛起,影響效率。
其構造函數如下:
log4cpp::OstreamAppender(const std::string& name);
可以通過成員函數getQueue()
獲取隊列指針,從而訪問內存中的日誌信息隊列。
隊列的類型爲std::queue<std::string> _queue;
Category
-
category是日誌記錄活動的主要承擔者。負責接收信息並記錄。
-
log4cpp中有一個總是可用並實例化好的Category,即根Category。使用log4cpp::Category::getRoot()可以得到根Category的引用。在大多數情況下,一個應用程序只需要一個日誌種類(Category),但是有時也會用到多個Category,此時可以使用根Category的getInstance方法來得到子Category。
-
注意category類的構造函數是私有成員寒素,因此只能 通過getInstance方法或getRoot方法獲取對象的引用,而不能直接創建對象。
-
通過category類的成員函數setPriority設置優先級敏感度,低於該優先級的日誌信息將不被記錄。
log4cpp優先級一覽,取值越小優先級越高:
typedef enum {EMERG = 0,
FATAL = 0,
ALERT = 100,
CRIT = 200,
ERROR = 300,
WARN = 400,
NOTICE = 500,
INFO = 600,
DEBUG = 700,
NOTSET = 800
} PriorityLevel;
自定義日誌類
將上述過程封裝,即可得到自己的日誌類
/*採用單例模式設計,包含兩個category對象,一個負責輸出到屏幕的信息,一個負責記錄到日誌的信息,通過設置優先級差別,可以實現所有信息都記錄在日誌中,遇到error及以上的信息時打印到屏幕上*/
class MyLog
{
private:
MyLog(bool b)
{
outToScreen = b;
}
~MyLog(){}
static MyLog * log;
bool outToScreen;//是否輸出日誌信息到屏幕
static std::string _screenInfo;//屏幕日誌信息
static std::string _logName;//文件日誌名稱
static log4cpp::Category& logCat;//日誌文件記錄對象
static log4cpp::Category& coutCat;//屏幕信息輸出對象
static log4cpp::FileAppender* logFile;//文件日誌輸入
static log4cpp::OstreamAppender* logScreen;//屏幕日誌輸入
static log4cpp::Priority::PriorityLevel logPri;//文件日誌優先級
static log4cpp::Priority::PriorityLevel coutPri;//屏幕日誌優先級
static log4cpp::PatternLayout* logLayout;//日誌佈局
static log4cpp::PatternLayout* screenLayout;//屏幕布局
public:
//獲取日誌函數,默認參數選擇是否輸出到屏幕
static MyLog* getLog(bool toScreen = true,std::string coutName ="screenInfo",std::string logName = "log"){
if(MyLog::log == NULL)
{
MyLog::log = new MyLog(toScreen);
}
MyLog::log->outToScreen = toScreen;
MyLog::_logName = logName;
MyLog::_screenInfo = coutName;
logScreen = new log4cpp::OstreamAppender("logScreen",&std::cout);
logFile = new log4cpp::FileAppender("logFile",MyLog::_logName);
//設置佈局
MyLog::logLayout = new log4cpp::PatternLayout();
MyLog::screenLayout = new log4cpp::PatternLayout();
logLayout->setConversionPattern("%d{%Y/%m/%d,%H:%M:%S} -- %p %c: %m%n");
screenLayout->setConversionPattern("%d{%Y/%m/%d %H:%M:%S} -- %p %c: %m%n");
MyLog::logScreen->setLayout(screenLayout);
MyLog::logFile->setLayout(logLayout);
//追加到目錄
MyLog::logCat.addAppender(MyLog::logFile);
MyLog::coutCat.addAppender(MyLog::logScreen);
//設置優先級
MyLog::logCat.setPriority(MyLog::logPri);
MyLog::coutCat.setPriority(MyLog::coutPri);
return MyLog::log;
}
//銷燬日誌對象
static void destoryLog()
{
log4cpp::Category::shutdown();
delete MyLog::log;
}
//設置日誌記錄優先級
static void setPri(log4cpp::Priority::PriorityLevel coutLevel,log4cpp::Priority::PriorityLevel logLevel)
{
MyLog::logPri = logLevel;
MyLog::coutPri = coutLevel;
MyLog::logCat.setPriority(MyLog::logPri);
MyLog::coutCat.setPriority(MyLog::coutPri);
}
//記錄日誌,調用時填入參數__FILE__, __LINE__ ,__FUNCTION__來記錄文件名,函數名和行數
void warn(const char * msg,const char *filename = __FILE__,int line = __LINE__,const char *function = "warn")
{
char info[4096] = {0};
sprintf(info,"\nIn file %s,line %d,function %s:%s",filename,line,function,msg);
if(this->outToScreen)
{
logCat.warn(info);
coutCat.warn(info);
}
else
{
logCat.warn(info);
}
}
void error(const char * msg,const char *filename = __FILE__,int line = __LINE__,const char *function = "error")
{
char info[4096] = {0};
sprintf(info,"\nIn file %s,line %d,function %s:%s",filename,line,function,msg);
if(this->outToScreen)
{
logCat.error(info);
coutCat.error(info);
}
else
{
logCat.error(info);
}
}
void debug(const char * msg,const char *filename = __FILE__,int line = __LINE__,const char *function = "debug")
{
char info[4096] = {0};
sprintf(info,"\nIn file %s,line %d,function %s:%s",filename,line,function,msg);
if(this->outToScreen)
{
logCat.debug(info);
coutCat.debug(info);
}
else
{
logCat.debug(info);
}
}
void info(const char * msg,const char *filename = __FILE__,int line = __LINE__,const char *function = "info")
{
char info[4096] = {0};
sprintf(info,"\nIn file %s,line %d,function %s:%s",filename,line,function,msg);
if(this->outToScreen)
{
logCat.info(info);
coutCat.info(info);
}
else
{
logCat.info(info);
}
}
};
//初始化各個靜態成員
MyLog* MyLog::log = NULL;
std::string MyLog::_screenInfo = "screenInfo";
std::string MyLog::_logName = "log";
log4cpp::Category& root = log4cpp::Category::getRoot();
log4cpp::Category& MyLog::logCat = root.getInstance(MyLog::_logName);
log4cpp::Category& MyLog::coutCat = root.getInstance(MyLog::_screenInfo);
log4cpp::Priority::PriorityLevel MyLog::coutPri = log4cpp::Priority::INFO;
log4cpp::Priority::PriorityLevel MyLog::logPri = log4cpp::Priority::NOTSET;
log4cpp::PatternLayout* MyLog::logLayout = NULL;
log4cpp::PatternLayout* MyLog::screenLayout = NULL;
log4cpp::FileAppender* MyLog::logFile = NULL;//文件日誌輸入
log4cpp::OstreamAppender* MyLog::logScreen = NULL;//屏幕日誌輸入