log4cxx 的編譯安裝過程和使用

shaderdx發表於2014-11-22

簡介

log4cxx是Java社群著名的log4j的c++移植版,用於為C++程式提供日誌功能,以便開發者對目標程式進行除錯和審計,log4cxx是apache軟體基金會的開源專案,基於APR實現跨平臺支援。一個良好的日誌系統不管是開發、除錯和維護,對一個專案來說是多麼的重要,我想做過開發的同學深知這點。我用過的日誌框架比較少,所以在這裡不做與其它日誌框架的比較,類似的日誌框架還有GLog、boost log,如果有興趣可以去研究一下。

其實剛工作時,9c的pass9專案中就需要日誌系統,只是那時候還不知道log4cxx這個好東西,只能自己動手實現了一個簡單,主要是用到了c函式的可變引數,值得注意的是執行緒安全的問題,所以在寫檔案時要加互斥鎖,為了提高效能,還需注意檔案塊大小問題,當然了,功能特別簡單,只有控制檯和檔案輸出,有等級、時間、執行緒號、檔名、行號、函式名這些輸出。

下載與安裝

環境:linux和windows
依賴:apr、apr-util
apr、apr-util下載地址:http://apr.apache.org/download.cgi
下面介紹下載原始碼以及編譯過程
1. 下載 log4cxx 以及 apr 和 apr-util 原始碼:
從上面的下載地址中下載如下三個檔案;

image

 

2. 將 3 個壓縮包解壓到同一個目錄.

image

 

3. 將 apr-1.2.11 重新命名為 apr, 將 apr-util-1.2.10 重新命名為 apr-util.

image

最終如下:

image

 

4. 使用 cmd, 切換至 apache-log4cxx-0.10.0 目錄, 執行 configure.bat

image

 

5. 在與第 4  步同一目錄下的 cmd 中, 執行 configure-aprutil.bat

1)和2)兩個步驟中選擇一個執行就行了

1) 此時, 如果環境變數中沒有 sed 程式, 則會出現:

image

此時應該:

手動修改 apr-util\include\apu.hw 裡的內容

#define APU_HAVE_APR_ICONV 1

改為

#define APU_HAVE_APR_ICONV 0

2) 如果系統可以找到 sed 程式, 那麼會出現:

image

 

6. 開啟 apache-log4cxx-0.10.0\projects 下的 log4cxx.dsw

image

開啟工程會提示轉換:

image

 

7. 將 log4cxx 設定為啟動項.

image

 

8. 開始編譯. 快捷鍵 F7.

image

 

9.

1) 出現

'4>D:\log4cxx-src\apache-log4cxx-0.10.0\src\main\include\log4cxx/spi/loggingevent.h(155): error C2252: 只能在名稱空間範圍內顯式例項化模板' 錯誤.

a) 雙擊 "輸出" 視窗中的錯誤行, 此時會在 "程式碼視窗" 中出現錯誤的位置.

b) 選擇 LOG4CXX_LIST_DEF, 按鍵盤 F12, 此時會跳轉到該巨集的定義

c) 將

#define LOG4CXX_LIST_DEF(N, T) \
template class LOG4CXX_EXPORT std::allocator<T>; \
template class LOG4CXX_EXPORT std::vector<T>; \
typedef std::vector<T> N

替換為:
#define LOG4CXX_LIST_DEF(N, T) \
typedef std::vector<T> N

image

 

2) 出現

'2>network_io\unix\multicast.c(137): error C2079: “mip”使用未定義的 struct“group_source_req"' 等錯誤.

image

雙擊第一行出錯輸出, 將 136 和 148 行的 #if MCAST_JOIN_SOURCE_GROUP 註釋, 替換為 #if defined (group_source_req)

 

image

 

3) 出現

'4>..\src\main\cpp\stringhelper.cpp(64): error C2039: “insert_iterator”: 不是“std”的成員' 等錯誤.

image

