Qter 发表于 2023-5-28 22:31:57

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安装
下载:
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简单介绍
log4cpp库中主要分为三大类:Category(种类)、Appender(附加目的地)和Layout(布局)。

category类是日志记录的主要执行类,它负责写日志
appender类用来指明目的地,即日志要写到什么地方去
layout类指明日志输出的格式
应用时的大致流程:

定义一个layout类对象,确定输出日志信息的格式
定义一个appender类对象,确定日志输出到什么地方,然后把layout对象用setlayout方法绑定一下。
定义一个catergory对象,与appender类对象绑定
调用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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
可以看到整套流程下来还是有点复杂的,可以在后续包装成一个自定义的日志类进行日志记录。

layout布局——日志输出格式
layout对象规定了日志输出的内容格式,创建后需要和appender对象绑定生效。
需要注意的是,一个布局对象只能绑定一个appender对象。
比较常用的布局有两种:log4cpp::BasicLayout和log4cpp::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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
输出的日志格式如下:

1248337987 ERROR: Hello log4cppin a Error Message!

1248337987 WARN: Hello log4cppin a Warning Message!
1
2
3
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
代码示例:

MyLog::screenLayout = new log4cpp::PatternLayout();
screenLayout->setConversionPattern("%d{%Y/%m/%d,%H:%M:%S} -- %p %c: %m%n");
1
2
上述代码表示日志记录的信息依次是“日期(年月日时分秒)-- 优先级 catgory:消息 换行”

appender
appender对象指定日志输出到什么地方去,创建后需要和category对象绑定才能生效。
一个apender只能和一个category对象绑定,但是一个category对象可以有多个appnder,可以输出到多个位置。

常用的appender类如下:

log4cpp::FileAppender                      // 输出到文件

log4cpp::RollingFileAppender         // 输出到回卷文件,即当文件到达某个大小后回卷

log4cpp::OstreamAppender         // 输出到一个ostream类

log4cpp::StringQueueAppender             // 输出到内存队列
1
2
3
4
5
6
7
log4cpp::FileAppender
构造函数如下:

   FileAppender(conststd::string& name, conststd::string& fileName, bool append = true, mode_tmode = 00644);
1
一般仅使用前两个参数,即“名称”( 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);
1
该函数与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::_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 = {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 = {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 = {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 = {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__,可以使用带参数的宏定义
#defineMyLogWARN(msg) MyLog::getLog()->warn(msg,__FILE__,__LINE__,__FUNCTION__);
#defineMyLogINFO(msg) MyLog::getLog()->info(msg,__FILE__,__LINE__,__FUNCTION__);
#defineMyLogERROR(msg) MyLog::getLog()->error(msg,__FILE__,__LINE__,__FUNCTION__);
#defineMyLogDEBUG(msg) MyLog::getLog()->debug(msg,__FILE__,__LINE__,__FUNCTION__);

————————————————
版权声明:本文为CSDN博主「black_kyatu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/black_kyatu/article/details/93399235

页: [1]
查看完整版本: C++编程之自定义日志类 ——log4cpp使用详解