log4cpp浅析

 

log4cpp是个基于LGPL的开源项目,移植自Java的日志处理跟踪项目log4j,并保持了API上的一致。其类似的支持库还包括Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(log4p)等。

 

Log4cpp中最重要概念有Category(种类)、Appender(附加器)、Layout(布局)、Priorty(优先级)、NDC(嵌套的诊断上下文)。

   Category、Appender与Layout三者的关系如下图所示:

 

 

 

log4cpp浅析_第1张图片

下载和编译

下载地址:https://sourceforge.net/projects/log4cpp/files/latest/download   我下载的是:log4cpp-1.1.3.tar.gz

解压:

tar zxf log4cpp-1.1.3.tar.gz

编译:

cd log4cpp
./configure
make
make check
sudo make install
sudo ldconfig

 

默认安装路径:

头文件:/usr/local/include/log4cpp

库文件:lqf@ubuntu:~/0voice/log/log4cpp$ ls -al /usr/local/lib/liblog4cpp.

liblog4cpp.a         liblog4cpp.la        liblog4cpp.so        liblog4cpp.so.5      liblog4cpp.so.5.0.6

测试范例

手动使用log4cpp的基本步骤

手动使用log4cpp的基本步骤如下:

  1. 实例化一个layout 对象;
  2. 初始化一个appender 对象;
  3. 把layout对象附着在appender对象上;
  4. 调用log4cpp::Category::getInstance("name"). 实例化一个category对象;
  5. 把appender对象附到category上(根据additivity的值取代其他appender或者附加在其他appender后)。
  6. 设置category的优先级;
  7. // FileName: 2-test_log4cpp.cpp
    #include "log4cpp/Category.hh"
    #include "log4cpp/FileAppender.hh"
    #include "log4cpp/BasicLayout.hh"
    int main(int argc, char *argv[])
    {
        // 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("darren");
        // 5. 设置additivity为false,替换已有的appender
        warn_log.setAdditivity(false);
        // 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");
    
        warn_log.critStream() << "This will show up << as "
                              << 1 << " critical message";
        // clean up and flush all appenders
        log4cpp::Category::shutdown();
        return 0;
    }

    编译:g++ -o 2-test_log4cpp 2-test_log4cpp.cpp -llog4cpp

配置文件驱动方式使用步骤

基本使用步骤是:

  1. 读取解析配置文件;
  2. 实例化category对象;
  3. 正常使用这些category对象进行日志处理;

 

配置文件:

# 文件名: 3-test_log4cpp.conf
# a simple test config
#定义了3个category sub1, sub2, sub1.sub2
log4cpp.rootCategory=DEBUG, rootAppender
log4cpp.category.sub1=,A1
log4cpp.category.sub2=INFO
#log4cpp.category.sub1.sub2=ERROR, A2
log4cpp.category.sub1.sub2=, A2
# 设置sub1.sub2 的additivity属性
log4cpp.additivity.sub1.sub2=true
#定义rootAppender类型和layout属性
log4cpp.appender.rootAppender=org.apache.log4cpp.ConsoleAppender
log4cpp.appender.rootAppender.layout=org.apache.log4cpp.BasicLayout
#定义A1的属性
log4cpp.appender.A1=org.apache.log4cpp.FileAppender
log4cpp.appender.A1.fileName=A1.log
log4cpp.appender.A1.layout=org.apache.log4cpp.SimpleLayout
#定义A2的属性
log4cpp.appender.A2=org.apache.log4cpp.ConsoleAppender
log4cpp.appender.A2.layout=org.apache.log4cpp.PatternLayout
#log4cpp.appender.A2.layout.ConversionPattern=The message '%m' at time 
# log4cpp.appender.A2.layout.ConversionPattern=%d %m %n
# %d 时间戳 %t 线程名 %x NDC  %p 优先级 %m log message 内容  %n 回车换行
log4cpp.appender.A2.layout.ConversionPattern=%d %p %x - %m%n
// FileName: 3-test_log4cpp.cpp
// Test log4cpp by config file.
#include "log4cpp/Category.hh"
#include "log4cpp/PropertyConfigurator.hh"
int main(int argc, char* argv[])
{
    // 1 读取解析配置文件
    // 读取出错, 完全可以忽略,可以定义一个缺省策略或者使用系统缺省策略
    // BasicLayout输出所有优先级日志到ConsoleAppender
    try { 
        log4cpp::PropertyConfigurator::configure("./test_log4cpp2.conf");
    } catch(log4cpp::ConfigureFailure& f) {
        std::cout << "Configure Problem " << f.what() << std::endl;
        return -1;
    }
     
    // 2 实例化category对象
    // 这些对象即使配置文件没有定义也可以使用,不过其属性继承其父category
    // 通常使用引用可能不太方便,可以使用指针,以后做指针使用
    // log4cpp::Category* root = &log4cpp::Category::getRoot();
    log4cpp::Category& root = log4cpp::Category::getRoot();
     
    log4cpp::Category& sub1 = 
        log4cpp::Category::getInstance(std::string("sub1"));
    log4cpp::Category& sub3 = 
        log4cpp::Category::getInstance(std::string("sub1.sub2"));
    // 3 正常使用这些category对象进行日志处理。
    // sub1 has appender A1 and rootappender.
    sub1.info("This is some info");
    sub1.alert("A warning");
     
    // sub3 only have A2 appender.
    sub3.debug("This debug message will fail to write");
    sub3.alert("All hands abandon ship");
    sub3.critStream() << "This will show up << as " << 1 << " critical message";
    sub3 << log4cpp::Priority::ERROR 
              << "And this will be an error";
    sub3.log(log4cpp::Priority::WARN, "This will be a logged warning");
     
    return 0;
}

