c++日志工具之——log4cpp

1、log4cpp概述

Log4cpp是一个开源的C++类库,它提供了C++程序中使用日志和跟踪调试的功能,它的优点如下:

  • 提供应用程序运行上下文,方便跟踪调试;

  • 可扩展的、多种方式记录日志,包括命令行、文件、回卷文件、内存、syslog服务器、Win事件日志等;

  • 可以动态控制日志记录级别,在效率和功能中进行调整;

  • 所有配置可以通过配置文件进行动态调整;

  • 多语言支持,包括Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(log4p)等;

类似日志工具:glog、boost.log、spdlog

c++日志工具之——log4cpp_第1张图片

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工程一样,顺序执行以下命令即可完成安装。

./configure
make
make install

安装文件将默认安装到/usr/local,库文件置于/usr/local/lib,头文件置于/usr/local/include。

是的,这里介绍的安装及下面介绍的应用都是基于linux系统。

4 log4cplus的使用

以下是官方提供的“hello, world”的示例程序:

#include 
#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,执行以下命令编译:

g++ test.cpp -o test -llog4cplus

编译后生成 test 可执行文件,运行./test,得到如下输出:

WARN - Hello, World!

这个程序使用的是log4cplus内置的默认配置选项,实际使用中一般要自己配置选项,接下来你会看到。

相关视频推荐

如何设计高效日志库

C++ Golang日志库Glog源码分析

c++后端绕不开的7个开源项目,每一个源码值得深入研究

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

c++日志工具之——log4cpp_第2张图片

5 log4cplus配置

log4cplus配置就是定义appender, 定义输出的格式即 layout。以下列出两种常用配置,以供参考。

配置输出到控制台(通常用于前台程序):

log4cplus.logger.logmain = TRACE, console
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.logger.logmain = INFO, file
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文件:

#pragma once
 
#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 
 
#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 - TCPDomain()
[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工程,其交叉编译方法大致如下:

./configure --prefix=/your/install/path --host=arm-linux CXX=your-toolkit-g++
make
make install

其中--prefix即指定你的安装目录,如/opt/log4cplus,--host指定目标平台,CXX指定你的交叉编译工具(确保shell环境能找到该工具)。编译安装完后可在安装目录找到arm版本的库文件。

8 总结

日志固然好,但也不建议随意使用,用多了会导致程序性能有所下降,且代码size增加不少。以上是笔者运用log4cplus的些许经验,更深一步的原理机制有待进一步探究。

配置文件

 1 #-------定义rootCategory的属性-------
 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 回车换行

自定义封装

将上述过程封装,即可得到自己的日志类

/*采用单例模式设计,包含两个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__);

发送到远程服务器

应用程序中SocketAppender配置

前面启动了LoggingServer,下面说一下需要收集其日志的各个应用程序中配置。

说白了,就是在原来的基础上加一个SocketAppender,SocketAppender会自动将日志发送给loggingserver。这样直接在loggingserver上就可以查看所有服务器上的日志信息啦。哥终于不用再担心你们的运行状态啦!

配置文件

log4cplus.rootLogger=TRACE,STDOUT, ALL_MSGS,RemoteServer
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.rootLogger=TRACE, STDOUT, ALL_MSGS,RemoteServer,RemoteServer2
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 

你可能感兴趣的:(c++,日志库,log4cpp,linux)