跳至主要內容

log4cpp库的使用

张威大约 11 分钟c/c++日志

log4cpp的使用

log4cpp开发文档:Main Page (sourceforge.net)open in new window

安装

Log4cpp的主页为:Log library for C++ download | SourceForge.netopen in new window

  1. 下载log4cpp(如log4cpp-1.1.1.tar.gz)

  2. 先将log4cpp-1.1.1.tar.gz拖入用户主目录(~)

  3. 依次执行下面的步骤

    $ cd ~/log4cpp/
    $ ./configure
    $ make
    $ make check
    $ sudo make install
    //这里已经安装成功.
    

默认lib库路径是 : /usr/local/lib/

image-20240411204317307
image-20240411204317307

默认头文件的位置/usr/local/include/log4cpp

image-20240411204413778
image-20240411204413778

make出现报错 ISO C++17 does not allow dynamic exception specifications

image-20240411203054816
image-20240411203054816

解决方法

  1. 安装低版本的g++及gcc(如g++-9及gcc-9)
sudo apt-get install gcc-9
sudo apt-get install g++-9
  1. 设置默认编辑器
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 50 --slave /usr/bin/g++ g++ /usr/bin/g++-9

log4cpp基本结构

log4cpp从大的结构上分为,。除此以外还提供了一些帮助类,简化我们对日志的代码设置。

日志类别(Category)

Category,日志输出主体类,提供了输出各种日志级别的方法,并且能够设置类别优先级,低于此优先级的类都不再输出,其他分类都是root分类的子分类,有点类似于java的单继承结构,子分类的输出同时会输出到父分类中

// 根分类root
log4cpp::Category& root = log4cpp::Category::getRoot();
// 子分类subCat1
log4cpp::Category& sub1 = log4cpp::Category::getInstance("sub1");	//用于%c时输出Category的类别“sub1”
 
root.error("test root error");
root.warn("test root error");
root.info("test root error");
 
sub1.fatal("test sub1 error");
sub1.notice("test sub1 error");
sub1.debug("test sub1 error");

日志追加器(Appender)

Appender,主要是用来确定日志输出行为,例如。类Appender是由Category来加载的,,一个Appender也可以被多个Category加载,但是有些Appender类没有设置互斥,日志的多线程支持主要是在Category里做的处理,因此不要使用一个Appender加载到多个Category中。

// log4cpp::OstreamAppender:输出到输出流中(输出到控制台上)
// log4cpp::FileAppender:输出到文件中
// log4cpp::RollingFileAppender:输出到文件中并且能够设置文件最大超过多少时生成新文件
// log4cpp::DailyRollingFileAppender:每天生成一个新文件
// log4cpp::NTEventLogAppender:将日志输出到Windows事件日志中去
// log4cpp::StringQueueAppender               // 内存队列
// ...
log4cpp::Appender *appender = new log4cpp::FileAppender("test", "D:/log4cppTest/log4cppTest.log");
root.addAppender(appender);

StringQueueAppender 内存队列-多线程调试

”。因为printf导致IO中断,会使得本线程挂起,其花费的时间比一条普通指令多数千倍,若多个线程同时运行,则严重干扰了线程间的运行方式。所以。StringQueueAppender的功能是将日志记录到一个字符串队列中,该字符串队列使用了STL中的两个容器,即字符串容器std::string和队列容器std::queue,具体如下:

std::queue<std::string> _queue; _queue变量是StringQueueAppender类中用于具体存储日志的内存队列。StringQueueAppender的使用方法与OstreamAppender类似,其创建函数只接收一个参数“名称”,记录完成后需要程序员自己从队列中取出每条日志,例子程序StringQueueAppenderExam如下:

#include<iostream>
#include<log4cpp/Category.hh>
#include<log4cpp/OstreamAppender.hh>
#include<log4cpp/BasicLayout.hh>
#include<log4cpp/Priority.hh>
#include<log4cpp/StringQueueAppender.hh>
using namespacestd;

int main(int argc,char* argv[])
{
    log4cpp::StringQueueAppender* strQAppender = newlog4cpp::StringQueueAppender("strQAppender");
    strQAppender->setLayout(newlog4cpp::BasicLayout());
    log4cpp::Category& root =log4cpp::Category::getRoot();
    root.addAppender(strQAppender);
    root.setPriority(log4cpp::Priority::DEBUG);
    root.error("Hello log4cpp in a Error Message!");
    root.warn("Hello log4cpp in a WarningMessage!");
    cout<<"Get message from MemoryQueue!"<<endl;
    cout<<"-------------------------------------------"<<endl;
    queue<string>& myStrQ =strQAppender->getQueue();
    while(!myStrQ.empty())
    {
        cout<<myStrQ.front();
        myStrQ.pop();
    }
    log4cpp::Category::shutdown();
    return 0;

}