编译:g++ -o 3-test_log4cpp  3-test_log4cpp.cpp -llog4cpp -lpthread

Category类

   Category英中翻译过来是“种类”,但我的理解它应该带有“归类”的意思。它的机制更像是“部门组织机构”,具有树型结构。它将日志按“区域”划分。

   Log4cpp有且只一个根Category,可以有多个子Category组成树型结构。Category具有Priority、additivity属性与若干方法。下面列出所有的属性与常用方法。

 

属性:Name

   Category的名称。不可重复。

相关方法

   virtual const std::string& getName() const throw();

属性:Priority

日志的优先级(详情请见Priority章节):

  • 对于根Category,必须指定Priority。(priority < Priority::NOTSET)
  • 对于非根Category,可以不指定Priority,此时优先级继承自父Category。
  • 对于非根Category,也可以指定Priority,此时优先级覆盖父Category的优先级。

相关方法

//设置当前Category的优先级

virtual void setPriority(Priority::Value priority);

//获取当前对象的优先级

virtual Priority::Value getPriority() const throw();

// 设置root Category的优先级

static void setRootPriority(Priority::Value priority);

// 获取root Category的优先级

static Priority::Value getRootPriority() throw();

// 获取当前category继承表中的优先级,如果当前的优先值没有设置的话,则找他的父亲,

// 如果父亲没有找到的话,他会继续向上找,因为root Category的优先值默认为Priority::INFO,

// 所以肯定是能够找到的

virtual Priority::Value getChainedPriority() const throw();

// 返回当前拥有priority优先级

virtual bool isPriorityEnabled(Priority::Value priority) const throw();

属性:additivity

 每个Category都有一个additivity属性,该属性默认值为true。

  • 如果值为true,则该Category的Appender包含了父Category的Appender。
  • 如果值为false,则该Category的Appender取代了父Category的Appender。

相关方法

virtual void setAdditivity(bool additivity);

virtual bool getAdditivity() const throw();

属性:parent

   上级Category。根Category的parent为空。

相关方法

virtual Category* getParent() throw();

virtual const Category* getParent() const throw();

 

方法:getRoot

   静态方法。取得根Category。

static Category& getRoot();

 

方法:getInstance

静态方法。取得指定名称(参数name)的Category,如果不存在,则自动创建一个以name命名,parent为rootCategory,Priority为INFO的Category(说白了就是一个工厂方法,不要和单例模式混淆了)。

static Category& getInstance(const std::string& name);

方法:exists

静态方法。判断是否存在指定名称(参数name)的Category。如果存在,则返回相应的Category(指针),否则返回空指针。­­

static Category* exists(const std::string& name);

方法:shutdown

   静态方法。从所有的Category中移除所有的Appender。

   static void shutdown();

方法:shutdownForced

   静态方法。从所有的Category中移除所有的Appender,并且删除所有的Appender。(shutdown方法只是不使用Appender,并没有彻底从内存中销毁Appender)。

   static void shutdownForced();

方法:Appender相关

// 添加一个Appender到Category中。

// 该方法将把Appender的所有权交给Category管理

virtual void addAppender(Appender* appender);    //appender的生命周期被函数内部(Category)接管

// 添加一个Appender到Category中。

// 但是该方法并不把Appender的所有权交给Category管理

virtual void addAppender(Appender& appender);

// 获取指定名字的Appender(指针)。如果不存在则返回空指针

virtual Appender* getAppender(const std::string& name) const;

