C++編程之自定義日誌類 ——log4cpp使用詳解

log4cpp簡介與安裝

log4cpp是一個開源的C++日誌管理庫,可以通過它來記錄程序運行過程中產生的各種信息。也可以進行再包裝實現個人自定義的日誌類。

log4cpp安裝

  1. 下載:
    wget https://nchc.dl.sourceforge.net/project/log4cpp/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-1.1.3.tar.gz
  2. 解包
    tar zxvf log4cpp-1.1.3.tar.gz
  3. cd log4cpp
  4. ./configure --with-pthreads
  5. ./configure
  6. make
  7. make install
  8. 添加環境變量
    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
  9. ldconfig -v

安裝好後, 在編譯源碼文件時要加上-llog4cpp -lpthread來鏈接動態庫

log4cpp簡單介紹

log4cpp庫中主要分爲三大類:Category(種類)、Appender(附加目的地)和Layout(佈局)。

  • category類是日誌記錄的主要執行類,它負責寫日誌
  • appender類用來指明目的地,即日誌要寫到什麼地方去
  • layout類指明日誌輸出的格式

應用時的大致流程:

  1. 定義一個layout類對象,確定輸出日誌信息的格式
  2. 定義一個appender類對象,確定日誌輸出到什麼地方,然後把layout對象用setlayout方法綁定一下。
  3. 定義一個catergory對象,與appender類對象綁定
  4. 調用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::BasicLayoutlog4cpp::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;//屏幕日誌輸入

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章