Log4Qt快速入门——Log4Qt日志格式化源码解析

一、Layout

1、Layout简介

Log4Qt提供了多种Layout对象,用于格式化日志输出,指定日志级别、线程名称、Logger名称、日期时间等信息。 Layout类是Log4Qt API中的抽象类。 PatternLayout:根据一个模式字符串输出日志事件; SimpleLayout:输出日志事件的级别和消息; TTCCLayout:输出日志事件的时间、线程名称、Logger名称和嵌套的诊断上下文信息。 PatternLayout和TTCCLayout通过PatternFormatter来实现格式化。当PatternFormatter解析模式字符串时,会根据发现的信息创建一个PatternConverter链,每个PatternConverter会处理LoggingEvent的某个成员。 转换字符:用于指定数据的类型,例如:类别、级别、日期、线程名称。Log4Qt中的转换字符有: c:Logger 名称。 d{format_string}:日期。参数 format_string 可选,用于格式化日期。 m:消息内容 p:消息级别 r:启动程序的相对时间 t:线程名称 x:NDC(嵌套的诊断上下文)名称 X:MDC(映射的诊断上下文)名称 F:文件名称 M:方法名称 L:行号 l:位置信息 n:平台相关的行分隔符,Windows:\r\n,Linux:\n

2、NDC简介

NDC(Nested Diagnostic Context)即嵌套诊断上下文,是log4J用于存储上下文信息(context information)的类,NDC采用栈的机制push和pop上下文,每个线程有独立的上下文,如果要存储的上下文信息是堆栈式的在选择NDC。 NDC常用接口如下: static QString pop(); 将NDC栈顶元素弹出 static void push(const QString &rMessage); 将rMessage压入NDC栈 static void clear(); 清空NDC栈 static int depth(); 获取NDC栈的深度 static void setMaxDepth(int maxDepth); 设置NDC栈的最大深度 static QString peek(); 获取NDC栈顶的数据 通常在context或servlet入口将线程相关的应用信息保存,在需要记录日志log信息等时将信息输出,并保证在当前线程的结束或servlet的出口使用clear()移除,防止信息泄露。

3、MDC简介

MDC(Mapped Diagnositc Context)即映射诊断上下文,是log4J用于存储上下文信息(context information)的类,MDC内部采用Hash容器实现,是线程独立的,但一个子线程会自动获得一个父线程MDC的copy,如果要存储的上下文信息是key/value式的选择MDC。 MDC常用接口如下: static void put(const QString &rKey, const QString &rValue); 将rKey/rValue数据存储到Hash容器中 static void remove(const QString &rKey); 从Hash容器中删除key为rKey的key/value static QString get(const QString &rKey); 从Hash容器中获取key为rKey的key/value static QHash<QString, QString> context(); 获取Hash容器中所有的内容

4、Layout常用接口

QString footer() const; 获取Layout对象的footer QString header() const; 获取Layout对象的header QString name() const; 获取Layout对象的对象名称 void setFooter(const QString &rFooter); 设置Layout对象的footer void setHeader(const QString &rHeader); 设置Layout对象的header void setName(const QString &rName); 设置Layout对象的对象名称 virtual QString format(const LoggingEvent &rEvent) = 0; Layout对象的日志消息格式化接口,派生类PatternLayout、SimpleLayout、TTCCLayout通过format函数来确定具体要输出信息的格式。

二、PatternLayout

1、PatternLayout 简介

PatternLayout是Layout的一个派生类,如果想生成基于模式匹配的特定格式的日志信息,可以使用PatternLayout来进行格式化。 PatternLayout的枚举ConversionPattern定义了两个常用的模式:

enum ConversionPattern
{
    DEFAULT_CONVERSION_PATTERN,// "%m,%n"
    TTCC_CONVERSION_PATTERN,//"%r [%t] %p %c %x - %m%n"
};

2、PatternLayout常用接口

QString conversionPattern() const; 获取PatternLayout对象的转换模式匹配字符串 void setConversionPattern(const QString &rPattern); 设置PatternLayout对象的转换模式匹配字符串 void setConversionPattern(ConversionPattern conversionPattern); 设置PatternLayout对象的转换模式匹配方式

3、format格式化实现

QString PatternLayout::format(const LoggingEvent &rEvent)
{
	Q_ASSERT_X(mpPatternFormatter, "PatternLayout::format()", "mpPatternConverter must not be null");

	return mpPatternFormatter->format(rEvent);
}

