C++编程之自定义日志类 ——log4cpp使用详解

C++编程之自定义日志类 ——log4cpp使用详解

      • log4cpp简介与安装
        • log4cpp安装
        • log4cpp简单介绍
      • layout布局——日志输出格式
        • log4cpp::BasicLayout
        • log4cpp::PatternLayout
      • appender
        • log4cpp::FileAppender
        • log4cpp::RollingFileAppender
        • log4cpp::OstreamAppender
        • log4cpp::StringQueueAppender
      • Category
      • 自定义日志类

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 _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::_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__);

你可能感兴趣的:(C++,Linux)