// 获取所有的Appender,以指针的方式存储在set中

virtual AppenderSet getAllAppenders() const;

// 删除所有的Appender

virtual void removeAllAppenders();

// 删除指定的Appender

virtual void removeAppender(Appender* appender);

// 判断指定Appender是否被当前Category拥有所有权。

// 如果是的话,在删除该Appender时,将同时销毁它。

virtual bool ownsAppender(Appender* appender) const throw();  

 

 

注意:

   在使用virtual void addAppender(Appender* appender);方法时,Category会接管appender的所有权,并确保会在合适的时机销毁它,所以不要再在外面调用delete方法去销毁它。

如:

log4cpp::Appender*appender = new log4cpp::OstreamAppender("default", &std::cout);

root.addAppender(appender);

delete appender;  // Error! Don't delete it

 

方法:日志相关

   Category的日志输出方式有两种,一种是简单的传递std::string类型的字符串,一种是采用类似c api中的printf,可以格式化生成字符串。

// 以指定的优先级生成日志

virtual void log(Priority::Value priority,

                    const std::string& message) throw();

// 以指定的优先级与格式化生成日志,类似c api中的printf方法

virtual void log(Priority::Value priority, const char* stringFormat,

                    ...) throw();

// 格式化生成日志的一种变体,直接传递va_list参数

virtual void logva(Priority::Value priority, const char* stringFormat,

                      va_list va) throw();

 

 

   除了以上三种通用日志三种方法外,Log4cpp还指供了多种预定义Priority的方法,如:

void debug(const char* stringFormat, ...) throw();

void info(const char* stringFormat, ...) throw();

……

 

 

   例子:

log4cpp::Category& rootCategory = log4cpp::Category::getRoot();

rootCategory.error("This is error message\n");

rootCategory.log(log4cpp::Priority::DEBUG, "This is %d message\n", 8);

 

方法: CategoryStream

   除了C风格的方法外,Log4cpp还指供了c++风格的流式输出方法。模彷std::iostream,Log4cpp也指供了CategoryStream辅助类。每个CategoryStream对象封装一种优先级的日志输出,并提供了对“<<”符号的重载函数。CategoryStream对象内部借由std::ostringstream来实现流式格式化输出生成日志消息(std::string)。

   // 获取(生成)指定优先级的CategoryStream

   virtual CategoryStream getStream(Priority::Value priority);

   // 对运算符“<<”的重载。也是获取(生成)指定优先级的CategoryStream

virtual CategoryStream operator<<(Priority::Value priority);

// 快捷方法。还有infoStram()等预定义优先级的快捷方法。

inline CategoryStream debugStream();

……

 

   在使用时,CategoryStream,应该只作为临时对象,因为它总是在对象销毁时才会真正将日志输出(flush)。当然你可以强制调用CategoryStream的flush方法,但这样会使代码很难看(日志代码跟实际业务无关,代码行应该尽可能的少,并且可以随时屏蔽日志功能)。

   例子:

   log4cpp::Category& root = log4cpp::Category::getRoot();

   root << log4cpp::Priority::ERROR << "This is error message";

   root.debugStream() << "This is " << 8 << " message";

FileAppender

作用:输出到文件系统。

构造函数:

FileAppender(const std::string& name, const std::string& fileName,

                    bool append = true, mode_t mode = 00644);

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

filename

string

 

文件名。

可以是绝对路径,也可以是相对路径。

当文件不存在时,Log4cpp会自动创建文件,但是不会自动创建目录。所以,当filename中的路径不存在时,操作将会失败。

append

boolean

true

是否从现有文件中追加。如果为false,则将会覆盖当前文件。

mode

int

00644

打开文件的权限模式。

参考c api中的open函数。

RollingFileAppender

作用:一种特例化的FileAppender,文件系统是文件大小为单位,当日志文件的大小达到预设值时,将会生成新的日志文件。

构造函数:

RollingFileAppender(const std::string& name,

                           const std::string& fileName,

                           size_t maxFileSize = 10*1024*1024,

                           unsigned int maxBackupIndex = 1,

                           bool append = true,

                           mode_t mode = 00644);

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

filename

string

 

文件名。

可以是绝对路径,也可以是相对路径。

当文件不存在时,Log4cpp会自动创建文件,但是不会自动创建目录。所以,当filename中的路径不存在时,操作将会失败。

append

boolean

true

是否从现有文件中追加。如果为false,则将会覆盖当前文件。

mode

int

00644

打开文件的权限模式。

参考c api中的open函数。

maxFileSize

int

10M

文件大小最大值。