调用PatternFormatter格式化函数根据模式转换器链表的每个模式转换器格式化信息。

QString PatternFormatter::format(const LoggingEvent &rLoggingEvent) const
	{
		QString result;
		PatternConverter *p_converter;
		Q_FOREACH(p_converter, mPatternConverters)
			p_converter->format(result, rLoggingEvent);
		return result;
	}

PatternFormatter使用模式匹配字符串创建一个模式转换器mPatternConverters,每个转换符对应一个模式转换器。

	void PatternFormatter::createConverter(const QChar &rChar, 
	                                       const FormattingInfo &rFormattingInfo, 
	                                       const QString &rOption)
	{
		Q_ASSERT_X(mConversionCharacters.indexOf(rChar) >= 0, "PatternFormatter::createConverter", "Unknown conversion character" );
	
	    LogError e("Creating Converter for character '%1' min %2, max %3, left %4 and option '%5'");
	    e << QString(rChar)
	      << FormattingInfo::intToString(rFormattingInfo.mMinLength) 
	      << FormattingInfo::intToString(rFormattingInfo.mMaxLength) 
	      << rFormattingInfo.mLeftAligned 
	      << rOption;
		logger()->trace(e);
	
		switch (rChar.toLatin1())
		{
			case 'c':
				mPatternConverters << new LoggerPatternConverter(rFormattingInfo, 
	                                                             parseIntegerOption(rOption));
				break;
			case 'd':
			{
				QString option = rOption;
				if (rOption.isEmpty())
	               option = QLatin1String("ISO8601");
				mPatternConverters << new DatePatternConverter(rFormattingInfo, 
	                                                           option); 
				break;
			}
			case 'm':
				mPatternConverters << new BasicPatternConverter(rFormattingInfo,
	                                                            BasicPatternConverter::MESSAGE_CONVERTER); 
				break;
			case 'p':
				mPatternConverters << new BasicPatternConverter(rFormattingInfo,
	                                                            BasicPatternConverter::LEVEL_CONVERTER); 
				break;
			case 'r':
				mPatternConverters << new DatePatternConverter(rFormattingInfo,
	                                                           QLatin1String("RELATIVE")); 
				break;
			case 't':
				mPatternConverters << new BasicPatternConverter(rFormattingInfo,
	                                                            BasicPatternConverter::THREAD_CONVERTER); 
				break;
			case 'x':
				mPatternConverters << new BasicPatternConverter(rFormattingInfo,
	                                                            BasicPatternConverter::NDC_CONVERTER); 
				break;
			case 'X':
				mPatternConverters << new MDCPatternConverter(rFormattingInfo, 
	                                                          rOption); 
				break;
			default:
				Q_ASSERT_X(false, "PatternFormatter::createConverter", "Unknown pattern character");
		}
	}

4、PatternLayout使用示例

#include <QCoreApplication>
#include <log4qt/logger.h>
#include <log4qt/patternlayout.h>
#include <log4qt/consoleappender.h>
#include <log4qt/loggerrepository.h>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 获取rootLogger
    Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
    // 创建PatternLayout(根据模式字符串输出日志事件)
    Log4Qt::PatternLayout *layout = new Log4Qt::PatternLayout();
    // 设置标头信息
    layout->setHeader("----- start -----");
    // 设置页脚信息
    layout->setFooter("----- end -----");
    // 设置转换模式
    layout->setConversionPattern("%d{yyyy-MM-dd hh:mm:ss} [%p] - %m%n");
    // 激活Layout
    layout->activateOptions();

    // 创建ConsoleAppender
    Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET);
    appender->activateOptions();
    logger->addAppender(appender);

    logger->setLevel(Log4Qt::Level::DEBUG_INT);
    logger->debug("Debug, Log4Qt!");
    logger->info("Info, Log4Qt!");

    // 关闭 logger
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();

    return a.exec();
}
// output:
// ----- start -----
// 2018-10-11 21:25:30 [DEBUG] - Debug, Log4Qt!
// 2018-10-11 21:25:30 [INFO] - Info, Log4Qt!
// ----- end -----

5、配置PatternLayout

使用log4qt.properties配置文件配置PatternLayout:

# 定义 rootLogger
log4j.rootLogger=DEBUG, console

