(五)

日志系统的另一个基本功能就是能够让使用者按照自己的意愿来控制什么时候,哪些log信息可以输出。如果能够让用户在任意时刻设置允许输出的LogLevel的信息就好了,log4cplus通过LogLevelManagerLogLogFilter三种方式实现了上述功能。

### 优先级控制 ###

在研究LogLevelManager之前,首先介绍一下log4cpluslogger的存储机制,在log4cplus中,所有logger都通过一个层次化的结构(其实内部是hash表)来组织的,有一个Root级别的logger,可以通过以下方法获取:

Logger root = Logger::getRoot();

用户定义的logger都有一个名字与之对应,比如:

Logger test = Logger::getInstance("test");

可以定义该logger的子logger:

Logger subTest = Logger::getInstance("test.subtest");

注意Root级别的logger只有通过getRoot方法获取,Logger::getInstance("root")获得的是它的子对象而已。有了这些具有父子关系的logger之后可分别设置其LogLevel,比如:

root.setLogLevel( ... );

Test.setLogLevel( ... );

subTest.setLogLevel( ... );

logger的这种父子关联性会体现在优先级控制方面,log4cplus将输出的log信息按照LogLevel(从低到高)分为:

NOT_SET_LOG_LEVEL ( -1) :接受缺省的LogLevel,如果有父logger则继承它的LogLevel

ALL_LOG_LEVEL ( 0) :开放所有log信息输出

TRACE_LOG_LEVEL ( 0) :开放trace信息输出(ALL_LOG_LEVEL)

DEBUG_LOG_LEVEL (10000) :开放debug信息输出

INFO_LOG_LEVEL (20000) :开放info信息输出

WARN_LOG_LEVEL (30000) :开放warning信息输出

ERROR_LOG_LEVEL (40000) :开放error信息输出

FATAL_LOG_LEVEL (50000) :开放fatal信息输出

OFF_LOG_LEVEL (60000) :关闭所有log信息输出

LogLevelManager负责设置logger的优先级,各个logger可以通过setLogLevel设置自己的优先级,当某个loggerLogLevel设置成NOT_SET_LOG_LEVEL时,该logger会继承父logger的优先级,另外,如果定义了重名的多个logger, 对其中任何一个的修改都会同时改变其它logger,我们举例说明:

〖例6

#include "log4cplus\logger.h"

#include "log4cplus\consoleappender.h"

#include "log4cplus\loglevel.h"

#include <iostream>

using namespace std;

using namespace log4cplus;

int main(){

SharedAppenderPtr _append(new ConsoleAppender());

_append->setName("test");

Logger::getRoot().addAppender(_append);

Logger root = Logger::getRoot();

Logger test = Logger::getInstance("test");

Logger subTest = Logger::getInstance("test.subtest");

LogLevelManager& llm = getLogLevelManager();

cout << endl << "Before Setting, Default LogLevel" << endl;

LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))

cout << endl << "Setting test.subtest to WARN" << endl;

subTest.setLogLevel(WARN_LOG_LEVEL);

LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))

cout << endl << "Setting test.subtest to TRACE" << endl;

test.setLogLevel(TRACE_LOG_LEVEL);

LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))

cout << endl << "Setting test.subtest to NO_LEVEL" << endl;

subTest.setLogLevel(NOT_SET_LOG_LEVEL);

LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()) << '\n')

cout << "create a logger test_bak, named \"test_\", too. " << endl;

Logger test_bak = Logger::getInstance("test");

cout << "Setting test to INFO, so test_bak also be set to INFO" << endl; test.setLogLevel(INFO_LOG_LEVEL);

LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))

LOG4CPLUS_FATAL(root, "test_bak: " << llm.toString(test_bak.getChainedLogLevel()))

return 0;

}

输出结果:

Before Setting, Default LogLevel

FATAL - root: DEBUG

FATAL - test: DEBUG

FATAL - test.subtest: DEBUG

Setting test.subtest to WARN

FATAL - root: DEBUG

FATAL - test: DEBUG

FATAL - test.subtest: WARN

Setting test.subtest to TRACE

FATAL - root: DEBUG

FATAL - test: TRACE

FATAL - test.subtest: WARN

Setting test.subtest to NO_LEVEL

FATAL - root: DEBUG