当日志文件的大小达到该值时,将会生成新的日志文件。

注意:在临界值时,Log4cpp保证了同一条日志并不会输出到两个文件中,以保留可读性。

maxBackupIndex

int

1

保留的日志文件数,该值必须大于或等于1。

当文件数量超出该值时,较早期的文件将被删除。

 

值得借鉴的是maxBackupIndex保持多少个日志文件数量。

 

对于这里检测文件大小(lseek(_fd, 0, SEEK_END))的方式效率太低,不值得借鉴。

 

OstreamAppender

作用:输出到指定流,需指定std:ostream对象。可以是std::cout或std::cerr或其它派生自std::ostream的流对象。

构造函数:OstreamAppender(const std::string& name, std::ostream* stream);

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

stream

std:ostream

 

可以是std::cout或std::cerr或其它派生自std::ostream的流对象。

 

范例:

log4cpp::OstreamAppender * osAppender = new log4cpp::OstreamAppender("osAppender",&std::cout);

 

RemoteSyslogAppender

作用:输出到远程syslog系统。

构造函数:

RemoteSyslogAppender(const std::string& name,

                            const std::string& syslogName,

                            const std::string& relayer,

                            int facility = LOG_USER,

                            int portNumber = 514);

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

syslogName

string

 

Syslog服务名称

relayer

string

 

主机名。可以是域名,也可以是IP地址。

facility

int

8

优先级

portNumber

int

514

Syslog的网络服务端口。

 

SmptAppender

作用:通过smtp协议发送到指定邮箱。

构造函数:

SmptAppender(const std::string& name, const std::string& host, const std::string& from,

                     const std::string& to, const std::string& subject);

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

host

string

 

Smtp服务器地址

from

string

 

发件人

to

string

 

收件人

subject

string

 

主题

 

StringQueueAppender

作用:输出到内存/字符串队列。

构造函数:StringQueueAppender(const std::string& name);

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

 

SyslogAppender

作用:输出到本地syslog系统。

相关参数:SyslogAppender(const std::string& name, const std::string& syslogName,

                      int facility = LOG_USER);

名称

类型

默认值

描述

name

string

 

Appender名称

syslogName

string

 

Syslog服务名称

facility

int

8

优先级

 

 

BufferingAppender

作用:输出到缓存队列

 

 

Win32DebugAppender

作用:输出到Windows缺省调试器。

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

 

IdsaAppender

作用:输出到Idsa服务。

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

idsaName

string

 

Idsa服务名

 

NTEventLogAppender

输出到Windows日志系统。

相关参数:

名称

类型

默认值

描述

name

string

 

Appender名称

sourceName

string

 

Windows日志中的“事件来源”名称

 

 

 

Layout类

   Layout控制输出日志的显示样式。Log4cpp内置了4种Layout。

对于不同的日志布局,大家参考:

  • BasicLayout::format
  • PassThroughLayout::format  支持自定义的布局,我们可以继承他实现自定义的日志格式
  • PatternLayout::format  log4cpp支持用户配置日志格式
  • SimpleLayout::format  比BasicLayout还简单的日志格式输出。

 

 

PassThroughLayout

   直通布局。顾名思义,这个就是没有布局的“布局”,你让它写什么它就写什么,它不会为你添加任何东西,连换行符都懒得为你加。

SimpleLayout

   简单布局。它只会为你添加“优先级”的输出。相当于PatternLayout格式化为:“%p: %m%n”。

const std::string& priorityName = Priority::getPriorityName(event.priority);

       message.width(Priority::MESSAGE_SIZE);message.setf(std::ios::left);

       message << priorityName << ": " << event.message << std::endl;

 

BasicLayout

   基本布局。它会为你添加“时间”、“优先级”、“种类”、“NDC”。相当于PatternLayout格式化为:“%R %p %c %x: %m%n”

 

PatternLayout

   格式化布局。它的使用方式类似C语言中的printf,使用格式化它符串来描述输出格式。目前支持的转义定义如下:

       %% - 转义字符'%'

       %c - Category

      %d - 日期;日期可以进一步设置格式,用花括号包围,例如%d{%H:%M:%S,%l}。

            日期的格式符号与ANSI C函数strftime中的一致。但增加了一个格式符号%l,表示毫秒,占三个十进制位。

       %m - 消息

       %n - 换行符;会根据平台的不同而不同,但对用户透明。

      %p - 优先级

       %r - 自从layout被创建后的毫秒数

       %R - 从1970年1月1日开始到目前为止的秒数

       %u - 进程开始到目前为止的时钟周期数

       %x - NDC

       %t - 线程id

 

 