日志布局(Layout)

Layout,设置日志输出风格,有BasicLayout、SimpleLayout、PatternLayout,其中BasicLayout,SimpleLayout主要是提供的成型的简单日志风格,实际中基本不会使用,主要是使用PatternLayout对日志做格式输出,Layout是加载到Appender中去的。

// %% 输出一个%
 // %c 输出Categroy名称
 // %d 输出日期
 // %m 输出消息
 // %n 输出换行
 // %p 输出Priority
 // %d{%Y-%m-%d %H:%M:%S:%l}对输出日期做定制,年月日时分秒毫秒
 // %t 线程ID
 
 log4cpp::PatternLayout *patternLayout = new log4cpp::PatternLayout();
 patternLayout->setConversionPattern("%d{%Y-%m-%d %H:%M:%S.%l} %t [%p] %m %n");
 appender->setLayout(patternLayout);

Category、Appender和Layout三者的关系

系统中可以有多个Category,它们都是继承自同一个根,每个Category负责记录自己的日志;每个Category可以添加多个Appender,每个Appender指定了一个日志的目的地,例如文件、字符流或者Windows日志,当Category记录一条日志时,该日志被写入所有附加到此Category的Appender;每个Append都包含一个Layout,该Layout定义了这个Appender上日志的格式。

日志级别(Priority)

系统中默认的优先级等级如下:

typedef enum
{
    EMERG  = 0,     // emergency
    FATAL  = 0,     // fatal
    ALERT  = 100,   // alert
    CRIT   = 200,   // critical
    ERROR  = 300,   // error
    WARN   = 400,   // wanning
    NOTICE = 500,   // notice
    INFO   = 600,   // infomation
    DEBUG  = 700,   // debug
    NOTSET = 800   
} PriorityLevel;

注意:取值越小,优先级越高。例如一个Category的优先级为101,则所有EMERG、FATAL、ALERT日志都可以记录下来,而其他则不能。

// 代码设置
root.setPriority(log4cpp::Priority::DEBUG);
appender->setThreshold()

帮助类

帮助类, log4cpp主要提供如下几个类来简化代码设置:SimpleConfigurator,BasicConfigurator,PropertyConfigurator,其中SimpleConfigurator和BasicConfigurator,主要是使用代码的方式,进行了一些简单的默认配置,而PropertyConfigurator,是通过配置文件的方式对日志进行配置,也是最常用的,下面就是配置文件的格式:

#文件名为logTest.Property
 
#配置root Category的Priority为DEBUG, Appender为rootAppender
log4cpp.rootCategory=DEBUG, rootAppender
#配置子Category sub1的Priority为DEBUG, Appender为A1
log4cpp.category.sub1=DEBUG, A1
 
#配置rootAppender为FileAppender
log4cpp.appender.rootAppender=FileAppender
#配置日志文件名为test1.log,存放在D盘根目录下
log4cpp.appender.rootAppender.fileName=D:/test1.log
#配置layout为PatternLayout
log4cpp.appender.rootAppender.layout=PatternLayout
#设置日志输出风格
log4cpp.appender.rootAppender.layout.ConversionPattern=%d{%Y-%m-%d %H:%M:%S.%l} %t [%p] %m %n
 
#配置A1为RollingFileAppender
log4cpp.appender.A1=RollingFileAppender
#配置日志文件名为test2.log,存放在D盘根目录下
log4cpp.appender.A1.fileName=D:/test2.log
#配置日志文件最大不能超过1M
log4cpp.appender.A1.maxFileSize=1024 * 1024
#日志文件最多存储3个文件,超过就会删除旧的
log4cpp.appender.A1.maxBackupIndex=2
#设置日志风格
log4cpp.appender.A1.layout=PatternLayout
log4cpp.appender.A1.layout.ConversionPattern=%d{%Y-%m-%d %H:%M:%S.%l} %t [%p] %m %n
 
 
// 代码设置配置文件
log4cpp::PropertyConfigurator::configure(logTest.Property);

注意

;;;

代码使用

windows环境下