在該 .cpp 中(stringhelper.cpp) 加入標頭檔案 <iterator>:

image

 

4) 出現

'無法解析的外部符號 xxx' 等錯誤.

image

將 apr, aprutil, xml 新增至 log4cxx 的引用中.

image

 

10. 選擇重新生成解決方案. 否則可能會造成 '無法開啟 apr_iconv.h' 錯誤. 個人懷疑是第 5 步中, configure-aprutil.bat 指令碼中的 sed 命令沒有正確更新檔案的時間所致.

image

編譯成功後:

image

 

下面介紹其使用方法:

1.優先順序

Logger的語法:               

log4j.rootLogger = [ level ] , appenderName, appenderName, …           其中,level 是日誌記錄的優先順序,分為OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定義的級別。Log4j建議只使用四個級別,優先順序從高到低分別是ERROR、WARN、INFO、DEBUG。通過在這裡定義的級別,可以控制到應用程式中相應級別的日誌資訊的開關。比如這裡定義了INFO級別,則應用程式中所有DEBUG級別的日誌資訊將不被列印出來。 appenderName名字任意,用來標示日誌資訊輸出到哪裡,可以同時指定多個。 

2.輸出目的地

Appender的語法:               

log4j.appender.appenderName = fully.qualified.name.of.appender.class          log4j.appender.appenderName.option1 = value1             …                 log4j.appender.appenderName.option = valueN             其中,Log4j提供的appender有以下幾種:             org.apache.log4j.ConsoleAppender 控制檯             org.apache.log4j.FileAppender 檔案              org.apache.log4j.DailyRollingFileAppender 每天產生一個日誌檔案           org.apache.log4j.RollingFileAppender 檔案大小到達指定尺寸的時候產生一個新的檔案         org.apache.log4j.WriterAppender 將日誌資訊以流格式傳送到任意指定的地方

3.輸出格式

log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class         log4j.appender.appenderName.layout.option1 = value1            …                 log4j.appender.appenderName.layout.option = valueN           Log4j提供的Layout有以下幾種:             org.apache.log4j.HTMLLayout 以HTML表格形式佈局            org.apache.log4j.PatternLayout 可以靈活地指定佈局模式            org.apache.log4j.SimpleLayout 包含日誌資訊的級別和資訊字串           org.apache.log4j.TTCCLayout 包含日誌產生的時間、執行緒、類別等等資訊           Log4j採用類似C語言中的printf函式的列印格式格式化日誌資訊,列印引數如下: %m 輸出程式碼中指定的訊息               %p 輸出優先順序,即DEBUG,INFO,WARN,ERROR,FATAL            %r 輸出自應用啟動到輸出該log資訊耗費的毫秒數             %c 輸出所屬的類目,通常就是所在類的全名             %t 輸出產生該日誌事件的執行緒名              %n 輸出一個回車換行符,Windows平臺為“/r/n”,Unix平臺為“/n”           %d 輸出日誌時間點的日期或時間,預設格式為ISO8601,也可以在其後指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},輸出2008年11月14日 15:16:17,890  %l 輸出日誌事件的發生位置,包括類目名、發生的執行緒,以及在程式碼中的行數。

以下只講怎樣使用。

一、建測試工程:testlog4cxx,直接選控制檯應用程式,    在配置屬性頁中,選C/C++,常規,在附加包含目錄中加入“./”;選連結器,常規,在附加庫目錄中加入"./",點選中常規下面的輸入,在附加依賴項中加入“log4cxx/log4cxx.lib”

二、在測試工程目錄下加入log4cxx檔案包

三、加入include標頭檔案:

#include <log4cxx/logger.h>

#include <log4cxx/propertyconfigurator.h>

四、定義配置檔案log4cxx.properties的位置

log4cxx::PropertyConfigurator::configure("log4cxx.properties");

此處的作用為定義配置檔案的位置,如果想讓程式在D盤根目錄下載入配置檔案,可以如下定義:

log4cxx::PropertyConfigurator::configure("D:\\log4cxx.properties");

五、定義不同的列印級別

此在配置檔案中log4cxx.properties將會有所定義。假設有這樣的場景,你必須使用別人提供一個DLL用來獲取介面,此DLL中使用了log4cxx日誌,然後你又想在自己的工程中同樣引入log4cxx日誌,為了區分你的日誌和別人的日誌,你該如何做呢,假設別人使用的為log4j.rootLogger頂級介面。(預設情況下,所有的日誌都會在頂級介面對應的日誌檔案中進行記錄)

這樣一個設定可使得頂級介面與子介面分離:

# 遮蔽gather的Appender繼承 
log4j.additivity.gather = false

以下配置假設引用的DLL庫使用了log4cxx的根介面,並且輸出兩種級別的檔案,一種為error,一種為debug

# 設定root logger為DEBUG級別,使用了兩個Appender log4j.rootLogger=DEBUG, COM, ERR

# 設定sample logger為DEBUG級別 log4j.logger.sample = DEBUG, sample 

# 遮蔽sample的Appender繼承 log4j.additivity.sample = false

log4j.logger.sample.error = WARN, sample.error #log4j.additivity.sample.error = false

log4j.appender.COM=org.apache.log4j.RollingFileAppender log4j.appender.COM.File=../log/dll_output.log log4j.appender.COM.Append=false log4j.appender.COM.MaxFileSize=3MB log4j.appender.COM.MaxBackupIndex=10 log4j.appender.COM.ImmediateFlush=true log4j.appender.COM.layout=org.apache.log4j.PatternLayout log4j.appender.COM.layout.ConversionPattern=%d %-5p %m%n

log4j.appender.ERR=org.apache.log4j.RollingFileAppender log4j.appender.ERR.File=../log/dll_error.log log4j.appender.ERR.Append=false log4j.appender.ERR.Threshold=ERROR log4j.appender.ERR.MaxFileSize=3MB log4j.appender.ERR.MaxBackupIndex=10 log4j.appender.ERR.layout=org.apache.log4j.PatternLayout log4j.appender.ERR.layout.ConversionPattern=%d %-5p %m%n

log4j.appender.sample=org.apache.log4j.RollingFileAppender log4j.appender.sample.File=../log/sample_output.log log4j.appender.sample.Append=false log4j.appender.sample.MaxFileSize=10MB log4j.appender.sample.MaxBackupIndex=10 log4j.appender.sample.ImmediateFlush=true log4j.appender.sample.layout=org.apache.log4j.PatternLayout log4j.appender.sample.layout.ConversionPattern=%d %-5p %m%n

log4j.appender.sample.error=org.apache.log4j.RollingFileAppender log4j.appender.sample.error.File=../log/sample_error.log log4j.appender.sample.error.Append=false log4j.appender.sample.error.MaxFileSize=3MB log4j.appender.sample.error.MaxBackupIndex=10 log4j.appender.sample.error.ImmediateFlush=true log4j.appender.sample.error.layout=org.apache.log4j.PatternLayout log4j.appender.sample.error.layout.ConversionPattern=%d %-5p %m%n

 

六、後臺列印的日誌與控制檯顯示的級別不同(一般控制檯列印比較慢,顯示的儘可能少,後臺則儘可能詳盡)

使用log4j.appender.ca.Threshold=WARN可達到控制檯顯示與後臺列印不相同,如下一個配置檔案設定:

# 設定root logger為DEBUG級別,使用了兩個Appender log4j.rootLogger=DEBUG, server, ca

# 設定kline logger為DEBUG級別 log4j.logger.kline = DEBUG, kline, ca 

# 遮蔽kline的Appender繼承 log4j.additivity.kline = false

log4j.logger.kline.error = WARN, kline.error, ca #log4j.additivity.kline.error = false