Priority优先级

   日志的优先级。Log4cpp内置了10种优先级。

typedef enum {

    EMERG  = 0,

    FATAL  = 0,

    ALERT  = 100,

    CRIT   = 200,

    ERROR  = 300,

    WARN   = 400,

    NOTICE = 500,

    INFO   = 600,

    DEBUG  = 700,

    NOTSET = 800

} PriorityLevel;

 

取值越小,优先级越高。例如一个Category的优先级为INFO(600),则包括EMEGR、FATAL、ALERT、CRIT、ERROR、WARN、INFO等小于或等于600的日志都会被输出,而DEBUG等大于600的日志则不会被输出。

注意1:优先级是一个整数,不一定要只取上面的预定义值,比如说101也是可以的。

注意2:对于根Category,优先级不能大于或等于NOTSET(800)。

NDC

   NDC是Nested Diagnostic Contexts的英文缩定,意思就是“嵌套的诊断上下文”,有了它我们可以更好的跟踪程序运行轨迹。

   NDC是以线程为基础的,每个线程拥有且只有一个NDC。NDC看起来像一个“栈”。

   NDC的几个常用方法是push、pop、get、clear。

       Push:把一个字符串压入NDC栈。

       Pop:从NDC栈中退出上一次压入的字符串。

       Get:取得当前NDC栈中的字符串,每次压入的字符串用空格隔开。

       Clear:清空NDC栈中的字符串。

   典型的NDC用法可以在每个函数入口处调用NDC::push(__FUNCTION__); 在函数出口处调用NDC::pop(); 在PatternLayout中指定%x参数。这样就可以在日志中清晰的知道函数的调用情况。

 

Log4cpp配置文件格式说明

log4cpp有3个主要的组件:categories(类别)、appenders(附加目的地)、和 layouts(布局),layout类控制输出日志消息的显示样式(看起来像什么)。log4cpp当前提供以下layout格式:

  • log4cpp::IdsaAppender // 发送到IDS或者logger,
  • log4cpp::FileAppender // 输出到文件
  • log4cpp::RollingFileAppender // 输出到回卷文件,即当文件到达某个大小后回卷
  • log4cpp::OstreamAppender // 输出到一个ostream类
  • log4cpp::RemoteSyslogAppender // 输出到远程syslog服务器
  • log4cpp::StringQueueAppender // 内存队列
  • log4cpp::SyslogAppender // 本地syslog
  • log4cpp::Win32DebugAppender // 发送到缺省系统调试器
  • log4cpp::NTEventLogAppender // 发送到win 事件日志

 

category 类真正完成记录日志功能,两个主要组成部分是appenders和priority(优先级)。优先级控制哪类日志信息可以被这个category记录,当前优先级分为:NOTSET, DEBUG, INFO, NOTICE, WARN, ERROR, CRIT, ALERT 或 FATAL/EMERG 。每个日志信息有个优先级,每个category有个优先级,当消息的优先级大于等于category的优先级时,这个消息才会被category记录,否则被忽略。优先级的关系如下:

NOTSET < DEBUG < INFO < NOTICE < WARN < ERROR < CRIT < ALERT < FATAL = EMERG

 

 

category类和appender的关系是,多个appender附在category上,这样一个日志消息可以同时输出到多个设备上。

category被组织成一个树,子category创建时优先级缺省NOTSET,category缺省会继承父category的appender。而如果不希望这种appender的继承关系,log4cpp允许使用additivity 标签,为false时新的appender取代category的appender列表。

 

log4cpp可以用手动方式使用,也可以使用配置文件使用,用配置文件的方式简单,便捷。所有,现在都用配置文件的方式。

 

appender属性(本质是根据不同的appender有不同的熟悉):

appender类型

fileName 文件名

layout 日志输出格式

 

比如FileAppender

FileAppender(const std::string& name, const std::string& fileName,

                    bool append = true, mode_t mode = 00644);

即是在配置文件的时候,配置以上属性(name,fileName,append ,mode)。

 

 

日志级别

log 的优先级别解读,参阅源码 log4cpp-1.1.3\include\log4cpp\Priority.hh

 

由高到低

  • EMERG
  • FATAL
  • ALERT
  • CRIT
  • ERROR
  • WARN
  • NOTICE
  • INFO
  • DEBUG
  • NOTSET

 

对应到 Category 相应函数,参阅源码 log4cpp-1.1.3\include\log4cpp\Category.hh

  • Category::emerg()
  • Category::fatal()
  • Category::alert()
  • Category::crit()
  • Category::error()
  • Category::warn()
  • Category::notice()
  • Category::info()
  • Category::debug()

 