# 定义 ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.immediateFlush=true
log4j.appender.console.target=STDOUT_TARGET

# 为 ConsoleAppender 定义 Layout
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.header=----- start -----
log4j.appender.console.layout.footer=----- end -----
log4j.appender.console.layout.conversionPattern=%d{yyyy-MM-dd hh:mm:ss} [%t] %p %c %x - %m%n

程序使用示例:

#include <QCoreApplication>
#include <log4qt/logger.h>
#include <log4qt/loggerrepository.h>
#include <QThread>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QThread::currentThread()->setObjectName("MainThread");

    // 获取rootLogger
    Log4Qt::Logger* logger = Log4Qt::Logger::rootLogger();

    // 打印消息
    logger->debug("Debug, Log4Qt!");
    logger->info("Info, Log4Qt!");

    // 关闭rootLogger
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();

    return a.exec();
}
// output:
// ----- start -----
// 2018-10-11 21:35:58 [MainThread] DEBUG root  - Debug, Log4Qt!
// 2018-10-11 21:35:58 [MainThread] INFO root  - Info, Log4Qt!
// ----- end -----

三、SimpleLayout

1、SimpleLayout简介

SimpleLayout 是Layout的一个派生类,对日志消息的格式化只包含日志的级别和消息内容。

2、format格式化实现

QString SimpleLayout::format(const LoggingEvent &rEvent)
	{
	    return rEvent.level().toString() + QLatin1String(" - ") + rEvent.message() + Layout::endOfLine();
	}

SimpleLayout对日志消息的格式化只包含日志的级别和消息内容。

3、SimpleLayout示例

#include <QCoreApplication>
#include <log4qt/logger.h>
#include <log4qt/simplelayout.h>
#include <log4qt/consoleappender.h>
#include <log4qt/loggerrepository.h>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 创建SimpleLayout
    Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
    Log4Qt::SimpleLayout *layout = new Log4Qt::SimpleLayout();
    layout->setFooter("end");
    layout->setHeader("start");
    layout->activateOptions();

    // 创建ConsoleAppender
    Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET);
    appender->activateOptions();
    logger->addAppender(appender);

    logger->setLevel(Log4Qt::Level::DEBUG_INT);
    logger->debug("Debug, Log4Qt!");
    logger->info("Info, Log4Qt!");

    // 关闭 logger
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();

    return a.exec();
}
// output:
// start
// DEBUG - Debug, Log4Qt!
// INFO - Info, Log4Qt!
// end

4、配置SimpleLayout

使用log4qt.properties配置文件配置SimpleLayout:

# 定义 rootLogger
log4j.rootLogger=DEBUG, console

# 定义 ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.immediateFlush=true
log4j.appender.console.target=STDOUT_TARGET

# 为 ConsoleAppender 定义 Layout
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
log4j.appender.console.layout.header=----- start -----
log4j.appender.console.layout.footer=----- end -----

程序使用示例:

#include <QCoreApplication>
#include <log4qt/logger.h>
#include <log4qt/loggerrepository.h>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 获取 rootLogger
    Log4Qt::Logger* logger = Log4Qt::Logger::rootLogger();

    // 打印消息
    logger->debug("Hello, Log4Qt!");
    logger->info("Hello, Log4Qt!");

    // 关闭 rootLogger
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();

    return a.exec();
}
// output:
// ----- start -----
// DEBUG - Hello, Log4Qt!
// INFO - Hello, Log4Qt!
// ----- end -----

四、TTCCLayout

1、TTCCLayout简介

TTCCLayout 是Layout的一个派生类,负责提供有关日志事件的详细信息,通常包含以下内容: Time:从启动应用程序开始,以毫秒数计算的时间; Thread:调用线程; Category:用于创建日志事件的类别或Logger; Context:NDC信息。NDC信息不会自动包含在LoggingEvent 对象中,必须专门包含NDC信息。因此,Context是TTCCLayout的一个可选输出,即使启用NDC设置,如果LoggingEvent 不包含任何NDC设置,TTCCLayout也可能不会显示任何NDC 数据。  TTCCLayout有多个可选参数,但即使不设置任何选项, TTCCLayout仍然会输出下列信息: Level:日志消息的级别; Message:日志消息本身。 TTCCLayout预定义了多种日期格式,定义如下:

enum DateFormat
{
    NONE,//没有日期格式
    ISO8601,// yyyy-MM-dd hh:mm:ss.zzz
    ABSOLUTE,// HH:mm:ss.zzz
    DATE,//MMM YYYY HH:mm:ss.zzzz
    RELATIVE // 程序启动开始的毫秒数量
};

2、TTCCLayout常用接口

bool categoryPrefixing() const; 获取是否格式化输出Logger名称 bool contextPrinting() const; 获取是否格式化输出NDC信息 QString dateFormat() const; 获取日期格式的字符串 bool threadPrinting() const; 获取是否格式化输出线程名称 void setCategoryPrefixing(bool categoryPrefixing); 设置指定Logger名称是否是格式化输出。 void setContextPrinting(bool contextPrinting); 设置是否格式化输出NDC信息 void setDateFormat(const QString &rDateFormat); 设置rDateFormat字符串表示的日期格式 void setDateFormat(DateFormat dateFormat); 设置DateFormat枚举类型定义的日期格式类型 void setThreadPrinting(bool threadPrinting); 设置是否格式化输出线程名称 virtual QString format(const LoggingEvent &rEvent); 格式化日志信息接口 static void Log4Qt::NDC::push(const QString &rMessage); 将rMessage信息压入NDC栈 static QString Log4Qt::NDC::pop(); 将NDC栈顶信息出栈

3、format格式化实现

QString TTCCLayout::format(const LoggingEvent &rEvent)
{
	Q_ASSERT_X(mpPatternFormatter, "TTCCLayout::format()", "mpPatternConverter must not be null");

	return mpPatternFormatter->format(rEvent);
}

TTCCLayout的格式化与PatternLayout的格式化方法相同,都是根据模式匹配字符串创建的模式转换器链表的每个模式转换器格式化信息。

4、TTCCLayout示例

#include <QCoreApplication>
#include <log4qt/logger.h>
#include <log4qt/ttcclayout.h>
#include <log4qt/consoleappender.h>
#include <log4qt/loggerrepository.h>
#include <log4qt/ndc.h>
#include <QThread>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QThread::currentThread()->setObjectName("MainThread");

    Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
    // 创建TTCCLayout
    Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout();
    layout->setDateFormat("yyyy-mm-dd hh:mm:ss");
    layout->activateOptions();

    // 创建一ConsoleAppender
    Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET);
    appender->activateOptions();
    logger->addAppender(appender);

    logger->setLevel(Log4Qt::Level::DEBUG_INT);
    // NDC信息
    Log4Qt::NDC::push("Thread start");
    logger->debug("Hello, Log4Qt!");
    Log4Qt::NDC::pop();
    logger->info("Hello, Log4Qt!");

    // 关闭 logger
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();

    return a.exec();
}
// output:
// 2018-18-11 23:18:12 [MainThread] DEBUG root Thread start - Hello, Log4Qt!
// 2018-18-11 23:18:12 [MainThread] INFO  root  - Hello, Log4Qt!

5、配置TTCCLayout

使用log4qt.properties配置文件配置TTCCLayout:

# 定义rootLogger
log4j.rootLogger=DEBUG, console

# 定义ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.immediateFlush=true
log4j.appender.console.target=STDOUT_TARGET

# 为ConsoleAppender定义Layout
log4j.appender.console.layout=org.apache.log4j.TTCCLayout
log4j.appender.console.layout.categoryPrefixing=true
log4j.appender.console.layout.contextPrinting=true
log4j.appender.console.layout.threadPrinting=true
log4j.appender.console.layout.dateFormat=ISO8601

程序使用示例:

#include <QCoreApplication>
#include <log4qt/logger.h>
#include <log4qt/ttcclayout.h>
#include <log4qt/consoleappender.h>
#include <log4qt/loggerrepository.h>
#include <log4qt/ndc.h>
#include <QThread>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QThread::currentThread()->setObjectName("MainThread");

    Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();

    // NDC信息
    Log4Qt::NDC::push("Thread start");
    logger->debug("Hello, Log4Qt!");
    Log4Qt::NDC::pop();
    logger->info("Hello, Log4Qt!");

    // 关闭 logger
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();

    return a.exec();
}
// output:
// 2018-10-11 23:22:09.936 [MainThread] DEBUG root Thread start - Hello, Log4Qt!
// 2018-10-11 23:22:09.936 [MainThread] INFO  root  - Hello, Log4Qt!