log4j.appender.server=org.apache.log4j.RollingFileAppender log4j.appender.server.File=../log/server_output.log log4j.appender.server.Append=false log4j.appender.server.MaxFileSize=3MB log4j.appender.server.MaxBackupIndex=10 log4j.appender.server.ImmediateFlush=true log4j.appender.server.layout=org.apache.log4j.PatternLayout log4j.appender.server.layout.ConversionPattern=%d %-5p %m%n

log4j.appender.server.error=org.apache.log4j.RollingFileAppender log4j.appender.server.error.File=../log/server_error.log log4j.appender.server.error.Append=false log4j.appender.server.error.Threshold=ERROR log4j.appender.server.error.MaxFileSize=3MB log4j.appender.server.error.MaxBackupIndex=10 log4j.appender.server.error.layout=org.apache.log4j.PatternLayout log4j.appender.server.error.layout.ConversionPattern=%d %-5p %m%n

log4j.appender.kline=org.apache.log4j.RollingFileAppender log4j.appender.kline.File=../log/kline_output.log log4j.appender.kline.Append=false log4j.appender.kline.MaxFileSize=10MB log4j.appender.kline.MaxBackupIndex=10 log4j.appender.kline.ImmediateFlush=true log4j.appender.kline.layout=org.apache.log4j.PatternLayout log4j.appender.kline.layout.ConversionPattern=%d %-5p %m%n

log4j.appender.kline.error=org.apache.log4j.RollingFileAppender log4j.appender.kline.error.File=../log/kline_error.log log4j.appender.kline.error.Append=false log4j.appender.kline.error.MaxFileSize=3MB log4j.appender.kline.error.MaxBackupIndex=10 log4j.appender.kline.error.ImmediateFlush=true log4j.appender.kline.error.layout=org.apache.log4j.PatternLayout log4j.appender.kline.error.layout.ConversionPattern=%d %-5p %m%n

#對Appender ca進行設定: # 這是一個控制檯型別的Appender #  輸出格式(layout)為PatternLayout log4j.appender.ca=org.apache.log4j.ConsoleAppender log4j.appender.ca.Threshold=WARN log4j.appender.ca.layout=org.apache.log4j.PatternLayout log4j.appender.ca.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %.16c - %m%n

 

七、列印日誌的方法

LOG4CXX_INFO(log4cxx::Logger::getLogger("hello"), "你好,log4cxx!");

以上列印方法可行,不過寫起來比較麻煩,建議進行一番包裝。

如下程式碼所示:

/******************************************************************************
  author  : luhx
  purpose : 日誌系統,以巨集log_XXX開頭,封裝log4cxx日誌。
*******************************************************************************/
#pragma once
 
 
#include "log4cxx/logger.h"
#include "log4cxx/propertyconfigurator.h"
/*
日誌類
*/
 
#include<string>
using namespace std;
 
class CServerlog;
extern CServerlog* g_serverlog;
 
#define log_debug(pszFormat, ...)    g_serverlog->WriteFormatDebugLog( pszFormat, __VA_ARGS__)
#define log_info(pszFormat, ...)    g_serverlog->WriteFormatInfoLog( pszFormat, __VA_ARGS__)
#define log_warn(pszFormat, ...)    g_serverlog->WriteFormatWarnLog( pszFormat, __VA_ARGS__)
#define log_error(pszFormat, ...)    g_serverlog->WriteFormatErrorLog( pszFormat, __VA_ARGS__)
 
class CServerlog
{
public:
    CServerlog();
    ~CServerlog();
 
    void WriteFormatDebugLog(char* lpszFormat, ...);
    void WriteFormatInfoLog(char* lpszFormat, ...);    
 
    void WriteFormatWarnLog(char* lpszFormat, ...);
    void WriteFormatErrorLog(char* lpszFormat, ...);
 
protected:
 
    log4cxx::LoggerPtr infologger;
    log4cxx::LoggerPtr errorlogger;
};

 

 

