log4cpp是一个开源的C++日志管理库,可以通过它来记录程序运行过程中产生的各种信息。也可以进行再包装实现个人自定义的日志类。
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库中主要分为三大类:Category(种类)、Appender(附加目的地)和Layout(布局)。
应用时的大致流程:
简单示例
#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对象规定了日志输出的内容格式,创建后需要和appender对象绑定生效。
需要注意的是,一个布局对象只能绑定一个appender对象。
比较常用的布局有两种:log4cpp::BasicLayout
和log4cpp::PatternLayout
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布局支持通过类似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对象指定日志输出到什么地方去,创建后需要和category对象绑定才能生效。
一个apender只能和一个category对象绑定,但是一个category对象可以有多个appnder,可以输出到多个位置。
常用的appender类如下:
log4cpp::FileAppender // 输出到文件
log4cpp::RollingFileAppender // 输出到回卷文件,即当文件到达某个大小后回卷
log4cpp::OstreamAppender // 输出到一个ostream类
log4cpp::StringQueueAppender // 输出到内存队列
构造函数如下:
FileAppender(conststd::string& name, conststd::string& fileName, bool append = true, mode_tmode = 00644);
一般仅使用前两个参数,即“名称”( FileAppender对象的名称)和“日志文件名”(要写入日志的文件名)。第三个参数指示是否在日志文件写满后继续记入日志,还是清空原日志文件再记录。第四个参数说明文件的打开方式。
FileAppender对象创建完毕后,调用成员函数setLayout来绑定一个布局对象。
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* osAppender = newlog4cpp::OstreamAppender("osAppender", &cout);
第一个参数是OstreamAppender对象的名称,第二个参数指定它关联的流的指针。
log4cpp::StringQueueAppender 可以将日志信息保存到内存队列中,在程序运行结束后再进行处理,主要用于记录多线程程序或者实时程序的日志,防止输出操作引起IO中断导致线程挂起,影响效率。
其构造函数如下:
log4cpp::OstreamAppender(const std::string& name);
可以通过成员函数getQueue()
获取队列指针,从而访问内存中的日志信息队列。
队列的类型为std::queue
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::_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__);