获取到源代码后,找到源码目录中的msvc10目录,使用VS2010打开msvc10.sln解决方案文件,编译源码获得log4cpp.DLL和log4cpp.lib;在源码目录中有include文件夹,将include整个拷贝到我们的工程目录下,将include添加到工程属性中的附加包含目录中(QT工程则添加到INCLUDEPATH中去),这时就能够正常使用了。

linux环境下

编译使用log4cpp库的CPP文件时,要加上库文件,才能顺利的编译通过,如下示例

$ g++ log4test.cpp -llog4cpp -lpthread

运行时,如若提示缺少log4cpp库文件,表示,需要进行以下设置

  1. 以管理员身份登录终端,然后执行以下操作:
$ sudo vim /etc/ld.so.conf
  1. 在打开的文件末尾另起一行添加动态库log4cpp的路径(这里是/usr/local/lib),然后保存退出
  2. 执行命令ldconfig使设置生效即可。
$ sudo ldconfig //更新库文件的缓存信息

测试用例

#include <log4cpp/BasicLayout.hh>
#include <log4cpp/SimpleLayout.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/RollingFileAppender.hh>
#include <log4cpp/Category.hh>
#include <log4cpp/Priority.hh>
#include <iostream>

using std::cout;
using std::endl;

using namespace log4cpp;//一次性将log4cpp中的实体全部引出来

void test()
{
    //日志的格式
    /* BasicLayout *pbl = new BasicLayout(); */
    SimpleLayout *psl = new SimpleLayout();

    //日志的目的地
    /* OstreamAppender *pos = new OstreamAppender("OstreamAppender123", &cout); */
    /* pos->setLayout(pbl); */
    OstreamAppender *pos = new OstreamAppender("OstreamAppender123", &cout);
    pos->setLayout(psl);
    
    //日志种类(日志记录器)
    Category &root = Category::getRoot();
    root.addAppender(pos);
    root.setPriority(Priority::ERROR);//过滤器

    root.fatal("The log is fatal");
    root.alert("The log is alter");
    root.crit("The log is crit");
    root.error("The log is error");
    root.warn("The log is warn");

    //回收资源
    Category::shutdown();

}

void test2()
{
    //日志的格式
    PatternLayout *ppl1 = new PatternLayout();
    ppl1->setConversionPattern("%d %c [%p] %m %n");

    PatternLayout *ppl2 = new PatternLayout();
    ppl2->setConversionPattern("%d %c [%p] %m %n");

    //日志的目的地
    OstreamAppender *pos = new OstreamAppender("OstreamAppender123", &cout);
    pos->setLayout(ppl1);

    RollingFileAppender *ppfa = 
        new RollingFileAppender("RollingFileAppender123", "test.log",
                                5 * 1024, 3);
    ppfa->setLayout(ppl2);
    
    //日志种类(日志记录器)
    Category &root = Category::getRoot();
    root.addAppender(pos);
    root.addAppender(ppfa);
    root.setPriority(Priority::ERROR);//过滤器

    size_t idx = 0;
    while(idx < 100)
    {
       root.fatal("The log is fatal");
       root.alert("The log is alter");
       root.crit("The log is crit");
       root.error("The log is error");
       root.warn("The log is warn");
       ++idx;

    }

    //回收资源
    Category::shutdown();

}
void test3()
{
    //日志的格式
    PatternLayout *ppl1 = new PatternLayout();
    ppl1->setConversionPattern("%d %c [%p] %m %n");

    PatternLayout *ppl2 = new PatternLayout();
    ppl2->setConversionPattern("%d %c [%p] %m %n");

    //日志的目的地
    OstreamAppender *pos = new OstreamAppender("OstreamAppender123", &cout);
    pos->setLayout(ppl1);

    FileAppender *pfl = new FileAppender("FileAppender1234", "wd.log");
    pfl->setLayout(ppl2);
    
    //日志种类(日志记录器)
    Category &root = Category::getRoot().getInstance("MyCat");
    root.addAppender(pos);
    root.addAppender(pfl);
    root.setPriority(Priority::ERROR);//过滤器

    root.fatal("The log is fatal");
    root.alert("The log is alter");
    root.crit("The log is crit");
    root.error("The log is error");
    root.warn("The log is warn");

    //回收资源
    Category::shutdown();

}
int main(int argc, char **argv)
{
    test2();
    return 0;
}

封装成日志类(单例模式)

线程安全、自动释放、回滚日志、同时存在文件和屏幕打印

#include <log4cpp/Category.hh>
#include <log4cpp/RollingFileAppender.hh>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/Priority.hh>

