1、log4cpp概述
Log4cpp是一個開源的C++類庫,它提供了C++程序中使用日志和跟蹤調試的功能,它的優點如下:
- 提供應用程序運行上下文,方便跟蹤調試;
- 可擴展的、多種方式記錄日志,包括命令行、文件、回卷文件、內存、syslog服務器、Win事件日志等;
- 可以動態控制日志記錄級別,在效率和功能中進行調整;
- 所有配置可以通過配置文件進行動態調整;
- 多語言支持,包括Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(log4p)等;
類似日志工具:glog、boost.log、spdlog
2、原理
Log4cpp有三個主要的組件:日志類別(Category)、輸出源(Appender)和布局(Layout)。這三種類型的組件一起工作使得系統可以根據信息的類型和級別記錄它們,并且在運行時控制這些信息的輸出格式和位置。
三個組件的介紹:
1)日志類別(Category)含義是:如果配置文件中設置的級別是DEBUG,則任意的log都能打印出來;但如果配置的級別是ERROR,則只有高于ERROR優先級的日志才可以打印出來。
日志的常用優先級:DEBUG < INFO < WARN < ERROR < FATAL
2)輸出源(Appender)用來輸出日志(被layout格式化后)到一些設備上,比如文件、命令行、內存等。也可以定義自己的appender輸出日志信息到別的設備上。log4cpp提供的appender如下:FileAppender 輸出到文件 RollingFileAppender 輸出到回卷文件,即當文件到達某個大小后回卷 ConsoleAppender 輸出到控制臺
3)布局(Layout):顯示樣式PatternLayout表示讓用戶根據類似于C語言printf函數的轉換模式來指定輸出格式
三個組件之間的關系:
- Category和Appender的關系是:多個Appender可以附加到一個Category上,這樣一個日志消息可以同時輸出到多個設備上。
- Appender和Layout的關系是:Layout附加在Appender上,appender調用layout處理完日志消息后,記錄到某個設備上。
3 log4cplus的安裝
log4cplus是開源的,源代碼可在這里找到。下載源代碼壓縮包后解壓,進入主目錄。和大多數autotools工程一樣,順序執行以下命令即可完成安裝。
make
make install
安裝文件將默認安裝到/usr/local,庫文件置于/usr/local/lib,頭文件置于/usr/local/include。
是的,這里介紹的安裝及下面介紹的應用都是基于linux系統。
4 log4cplus的使用
以下是官方提供的“hello, world”的示例程序:
#include
#include
#include
using namespace log4cplus;
int main()
{
BasicConfigurator config;
config.configure();
Logger logger = Logger::getInstance(LOG4CPLUS_TEXT("main"));
LOG4CPLUS_WARN(logger, LOG4CPLUS_TEXT("Hello, World!"));
return 0;
}
程序包含了一些必要的頭文件,編譯時需要鏈接log4cplus庫,將這段代碼保存為 test.cpp,執行以下命令編譯:
編譯后生成 test 可執行文件,運行./test,得到如下輸出:
這個程序使用的是log4cplus內置的默認配置選項,實際使用中一般要自己配置選項,接下來你會看到。
5 log4cplus配置
log4cplus配置就是定義appender, 定義輸出的格式即 layout。以下列出兩種常用配置,以供參考。
配置輸出到控制臺(通常用于前臺程序):
log4cplus.appender.console = log4cplus::ConsoleAppender
log4cplus.appender.console.layout = log4cplus::PatternLayout
log4cplus.appender.console.layout.ConversionPattern = [%D{%m/%d/%y %H:%M:%S,%q} %-5p] - %m%n
配置輸出到文件(通常用于后臺程序):
log4cplus.appender.file = log4cplus::FileAppender
log4cplus.appender.file.File = /var/log/myapp.log
log4cplus.appender.file.MaxFileSize = 10M
log4cplus.appender.file.Append = true
log4cplus.appender.file.layout = log4cplus::PatternLayout
log4cplus.appender.file.layout.ConversionPattern = [%D{%m/%d/%y %H:%M:%S,%q} %-5p] - %m%n
簡單說明一下,配置文件中log4cplus.logger.logmain即定義一個logmain對象,后面跟的兩個字段前一個表示log級別,后一個指定使用的appender,即日志輸出對象。log級別按嚴重程度從低到高依次為TRACE、DEBUG、INFO、WARN、ERROR、FATAL。log4cplus.appender.xxx定義具體的appender屬性,如是控制臺還是文件,進一步配置文件名、文件大小等。
將配置保存到一個配置文件中(如log4cplus.conf),以下你將看到如何使用配置文件。有關更詳細的配置,讀者可自行摸索。
6 log4cplus運用于項目
以上“hello, world”程序只是大概演示log4cplus的用法,實際項目使用要有系統觀念,就是怎樣用才更方便,我們可以再做點封裝。我們可以定義一個全局logger對象,將log4cplus初始化配置放到一個源文件中,重新定義一些簡化的宏置于頭文件,比如筆者就定義了Log.h/Log.cpp兩個文件,代碼如下:
Log.h文件:
#include
#include
using namespace log4cplus;
using namespace log4cplus::helpers;
// global object
extern Logger logger;
// define some macros for simplicity
#define LOG_TRACE(logEvent) LOG4CPLUS_TRACE(logger, logEvent)
#define LOG_DEBUG(logEvent) LOG4CPLUS_DEBUG(logger, logEvent)
#define LOG_INFO(logEvent) LOG4CPLUS_INFO(logger, logEvent)
#define LOG_WARN(logEvent) LOG4CPLUS_WARN(logger, logEvent)
#define LOG_ERROR(logEvent) LOG4CPLUS_ERROR(logger, logEvent)
#define LOG_FATAL(logEvent) LOG4CPLUS_FATAL(logger, logEvent)
extern void InitLogger(bool daemonized);
Log.cpp文件:
#include
#include
#include
#include
#include "Log.h"
Logger logger = Logger::getInstance(LOG4CPLUS_TEXT("logmain"));
void InitLogger(bool daemonized)
{
if (daemonized)
PropertyConfigurator::doConfigure(LOG4CPLUS_TEXT("/your/path/log4cplusd.conf"));
else
PropertyConfigurator::doConfigure(LOG4CPLUS_TEXT("/your/path/log4cplus.conf"));
}
將這兩個文件置于你的項目中,然后在 main 函數中調用 InitLogger() 初始化 log4cplus,再在需要加log的文件中包含Log.h即可。注意InitLogger函數的參數daemonized,該參數表示應用程序是否是守護進程(后臺運行),一般我們開發的應用程序大多是守護進程(linux后臺服務大多是守護進程),但調試的時候會前臺運行,對于守護進程,我們只需要把日志記錄到某個文件中就行了,而對于前臺調試運行,我們就只需要將日志輸出到控制臺,所以這里是一點使用技巧。做到這點我們只需分別提供兩個配置文件即可,"/your/path"就是你放置配置文件的地方,一般可以設為你應用程序部署的目錄下的etc目錄。
至此我們可以使用log4cplus了!以下是實際的日志輸出效果:
[11/05/12 10:28:36,002 INFO ] - TCPDomain - Connect server success!
[11/05/12 10:28:36,002 TRACE] - Session - Thread run.
[11/05/12 10:28:46,006 ERROR] - TCPDomain - SelectRead time out!
[11/05/12 10:28:56,016 ERROR] - TCPDomain - SelectRead time out!
7 log4cplus交叉編譯
對于嵌入式應用 ,有交叉編譯這么一說。以上的介紹是基于PC的,如果你的平臺是嵌入式平臺如arm,則只需編譯鏈接arm平臺的log4cplus庫即可,其它都一樣。對于大多數autotools工程,其交叉編譯方法大致如下:
make
make install
其中--prefix即指定你的安裝目錄,如/opt/log4cplus,--host指定目標平臺,CXX指定你的交叉編譯工具(確保shell環境能找到該工具)。編譯安裝完后可在安裝目錄找到arm版本的庫文件。
8 總結
日志固然好,但也不建議隨意使用,用多了會導致程序性能有所下降,且代碼size增加不少。以上是筆者運用log4cplus的些許經驗,更深一步的原理機制有待進一步探究。
配置文件
2
3 #指定rootCategory的log優先級是ERROR,其Appenders有兩個,分別是console,TESTAppender
4 log4cpp.rootCategory=ERROR, console,TESTAppender
5
6 #-------定義console屬性-------
7
8 #consoleAppender類型:控制臺輸出
9 #下面這三條語句表示控制臺輸出的log輸出的布局按照指定的格式;輸出格式是:[%p] %d{%H:%M:%S.%l} (%c): %m%n
10 log4cpp.appender.console=ConsoleAppender
11 log4cpp.appender.console.layout=PatternLayout
12 log4cpp.appender.console.layout.ConversionPattern=[%p] %d{%H:%M:%S.%l} (%c): %m%n
13
14 #-------定義TESTAppender的屬性-------
15
16 #RollingFileAppender類型:輸出到回卷文件,即文件到達某個大小的時候產生一個新的文件
17 #下面的語句表示文件輸出到指定的log文件,輸出的布局按照指定的格式,輸出的格式是:[%d{%Y-%m-%d %H:%M:%S.%l} - %p] (%c): %m%n
18 log4cpp.appender.TESTAppender=RollingFileAppender
19
20 #當日志文件到達maxFileSize大小時,將會自動滾動
21 log4cpp.appender.TESTAppender.maxFileSize=400000
22
23 #maxBackupIndex指定可以產生的滾動文件的最大數
24 log4cpp.appender.TESTAppender.maxBackupIndex=3
25
26 #fileName指定信息輸出到logs/TESTAppender.txt文件
27 log4cpp.appender.TESTAppender.fileName=logs/TESTAppender.txt
28
29 #PatternLayout 表示可以靈活指定布局模式
30 log4cpp.appender.TESTAppender.layout=PatternLayout
31
32 #append=true 信息追加到上面指定的日志文件中,false表示將信息覆蓋指定文件內容
33 log4cpp.appender.TESTAppender.append=true
34 log4cpp.appender.TESTAppender.layout.ConversionPattern=[%d{%Y-%m-%d %H:%M:%S.%l} - %p] (%c): %m%n
ConversionPattern的參數含義:
%d 輸出日志時間點的日期或時間,可以在其后指定格式,如上%d{%Y-%m-%d %H:%M:%S.%l},輸出類似:2017-02-14 09:25:00.953
%p 優先級,即DEBUG,INFO,WARN,ERROR,FATAL
%c 輸出日志信息所屬的類目,通常就是所在類的全名
%m 輸出log的具體信息
%n 回車換行
自定義封裝
將上述過程封裝,即可得到自己的日志類
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::_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);
}
MyLog::log->outToScreen = toScreen;
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;//屏幕日志輸入
//為避免每次調用都要填寫參數__FILE__,__LINE__和__FUNCTION__,可以使用帶參數的宏定義
#define MyLogWARN(msg) MyLog::getLog()->warn(msg,__FILE__,__LINE__,__FUNCTION__);
#define MyLogINFO(msg) MyLog::getLog()->info(msg,__FILE__,__LINE__,__FUNCTION__);
#define MyLogERROR(msg) MyLog::getLog()->error(msg,__FILE__,__LINE__,__FUNCTION__);
#define MyLogDEBUG(msg) MyLog::getLog()->debug(msg,__FILE__,__LINE__,__FUNCTION__);
發送到遠程服務器
應用程序中SocketAppender配置
前面啟動了LoggingServer,下面說一下需要收集其日志的各個應用程序中配置。
說白了,就是在原來的基礎上加一個SocketAppender,SocketAppender會自動將日志發送給loggingserver。這樣直接在loggingserver上就可以查看所有服務器上的日志信息啦。哥終于不用再擔心你們的運行狀態啦!
配置文件
log4cplus.logger.rollfile=TRACE,R2
log4cplus.appender.STDOUT=log4cplus::ConsoleAppender
log4cplus.appender.STDOUT.layout=log4cplus::PatternLayout
log4cplus.appender.STDOUT.layout.ConversionPattern=@@@[%d %d{%Z %Q}][pthread_id:%t][%l][%-5p][%c{2}] - %m%n
log4cplus.appender.R2=log4cplus::RollingFileAppender
log4cplus.appender.R2.File=./mytest.log
log4cplus.appender.R2.MaxFileSize=4MB
log4cplus.appender.R2.MaxBackupIndex=5
log4cplus.appender.R2.layout=log4cplus::PatternLayout
log4cplus.appender.R2.layout.ConversionPattern=@@@[%d %d{%Z %Q}][pthread_id:%t][%l][%-5p][%c{2}] - %m%n
log4cplus.appender.RemoteServer=log4cplus::SocketAppender
log4cplus.appender.RemoteServer.host=192.168.2.130
log4cplus.appender.RemoteServer.port=9000
還可以輸出到多個server,配置如下:
log4cplus.logger.rollfile=TRACE,R2
log4cplus.appender.STDOUT=log4cplus::ConsoleAppender
log4cplus.appender.STDOUT.layout=log4cplus::PatternLayout
log4cplus.appender.STDOUT.layout.ConversionPattern=@@@[%d %d{%Z %Q}][pthread_id:%t][%l][%-5p][%c{2}] - %m%n
#log4cplus.appender.STDOUT.layout.ConversionPattern=@@@[%l][%-5p] - %m%n
log4cplus.appender.R2=log4cplus::RollingFileAppender
log4cplus.appender.R2.File=/opt/apps/3k/i1client/i1client.log
log4cplus.appender.R2.MaxFileSize=10000KB
log4cplus.appender.R2.MaxBackupIndex=100
log4cplus.appender.R2.layout=log4cplus::PatternLayout
log4cplus.appender.R2.layout.ConversionPattern=@@@[%d %d{%Z %Q}][pthread_id:%t][%l][%-5p][%c{2}] - %m%n
#log4cplus.appender.R2.layout.ConversionPattern=@@@[%l][%-5p] - %m%n
log4cplus.appender.RemoteServer=log4cplus::SocketAppender
log4cplus.appender.RemoteServer.host=192.168.2.130
log4cplus.appender.RemoteServer.port=9000
log4cplus.appender.RemoteServer2=log4cplus::SocketAppender
log4cplus.appender.RemoteServer2.host=192.168.2.131
log4cplus.appender.RemoteServer2.port=9001
測試:
在Log4cplus的源碼包中,有一個loggingServer目錄,該目錄中實現了一個LoggingServer。
在編譯Log4cplus時,會自動編譯該目錄,在目錄中生成loggingServer可執行文件,當然可以自己make(需要依賴log4cplus庫)。
loggingServer使用方式如下:
./loggingserver 9000 log4cplus.properties
9000表示監聽的端口號(不需要地址,默認監聽本機地址)
log4cplus.properties是一個log4cplus的配置文件,和普通的log4cplus配置文件相同,loggingserver收到各個socket發來的日志后,根據配置文件信息,將其寫入文件。
服務端配置文件如下
log4cplus.rootLogger=TRACE, STDOUT, ALL_MSGS
#Appender輸出位置類型:控制臺輸出
log4cplus.appender.STDOUT=log4cplus::ConsoleAppender
#日志輸出格式 有詞法分析功能的模式布局器
log4cplus.appender.STDOUT.layout=log4cplus::PatternLayout
log4cplus.appender.STDOUT.layout.ConversionPattern=@@@[%d %d{%Z %Q}][pthread_id:%t][%l][%-5p][%c{2}] - %m%n
#Appender輸出位置類型有:文件輸出 設置日志追加到文件尾
log4cplus.appender.ALL_MSGS=log4cplus::RollingFileAppender
#設置輸出日志路徑
log4cplus.appender.ALL_MSGS.File=log/i1client.log
#設置日志文件大小
log4cplus.appender.ALL_MSGS.MaxFileSize=1024KB
#設置生成日志最大個數
log4cplus.appender.ALL_MSGS.MaxBackupIndex=20
#日志輸出格式 有詞法分析功能的模式布局器
log4cplus.appender.ALL_MSGS.layout=log4cplus::PatternLayout
log4cplus.appender.ALL_MSGS.layout.ConversionPattern=@@@[%d %d{%Z %Q}][pthread_id:%t][%l][%-5p][%c{2}] - %m%n
-
服務器
+關注
關注
12文章
9123瀏覽量
85324 -
內存
+關注
關注
8文章
3019瀏覽量
74003 -
程序
+關注
關注
117文章
3785瀏覽量
81004 -
C++
+關注
關注
22文章
2108瀏覽量
73621 -
日志
+關注
關注
0文章
138瀏覽量
10639
發布評論請先 登錄
相關推薦
評論