#include <Windows.h> 
#include <tchar.h> 
#include <stdio.h> 
#include "serverlog.h"
 
 
CServerlog* g_serverlog = new CServerlog();
 
CServerlog::CServerlog()
{
    log4cxx::PropertyConfigurator::configure("log4cxx.properties");
    infologger = (log4cxx::Logger::getLogger("server"));
    errorlogger = (log4cxx::Logger::getLogger("server.error"));
}
 
CServerlog::~CServerlog()
{
}
 
 
void CServerlog::WriteFormatDebugLog(char* lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    CHAR szBuffer[1024];
    vsprintf(szBuffer, lpszFormat, args);
    va_end(args);
    infologger->debug(szBuffer);
}
 
 
void CServerlog::WriteFormatInfoLog(char* lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    CHAR szBuffer[1024];
    vsprintf(szBuffer, lpszFormat, args);
    va_end(args);
    infologger->info(szBuffer);
}
 
void CServerlog::WriteFormatWarnLog(char* lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    CHAR szBuffer[1024];
    vsprintf(szBuffer, lpszFormat, args);
    va_end(args);
 
    errorlogger->warn(szBuffer);
}
 
void CServerlog::WriteFormatErrorLog(char* lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    CHAR szBuffer[1024];
    vsprintf(szBuffer, lpszFormat, args);
    va_end(args);
    errorlogger->error(szBuffer);
}
 

 

另外,發現列印出的日誌在某些機器上不支援中文, 中文顯示為亂碼,通過在配置檔案中增加如下配置項即可解決:

log4j.appender.sample.encoding=UTF-8,這裡,sample即為你的日誌類.


前面已經說完了怎樣使用log4cxx進行日誌記錄,今天發現問題稍有點複雜。原因是系統中用到的一個dll已經使用了log4cxx。我們在開發的過程中也想使用log4cxx,但不想與DLL中的日誌寫到同一個檔案中,問題就來了,怎樣區別列印到不同的檔案中呢,DLL中採用的為應該為getRootLogger的方式。

這個網址中的文章很好,解決方案主要參考此文:http://www.open-open.com/doc/view/4bf2bd4f517c4044b1f7d4e2d22eccaf

log4cxx主要是由三部分組成:loggers, appenders和layouts.這三個主要組成部分,協同協作能夠使我們根據實際的需要進行日誌的輸出,它們規定了日誌資訊的型別和級別,控制應用程式執行時的日誌資訊組成方式以及日誌記錄的輸出方式。

Logger是Log4cxx的核心,Logger具有繼承關係的層次結構,最頂層為RootLogger,每個Logger都有一個級別(Level),每個Logger可以附加多個Appender.Appender代表了日誌輸出的目標,對於每一個Appender可以通過Layout進行格式配置。

Logger命名:保持Logger與其所在的類的名稱一致的方法是目前所知的最好的命名策略。

一個關於Logger繼承的例子:

#設定root logger 為DEBUG級別,使用了ca, fa兩個Appender

log4j.rootLogger = DEBUG, ca, fa

#設定list logger

log4j.logger.list = DEBUG, listApp

#設定list.voice logger

log4j.logger.list.voice= INFO, listVoice, listVoiceBak

list是list.voice的父親Logger, list.voice是list的兒子Logger. rootLogger是根Logger, 在此例中list和list.voice兩個Logger又都繼承了rootLogger,不過只是繼承了Appenders,Level並沒有繼。(此解有待驗證)

 

還是看配置檔案:log4cxx.properties

# 設定root logger為DEBUG級別,使用了ca和fa兩個Appender 
log4j.rootLogger=DEBUG, fa, ca 
# 設定.listApp logger,遮蔽logger-list的LEVEL繼承 
log4j.logger.listApp=DEBUG, listApp

# 設定.listApp2 logger,遮蔽logger-list的LEVEL繼承 
log4j.logger.listApp2=DEBUG, listApp2