以上函数都有 2 个重载函数,可分别接受格式化字串或 std::string,例如 debug(),有

  • void debug(const char* stringFormat, ...) throw();
  • void debug(const std::string& message) throw();

 

 

关于优先级别使用的建议

开发运行时,设为 DEBUG 级,而正式运营时,则设为 NOTICE ;

一定要显示出来的信息则可以用 NOTICE 或以上级别;

跟踪函数运行痕迹的信息用 INFO 级别;

运行时调试的信息用 DEBUG 级别;

举例说明

void initialize(int argc, char* argv[])
{
  log.info("initialize() : argc=%d", argc);
  for (int i=0; i < argc; ++i)
  {
    log.debug("initialize() : argv[%d]=%s", i, argv[i]);
  }
  log.notice("initialize() : done");
}

 

log4cpp 的 category 分为 rootCategory 和其它自定义的 category。

而每个 category 都可以输出到多个 appender。并且 category 也是有包含关系的。

例如 rootCategory 就是所有 category 的根。而自定义的 category 也可以在配置文件中定义其包含关系。

先看一个 rootCategory 的配置

log4cpp.rootCategory=DEBUG, console, sample

这个定义里,指定了 rootCategory 的 log 优先级是 DEBUG,其 appender 有 2 个,分别是 console 和 sample。

即是说,等号右边内容以逗号分隔,第一项是优先级别,接下来的都是 appender 名字,可以有一个或多个。

 

现在来看看自定义的 category

log4cpp.category.demo=DEBUG, sample

这里定义了一个名字为 demo 的 category,其优先级为 DEBUG,appender 为 sample。

注意, category 和 appender 名字可以完全相同。

 

再来看看有包含关系的 category 的定义

log4cpp.category.demo.son=DEBUG, son
log4cpp.category.demo.daughter=DEBUG, daughter

以上定义了 2 个 category,名字分别为 son 和 daughter,其父 category 为 demo。

son 产生的 log 会写到 son 和 demo 的 appender 中。同理,daughter 的 log 会写到 daughter 和 demo 的 appender 中。

 

现在来看看 appender 的定义。appender 有很多种,我这里只介绍几种,分别是

ConsoleAppender : 控制台输出,即 std::cout
Win32DebugAppender : VC IDE 的输出,即 ::OutputDebugString
FileAppender : 文件输出
RollingFileAppender : 有待研究

 

现在看一个 ConsoleAppender 的例子

log4cpp.appender.console=ConsoleAppender
log4cpp.appender.console.layout=PatternLayout
log4cpp.appender.console.layout.ConversionPattern=%d [%p] - %m%n

以上信息解释为:一个名为 console 的 appender,其类型为 ConsoleAppender,即 控制台输出 log 输出的布局是 指定的样式

输出的格式 是 "%d [%p] - %m%n" (稍后再解释)

 

 

再看一个 FileAppender 的例子

log4cpp.appender.sample=FileAppender
log4cpp.appender.sample.fileName=sample.log
log4cpp.appender.sample.layout=PatternLayout
log4cpp.appender.sample.layout.ConversionPattern=%d [%p] - %m%n

以上信息解释为:一个名为 sample 的 appender,其类型为 FileAppender,即 文件输出指定的 log 文件名为 sample.log,输出的布局是 指定的样式,输出的格式 是 "%d [%p] - %m%n"

对应 category 和 appender 的配置方式,可以发现

category 是 "log4cpp.category." + "category name"

category 名字可以用 "." 分隔,以标识包含关系

appender 是 "log4cpp.appender." + "appender name"

appender 名字 不能 用 "." 分隔,即是说 appender 是没有包含关系的

现在看一个完整的配置文件例子

#定义 root category 的属性
log4cpp.rootCategory=DEBUG, console

#定义 console 属性
log4cpp.appender.console=ConsoleAppender
log4cpp.appender.console.layout=PatternLayout
log4cpp.appender.console.layout.ConversionPattern=%d [%p] - %m%n

#定义 sample category 的属性
log4cpp.category.sample=DEBUG, sample

#定义 sample appender 的属性
log4cpp.appender.sample=FileAppender
log4cpp.appender.sample.fileName=sample.log
log4cpp.appender.sample.layout=PatternLayout
log4cpp.appender.sample.layout.ConversionPattern=%d [%p] - %m%n

#定义 sample.son category 的属性
log4cpp.category.sample.son=DEBUG, son

#定义 son appender 的属性
log4cpp.appender.son=FileAppender
log4cpp.appender.son.fileName=son.log
log4cpp.appender.son.layout=PatternLayout
log4cpp.appender.son.layout.ConversionPattern=%d [%p] - %m%n