FATAL - test: TRACE

FATAL - test.subtest: TRACE

create a logger test_bak, named "test_", too.

Setting test to INFO, so test_bak also be set to INFO

FATAL - test: INFO

FATAL - test_bak: INFO

下面的例子演示了如何通过设置LogLevel来控制用户的log信息输出:

〖例7

#include "log4cplus\logger.h"

#include "log4cplus\consoleappender.h"

#include "log4cplus\loglevel.h"

#include <iostream>

using namespace std;

using namespace log4cplus;

void ShowMsg(void){

LOG4CPLUS_TRACE(Logger::getRoot(),"info")

LOG4CPLUS_DEBUG(Logger::getRoot(),"info")

LOG4CPLUS_INFO(Logger::getRoot(),"info")

LOG4CPLUS_WARN(Logger::getRoot(),"info")

LOG4CPLUS_ERROR(Logger::getRoot(),"info")

LOG4CPLUS_FATAL(Logger::getRoot(),"info")}

int main(){

SharedAppenderPtr _append(new ConsoleAppender());

_append->setName("test");

_append->setLayout(std::auto_ptr<Layout> (new TTCCLayout()));

Logger root = Logger::getRoot();

root.addAppender(_append);

cout << endl << "all-log allowed" << endl;

root.setLogLevel(ALL_LOG_LEVEL);

ShowMsg();

cout << endl << "trace-log and above allowed" << endl;

root.setLogLevel(TRACE_LOG_LEVEL);

ShowMsg();

cout << endl << "debug-log and above allowed" << endl;

root.setLogLevel(DEBUG_LOG_LEVEL);

ShowMsg();

cout << endl << "info-log and above allowed" << endl;

root.setLogLevel(INFO_LOG_LEVEL);

ShowMsg();

cout << endl << "warn-log and above allowed" << endl;

root.setLogLevel(WARN_LOG_LEVEL);

ShowMsg();

cout << endl << "error-log and above allowed" << endl;

root.setLogLevel(ERROR_LOG_LEVEL);

ShowMsg();

cout << endl << "fatal-log and above allowed" << endl;

root.setLogLevel(FATAL_LOG_LEVEL);

ShowMsg();

cout << endl << "log disabled" << endl;

root.setLogLevel(OFF_LOG_LEVEL);

ShowMsg();

return 0;

}

输出结果:

all-log allowed

10-17-04 10:11:40,587 [1075298944] TRACE root <> - info

10-17-04 10:11:40,590 [1075298944] DEBUG root <> - info

10-17-04 10:11:40,591 [1075298944] INFO root <> - info

10-17-04 10:11:40,591 [1075298944] WARN root <> - info

10-17-04 10:11:40,592 [1075298944] ERROR root <> - info

10-17-04 10:11:40,592 [1075298944] FATAL root <> - info

trace-log and above allowed

10-17-04 10:11:40,593 [1075298944] TRACE root <> - info

10-17-04 10:11:40,593 [1075298944] DEBUG root <> - info

10-17-04 10:11:40,594 [1075298944] INFO root <> - info

10-17-04 10:11:40,594 [1075298944] WARN root <> - info

10-17-04 10:11:40,594 [1075298944] ERROR root <> - info

10-17-04 10:11:40,594 [1075298944] FATAL root <> - info

debug-log and above allowed

10-17-04 10:11:40,595 [1075298944] DEBUG root <> - info

10-17-04 10:11:40,595 [1075298944] INFO root <> - info

10-17-04 10:11:40,596 [1075298944] WARN root <> - info

10-17-04 10:11:40,596 [1075298944] ERROR root <> - info

10-17-04 10:11:40,596 [1075298944] FATAL root <> - info

info-log and above allowed

10-17-04 10:11:40,597 [1075298944] INFO root <> - info

10-17-04 10:11:40,597 [1075298944] WARN root <> - info

10-17-04 10:11:40,597 [1075298944] ERROR root <> - info

10-17-04 10:11:40,598 [1075298944] FATAL root <> - info

warn-log and above allowed

10-17-04 10:11:40,598 [1075298944] WARN root <> - info

10-17-04 10:11:40,598 [1075298944] ERROR root <> - info

10-17-04 10:11:40,599 [1075298944] FATAL root <> - info