#遮蔽listApp的Appender繼承 
log4j.additivity.listApp=false

#遮蔽listApp2的Appender繼承 
log4j.additivity.listApp2=false

 

#對Appender fa進行設定:# 這是一個檔案型別的Appender, 
log4j.appender.fa=org.apache.log4j.FileAppender 
log4j.appender.fa.ImmediateFlush=true

# 其輸出檔案(File)為 Runlog.log, 
log4j.appender.fa.File=Runlog.log 
log4j.appender.fa.layout=org.apache.log4j.PatternLayout

# 輸出方式(Append)為覆蓋方式, 
log4j.appender.fa.Append=false

# 輸出格式(layout)為PatternLayout 
log4j.appender.fa.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %.16c - %m%n

 

 

#對Appender ca進行設定:

# 這是一個控制檯型別的Appender 
log4j.appender.ca=org.apache.log4j.ConsoleAppender

# 輸出格式(layout)為PatternLayout 
log4j.appender.ca.layout=org.apache.log4j.PatternLayout 
log4j.appender.ca.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %.16c - %m%n

 

#對Appender listApp進行設定# 這是一個檔案型別的Appender, 
log4j.appender.listApp=org.apache.log4j.FileAppender

#立即寫入日誌檔案 
log4j.appender.listApp.ImmediateFlush=true

# 其輸出檔案(File)為 listApp.log 
log4j.appender.listApp.File=listApp.log

# 輸出方式(Append)為覆蓋方式, 
log4j.appender.listApp.Append=false 
log4j.appender.listApp.layout=org.apache.log4j.PatternLayout 
log4j.appender.listApp.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %.16c - %m%n

 

#對Appender listApp2進行設定 
log4j.appender.listApp2=org.apache.log4j.FileAppender 
log4j.appender.listApp2.ImmediateFlush=true 
log4j.appender.listApp2.File=listApp2.log 
log4j.appender.listApp2.Append=false 
log4j.appender.listApp2.layout=org.apache.log4j.PatternLayout 
log4j.appender.listApp2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %.16c - %m%n

 

 

以下為實現程式碼:

// testlog4cxx.cpp : 定義控制檯應用程式的入口點。
//
 
#include "stdafx.h"
#include <iostream>
using namespace std;
#include <log4cxx/logger.h>
#include <log4cxx/propertyconfigurator.h>
 
using namespace log4cxx;
using namespace log4cxx::helpers;
 
int _tmain(int argc, _TCHAR* argv[])
{
 
    PropertyConfigurator::configure("log4cxx.properties");
 
    LOG4CXX_INFO(log4cxx::Logger::getLogger("list5"), "list,log4cxx!");
    LOG4CXX_INFO(log4cxx::Logger::getLogger("list5"), "list,mylog4cxx!");
 
    LOG4CXX_INFO(log4cxx::Logger::getLogger("listApp"), "listApp,log4cxx!");
 
    LOG4CXX_INFO(log4cxx::Logger::getLogger("listApp2"), "listApp2, hello world!");
 
    return 0;
}

 

將log4cxx.properties與生成後的testlog4cxx.exe放置同一目錄,執行後發現目錄下生成三個檔案,分別為:他們的檔名及內容分別為

Runlog.log:

2012-04-06 21:13:56 INFO  list5 - list,log4cxx! 
2012-04-06 21:13:56 INFO  list5 - list,mylog4cxx!

 

listApp.log:

2012-04-06 21:13:56 INFO  listApp - listApp,log4cxx! 
2012-04-06 21:13:56 INFO  listApp - listApp TimeRsp.nErrno: 123

 

listApp2.log:

2012-04-06 21:13:56 INFO  listApp2 - listApp2, hello world!

 

由此可以看出,所有Logger預設繼承於rootLogger的Appender,一般情況下(Level級別允許)總會在Runlog.log中打出,若想有所例外,需用配置log4j.additivity.AppenderName = false將預設繼承去除。




相關文章