#定义 sample.daughter category 的属性
log4cpp.category.sample.daughter=DEBUG, daughter

#定义 daughter appender 的属性
log4cpp.appender.daughter=FileAppender
log4cpp.appender.daughter.fileName=daughter.log
log4cpp.appender.daughter.layout=PatternLayout
log4cpp.appender.daughter.layout.ConversionPattern=%d [%p] - %m%n

 

 

 

ConversionPattern 参数解读,参阅源码 log4cpp-1.1.3\src\PatternLayout.cpp

%m log message 内容, 即 用户写 log 的具体信息
%n 回车换行
%c category 名字
%d 时间戳
%p 优先级
%r 距离上一次写 log 的间隔, 单位毫秒
%R 距离上一次写 log 的间隔, 单位秒
%t 线程号
%u 处理器时间
%x NDC ?

 

 

 

窃以为,以下格式就足够了,即输出 "时间 [线程号] 优先级 - log内容 回车换行"

%d [%t] %p - %m%n

配置的知识就差不多了,现在看看实际代码应用

首先是初始化 log4cpp 的配置,例如我的配置文件名是 log4cpp.properties

try
{
  log4cpp::PropertyConfigurator::configure("log4cpp.properties");
}
catch (log4cpp::ConfigureFailure & f)
{
  std::cerr << "configure problem " << f.what() << std::endl;
}

 

初始化完成后,就可以这样用了(具体的应用技巧,你自己摸索吧)

log4cpp::Category & log = log4cpp::Category::getInstance(std::string("sample"));
log.debug("test debug log");
log.info("test info log");
// 用 sample.son
log4cpp::Category & log = log4cpp::Category::getInstance(std::string("sample.son"));
log.debug("test debug log of son");
log.info("test info log of son");
// 用 sample.daughter
log4cpp::Category & log = log4cpp::Category::getInstance(std::string("sample.daughter"));
log.debug("test debug log of daughter");
log.info("test info log of daughter");

 

Log4cpp源码分析

参考:《log4cpp源码详解手册》

 

 

 

log4cpp::FileAppender::_append 调用栈

结合4-RollingFileAppender.cpp

Breakpoint 3, 0x00007ffff7b9ba9a in log4cpp::FileAppender::_append(log4cpp::LoggingEvent const&) ()

  from /usr/local/lib/liblog4cpp.so.5

(gdb) bt

#0  0x00007ffff7b9ba9a in log4cpp::FileAppender::_append(log4cpp::LoggingEvent const&) () from /usr/local/lib/liblog4cpp.so.5

#1  0x00007ffff7b92b9c in log4cpp::AppenderSkeleton::doAppend(log4cpp::LoggingEvent const&) ()

  from /usr/local/lib/liblog4cpp.so.5

#2  0x00007ffff7ba5bb4 in log4cpp::Category::callAppenders(log4cpp::LoggingEvent const&) () from /usr/local/lib/liblog4cpp.so.5

#3  0x00007ffff7ba5e34 in log4cpp::Category::_logUnconditionally2(int, std::__cxx11::basic_string, std::allocator > const&) () from /usr/local/lib/liblog4cpp.so.5

#4  0x00007ffff7ba6736 in log4cpp::Category::error(std::__cxx11::basic_string, std::allocator > const&) () from /usr/local/lib/liblog4cpp.so.5

#5  0x0000000000401b77 in main (argc=1, argv=0x7fffffffe4d8) at 4-RollingFileAppender.cpp:100

 

log4cpp::RollingFileAppender::_append

结合4-RollingFileAppender.cpp

Breakpoint 2, 0x00007ffff7b9d7b8 in log4cpp::RollingFileAppender::_append(log4cpp::LoggingEvent const&) ()

  from /usr/local/lib/liblog4cpp.so.5

(gdb) bt

#0  0x00007ffff7b9d7b8 in log4cpp::RollingFileAppender::_append(log4cpp::LoggingEvent const&) ()

  from /usr/local/lib/liblog4cpp.so.5

#1  0x00007ffff7b92b9c in log4cpp::AppenderSkeleton::doAppend(log4cpp::LoggingEvent const&) ()

  from /usr/local/lib/liblog4cpp.so.5

#2  0x00007ffff7ba5bb4 in log4cpp::Category::callAppenders(log4cpp::LoggingEvent const&) () from /usr/local/lib/liblog4cpp.so.5