#include <iostream>
#include <string>
using log4cpp::Category;
using log4cpp::RollingFileAppender;
using log4cpp::OstreamAppender;
using log4cpp::PatternLayout;
using log4cpp::Priority;
using std::cout;
using std::endl;
using std::string;

class Logger
{
public:
    static Logger *getInstance(const char* fileName = "myLog.txt");
    static void destroy();

    void warn(const char *msg);
    void warn(const char * msg, const char *file, const char *func, int line);
    void error(const char *msg);
    void error(const char * msg, const char *file, const char *func, int line);
    void debug(const char *msg);
    void debug(const char * msg, const char *file, const char *func, int line);
    void info(const char *msg);
    void info(const char * msg, const char *file, const char *func, int line);

private:
    Logger(string fileName);
    ~Logger();
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

private:
    PatternLayout *_ptnLayout1;
    PatternLayout *_ptnLayout2;
    OstreamAppender *_osAppender;
    RollingFileAppender *_rfAppender;
    Category &_mycategory;
};
#include "Logger.h"

Logger *Logger::getInstance(const char *fileName )
{
    static Logger logger(fileName);
    // 注册destroy函数,在进程结束的时候执行,从而自动回收单例
    atexit(Logger::destroy);
    return &logger;
}
void Logger::destroy()
{
    Category::shutdown();
}

Logger::Logger(string fileName)
    : _ptnLayout1(new PatternLayout())
    , _ptnLayout2(new PatternLayout())
    , _osAppender(new OstreamAppender("osAppender", &cout))
    , _rfAppender(new RollingFileAppender("fAppender", fileName, 5 * 1024, 3))
    , _mycategory(Category::getRoot().getInstance("myCategory"))
{
    cout << "Logger()" << endl;
    _ptnLayout1->setConversionPattern("%d [%p] %m%n");
    _ptnLayout2->setConversionPattern("%d [%p] %m%n");
    _osAppender->setLayout(_ptnLayout1);
    _rfAppender->setLayout(_ptnLayout2);

    _mycategory.setPriority(Priority::DEBUG);
    _mycategory.addAppender(_osAppender); // 设置同时存入文件和屏幕显示
    _mycategory.addAppender(_rfAppender);
}

Logger::~Logger()
{
    cout << "~Logger()" << endl;
}

void Logger::warn(const char *msg)
{
    _mycategory.warn("%s", msg);
}

void Logger::warn(const char *msg, const char *file, const char *func, int line)
{
    _mycategory.warn("%s@%s,%d : %s", func, file, line, msg);
}

void Logger::error(const char *msg)
{
    _mycategory.error("%s", msg);
}

void Logger::error(const char *msg, const char *file, const char *func, int line)
{
    _mycategory.error("%s@%s,%d : %s", func, file, line, msg);
}

void Logger::debug(const char *msg)
{
    _mycategory.debug("%s", msg);
}

void Logger::debug(const char *msg, const char *file, const char *func, int line)
{
    _mycategory.debug("%s@%s,%d : %s", func, file, line, msg);
}

void Logger::info(const char *msg)
{
    _mycategory.info("%s", msg);
}

void Logger::info(const char *msg, const char *file, const char *func, int line)
{
    _mycategory.info("%s@%s,%d : %s", func, file, line, msg);
}
#include "Logger.h"

int main()
{
    Logger * log = Logger::getInstance();
    log->warn("This is warn msg!", __FILE__, __FUNCTION__, __LINE__);
    log->error("This is error msg!", __FILE__, __FUNCTION__, __LINE__);
    log->debug("This is debug msg!", __FILE__, __FUNCTION__, __LINE__);
    log->info("This is info msg!", __FILE__, __FUNCTION__, __LINE__);

    log->warn("This is warn msg!");
    log->error("This is error msg!");
    log->debug("This is debug msg!");
    log->info("This is info msg!");

    //Logger::destory();

    return 0;
}
$./a.out 
Logger()
2024-04-11 23:03:02,056 [WARN] main@test.cc,6 : This is warn msg!
2024-04-11 23:03:02,057 [ERROR] main@test.cc,7 : This is error msg!
2024-04-11 23:03:02,057 [DEBUG] main@test.cc,8 : This is debug msg!
2024-04-11 23:03:02,057 [INFO] main@test.cc,9 : This is info msg!
2024-04-11 23:03:02,058 [WARN] This is warn msg!
2024-04-11 23:03:02,058 [ERROR] This is error msg!
2024-04-11 23:03:02,059 [DEBUG] This is debug msg!
2024-04-11 23:03:02,059 [INFO] This is info msg!
~Logger()