error-log and above allowed

10-17-04 10:11:40,599 [1075298944] ERROR root <> - info

10-17-04 10:11:40,600 [1075298944] FATAL root <> - info

fatal-log and above allowed

10-17-04 10:11:40,600 [1075298944] FATAL root <> - info

log disabled

用户也可以自行定义LogLevel,操作比较简单,首先要定义LEVEL值,比如HELLO_LOG_LEVEL定义如下:

/* DEBUG_LOG_LEVEL < HELLO_LOG_LEVEL < INFO_LOG_LEVEL */

const LogLevel HELLO_LOG_LEVEL = 15000;

然后定义以下宏即可:

/* define MACRO LOG4CPLUS_HELLO */

#define LOG4CPLUS_HELLO(logger, logEvent) \

if(logger.isEnabledFor(HELLO_LOG_LEVEL)) { \

log4cplus::tostringstream _log4cplus_buf; \

_log4cplus_buf << logEvent; \

logger.forcedLog(HELLO_LOG_LEVEL, _log4cplus_buf.str(), __FILE__, __LINE__); \

}

不过log4cplus没有提供给用户一个接口来实现LEVEL值与字符串的转换,所以当带格式输出LogLevel字符串时候会显示"UNKNOWN", 不够理想。比如用TTCCLayout控制输出的结果可能会如下所示:

10-17-04 11:17:51,124 [1075298944] UNKNOWN root <> - info

而不是期望的以下结果:

10-17-04 11:17:51,124 [1075298944] HELLO root <> - info

要想实现第二种结果,按照log4cplus现有的接口机制,只能改其源代码后重新编译,方法是在loglevel.cxx中加入:

#define _HELLO_STRING LOG4CPLUS_TEXT("HELLO")

然后修改log4cplus::tstring defaultLogLevelToStringMethod(LogLevel ll)函数,增加一个判断:

case HELLO_LOG_LEVEL: return _HELLO_STRING;

重新编译log4cplus源代码后生成库文件,再使用时即可实现满意效果。

### 调试模式 ###

即通过loglog来控制输出调试、警告或错误信息,见例4,这里不再赘述。

### 基于脚本配置来过滤log信息 ###

除了通过程序实现对log环境的配置之外,log4cplus通过PropertyConfigurator类实现了基于脚本配置的功能。通过脚本可以完成对loggerappenderlayout的配置,因此可以解决怎样输出,输出到哪里的问题,我将在全文的最后一部分中提到多线程环境中如何利用脚本配置来配合实现性能测试,本节将重点介绍基脚本实现过滤log信息的功能。

首先简单介绍一下脚本的语法规则:

包括Appender的配置语法和logger的配置语法,其中:

1.Appender的配置语法:

1)设置名称:

/*设置方法*/

log4cplus.appender.appenderName=fully.qualified.name.of.appender.class

例如(列举了所有可能的Appender,其中SocketAppender后面会讲到):log4cplus.appender.append_1=log4cplus::ConsoleAppender

log4cplus.appender.append_2=log4cplus::FileAppender

log4cplus.appender.append_3=log4cplus::RollingFileAppender

log4cplus.appender.append_4=log4cplus::DailyRollingFileAppender

log4cplus.appender.append_4=log4cplus::SocketAppender

2)设置Filter

包括选择过滤器和设置过滤条件,可选择的过滤器包括:LogLevelMatchFilterLogLevelRangeFilter、和StringMatchFilter

LogLevelMatchFilter来说,过滤条件包括LogLevelToMatchAcceptOnMatchtrue|false), 只有当log信息的LogLevel值与LogLevelToMatch相同,且AcceptOnMatchtrue时才会匹配。

LogLevelRangeFilter来说,过滤条件包括LogLevelMinLogLevelMaxAcceptOnMatch,只有当log信息的LogLevelLogLevelMinLogLevelMax之间同时AcceptOnMatchtrue时才会匹配。

StringMatchFilter来说,过滤条件包括StringToMatchAcceptOnMatch,只有当log信息的LogLevel值与StringToMatch对应的LogLevel值与相同, 且AcceptOnMatchtrue时会匹配。

过滤条件处理机制类似于IPTABLEResponsibility chain,(即先deny、再allow)不过执行顺序刚好相反,后写的条件会被先执行,比如:

log4cplus.appender.append_1.filters.1=log4cplus::spi::LogLevelMatchFilter

log4cplus.appender.append_1.filters.1.LogLevelToMatch=TRACE

log4cplus.appender.append_1.filters.1.AcceptOnMatch=true

#log4cplus.appender.append_1.filters.2=log4cplus::spi::DenyAllFilter

会首先执行filters.2的过滤条件,关闭所有过滤器,然后执行filters.1,仅匹配TRACE信息。

3)设置Layout

可以选择不设置、TTCCLayout、或PatternLayout

如果不设置,会输出简单格式的log信息。

设置TTCCLayout如下所示:

log4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout

设置PatternLayout如下所示:log4cplus.appender.append_1.layout=log4cplus::PatternLayout

log4cplus.appender.append_1.layout.ConversionPattern=%d{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p - %m%n

2.logger的配置语法

包括rootLoggernon-root logger

对于rootLogger来说:

log4cplus.rootLogger=[LogLevel], appenderName, appenderName, ...

对于non-root logger来说:

log4cplus.logger.logger_name=[LogLevel|INHERITED], appenderName, appenderName, ...

脚本方式使用起来非常简单,只要首先加载配置即可(urconfig.properties是自行定义的配置文件):

PropertyConfigurator::doConfigure("urconfig.properties");

下面我们通过例子体会一下log4cplus强大的基于脚本过滤log信息的功能。

〖例8

/*

* urconfig.properties

*/

log4cplus.rootLogger=TRACE, ALL_MSGS, TRACE_MSGS, DEBUG_INFO_MSGS, FATAL_MSGS

log4cplus.appender.ALL_MSGS=log4cplus::RollingFileAppender

log4cplus.appender.ALL_MSGS.File=all_msgs.log

log4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout

log4cplus.appender.TRACE_MSGS=log4cplus::RollingFileAppender

log4cplus.appender.TRACE_MSGS.File=trace_msgs.log

log4cplus.appender.TRACE_MSGS.layout=log4cplus::TTCCLayout

log4cplus.appender.TRACE_MSGS.filters.1=log4cplus::spi::LogLevelMatchFilter

log4cplus.appender.TRACE_MSGS.filters.1.LogLevelToMatch=TRACE

log4cplus.appender.TRACE_MSGS.filters.1.AcceptOnMatch=true

log4cplus.appender.TRACE_MSGS.filters.2=log4cplus::spi::DenyAllFilter

log4cplus.appender.DEBUG_INFO_MSGS=log4cplus::RollingFileAppender

log4cplus.appender.DEBUG_INFO_MSGS.File=debug_info_msgs.log

log4cplus.appender.DEBUG_INFO_MSGS.layout=log4cplus::TTCCLayout

log4cplus.appender.DEBUG_INFO_MSGS.filters.1=log4cplus::spi::LogLevelRangeFilter

log4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMin=DEBUG

log4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMax=INFO

log4cplus.appender.DEBUG_INFO_MSGS.filters.1.AcceptOnMatch=true

log4cplus.appender.DEBUG_INFO_MSGS.filters.2=log4cplus::spi::DenyAllFilter

log4cplus.appender.FATAL_MSGS=log4cplus::RollingFileAppender

log4cplus.appender.FATAL_MSGS.File=fatal_msgs.log

log4cplus.appender.FATAL_MSGS.layout=log4cplus::TTCCLayout

log4cplus.appender.FATAL_MSGS.filters.1=log4cplus::spi::StringMatchFilter

log4cplus.appender.FATAL_MSGS.filters.1.StringToMatch=FATAL

log4cplus.appender.FATAL_MSGS.filters.1.AcceptOnMatch=true

log4cplus.appender.FATAL_MSGS.filters.2=log4cplus::spi::DenyAllFilter

/*

* main.cpp

*/

#include <log4cplus\logger.h >

#include <log4cplus\configurator.h >

#include <log4cplus\helpers\stringhelper.h >

using namespace log4cplus;

static Logger logger = Logger::getInstance("log");

void printDebug(){

LOG4CPLUS_TRACE_METHOD(logger, "::printDebug()");

LOG4CPLUS_DEBUG(logger, "This is a DEBUG message");

LOG4CPLUS_INFO(logger, "This is a INFO message");

LOG4CPLUS_WARN(logger, "This is a WARN message");

LOG4CPLUS_ERROR(logger, "This is a ERROR message");

LOG4CPLUS_FATAL(logger, "This is a FATAL message");

}

int main()

{

Logger root = Logger::getRoot();

PropertyConfigurator::doConfigure("urconfig.properties");

printDebug();

return 0;

}

运行结果:

1. all_msgs.log

10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug()

10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message

10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message

10-17-04 14:55:25,873 [1075298944] WARN log <> - This is a WARN message

10-17-04 14:55:25,874 [1075298944] ERROR log <> - This is a ERROR message

10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message

10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT: ::printDebug()

2. trace_msgs.log

10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug()

10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT: ::printDebug()

3. debug_info_msgs.log

10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message

10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message

4. fatal_msgs.log10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message

本部分详细介绍了如何有选择地控制log信息的输出,最后一部分我们将介绍一下多线程、和C/S模式下该如何操作,顺便提一下NDC的概念。

log4cplus在很多方面做的都很出色,但是使用过程有些地方感觉不爽。在继续吹捧之前我先把不爽之处

稍微提一提,然后继续介绍关于线程和套接字的知识。

### 一些可以改进之处 ###

1. 用户自定义LogLevel的实现机制不够开放在第五篇中曾经介绍过如何实现用户自行定义LogLevel,为了实现比较理想的效果,甚至还需要改log4cplus的源代码。:(

2. 生成Logger对象的机制可以改进

我在使用时候,经常需要在不同的文件、函数中操作同一个logger,虽然log4cplus实现了树状存储以及根据名称生成Logger,却没有充分利用这样的特点确保同一个名称对应的logger对象的唯一性,比如以下代码:

... ...

Logger logger1 = Logger::getInstance("test");

Logger logger2 = Logger::getInstance("test");

Logger * plogger1 = &logger1;

Logger * plogger2 = &logger2;

std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl;

... ...

运行结果:

plogger1: 0xbfffe5a0

plogger2: 0xbfffe580

从结果可以看出,明明是同一个Logger,但每次调用都会产生一个Logger副本,虽然结果是正确的(因为将存储和操作分开了),但是资源有些浪费,我看了一下log4cplus的代码,其实可以按照如下方式实现(示意性的):

#include <iostream>

#include <string>

#include

/* forward declaration */

class Logger;

class LoggerContainer

{

public:

~LoggerContainer();

Logger * getinstance(const std::string & strLogger);

private:

typedef std::map<:string,> LoggerMap;

LoggerMap loggerPtrs;

};

class Logger

{

public:

Logger() {std::cout << "ctor of Logger " << std::endl; }

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

static Logger * getInstance( const std::string & strLogger)

{

static LoggerContainer defaultLoggerContainer;

return defaultLoggerContainer.getinstance(strLogger);

}

};

LoggerContainer::~LoggerContainer(){

/* release all ptr in LoggerMap */

LoggerMap::iterator itr = loggerPtrs.begin();

for( ; itr != loggerPtrs.end(); ++itr ){

delete (*itr).second;

}

}

Logger * LoggerContainer::getinstance(const std::string & strLogger){

LoggerMap::iterator itr = loggerPtrs.find(strLogger);

if(itr != loggerPtrs.end())

{

/* logger exist, just return it */

return (*itr).second;

}

Else

{

/* return a new logger */

Logger * plogger = new Logger();

loggerPtrs.insert(std::make_pair(strLogger, plogger));

return plogger;

}

}

int main()

{

Logger * plogger1 = Logger::getInstance("test");

Logger * plogger2 = Logger::getInstance("test");

std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl;

return 0;

}

运行结果:

ctor of Logger

plogger1: 0x804fc30

plogger2: 0x804fc30

dtor of Logger

这里的LoggerContainer相当于log4cplus中的Hierarchy类,结果可以看出,通过同一个名称可以获取相同的Logger实例。

还有一些小毛病比如RollingFileAppenderDailyRollingFileAppender的参数输入顺序可以调整成统一方式等等,就不细说了。本部分提到了使用log4cplus时候感觉不爽的地方,最后一部分将介绍一下log4cplus中线程和套接字实现情况