#3  0x00007ffff7ba5e34 in log4cpp::Category::_logUnconditionally2(int, std::__cxx11::basic_string, std::allocator > const&) () from /usr/local/lib/liblog4cpp.so.5

#4  0x00007ffff7ba6736 in log4cpp::Category::error(std::__cxx11::basic_string, std::allocator > const&) () from /usr/local/lib/liblog4cpp.so.5

#5  0x0000000000401b9e in main (argc=1, argv=0x7fffffffe4d8) at 4-RollingFileAppender.cpp:100

void RollingFileAppender::rollOver() {
        ::close(_fd);
        if (_maxBackupIndex > 0) {
            std::ostringstream filename_stream;
        	filename_stream << _fileName << "." << std::setw( _maxBackupIndexWidth ) << std::setfill( '0' ) << _maxBackupIndex << std::ends;
        	// remove the very last (oldest) file
        	std::string last_log_filename = filename_stream.str();
            // std::cout << last_log_filename << std::endl; // removed by request on sf.net #140
            ::remove(last_log_filename.c_str());
            
            // rename each existing file to the consequent one 修改文件序号
            for(unsigned int i = _maxBackupIndex; i > 1; i--) {
                filename_stream.str(std::string());
                filename_stream << _fileName << '.' << std::setw( _maxBackupIndexWidth ) << std::setfill( '0' ) << i - 1 << std::ends;  // set padding so the files are listed in order
                ::rename(filename_stream.str().c_str(), last_log_filename.c_str());
                last_log_filename = filename_stream.str();
            }
            // new file will be numbered 1
            ::rename(_fileName.c_str(), last_log_filename.c_str());
        }
        _fd = ::open(_fileName.c_str(), _flags, _mode);
    }

log4cpp::StringQueueAppender::_append

#0  0x00007ffff7b9e692 in log4cpp::StringQueueAppender::_append(log4cpp::LoggingEvent const&) ()

  from /usr/local/lib/liblog4cpp.so.5

#1  0x00007ffff7b92b9c in log4cpp::AppenderSkeleton::doAppend(log4cpp::LoggingEvent const&) ()

  from /usr/local/lib/liblog4cpp.so.5

#2  0x00007ffff7ba5bb4 in log4cpp::Category::callAppenders(log4cpp::LoggingEvent const&) () from /usr/local/lib/liblog4cpp.so.5

#3  0x00007ffff7ba5e34 in log4cpp::Category::_logUnconditionally2(int, std::__cxx11::basic_string, std::allocator > const&) () from /usr/local/lib/liblog4cpp.so.5

#4  0x00007ffff7ba5d54 in log4cpp::Category::_logUnconditionally(int, char const*, __va_list_tag*) ()

  from /usr/local/lib/liblog4cpp.so.5

#5  0x00007ffff7ba66c9 in log4cpp::Category::error(char const*, ...) () from /usr/local/lib/liblog4cpp.so.5

#6  0x000000000040288b in main (argc=1, argv=0x7fffffffe4d8) at 5-StringQueueAppender.cpp:42

日志库比较

 

   log4cxx:Apache的产品,属于apache的子项目之一,由log4j移植过来的。移植过来的东西多数都有一个特点,那就是要依赖各种碗糕。log4cxx就需要依赖apr-utils,编译起来没那么直接,因此我放弃了它。
   log4cpp:感觉上log4cpp和log4cplus大同小异,支持的功能也类似。上上一次更新是在2007年,不过在时隔五年之后的去年年底(2012年),它突然又更新了, 从1.0版提升到了1.1版,有点不容易,感觉诈尸了。另外,它也是由log4j移植过来的。
   log4cplus:log4cplus究竟和log4cpp区别在哪,如果一定要作对比似乎log4cplus比log4cpp复杂了那么一点点。log4cplus的功能比log4cpp支持的功能更加全面,支持线程安全(log4cpp的代码里也做了一些对线程安全的保证,但官方没有介绍它支持线程安全,但是我自己在centos 6.2上面编译log4cpp后发现其不支持多线程,详情:http://sourceforge.net/p/log4cpp/feature-requests/34/。反而log4cplus宣称自己是线程安全的),另外log4plus更新非常频繁,对于一个有版本洁癖的人来说,这也是我选择它的原因,新版本提供了vc10的解决方案,还支持可选编译Unicode和非Unicode版本。
   glog:这个就不介绍了,全称Google Glog, Google的东西一直比较让人放心。它比log4系列的库都要简单,不过它不支持用配置文件控制日志行为,这对我来说是个遗憾。
   zlog:  使用C编写,支持window和linux,但是Linux下有些问题,所以放弃了

你可能感兴趣的:(网络编程,c++)