(本文翻译自:http://pocoproject.org/slides/110-Logging.pdf)
- 消息,日志和通道
- 格式化
- 性能注意事项
1 日志框架
2 消息类(Logger Message)
POCO使用Poco::Message 对象存储和传递日志信息。
#include "Poco/Message.h"
日志消息(Message)包括:
- 优先级(priority)
- 消息源(source)
- 文本(text)
- 时间戳(timestamp)
- 进程和线程标识符
- 可选参数<名称-值>对
2.1 优先级(Message Priority)
POCO的定义八个消息优先级:
- PRIO_FATAL (最高优先级)
- PRIO_CRITICAL
- PRIO_ERROR
- PRIO_WARNING
- PRIO_NOTICE
- PRIO_INFORMATION
- PRIO_DEBUG
- PRIO_TRACE (最低优先级)
API接口:
- void setPriority(Priority prio)
- Priority getPriority() const
2.2 源(Message Source)
描述一个日志的消息源
日志名称通常用于设置消息源
因此需要明智的选择日志名称:
- 生成消息的类名称;
- 或者,生成消息的子系统名称
API接口:
- void setSource(const std::string& source)
- const std::string& getSource() const
2.3 文本(Message Text)
实际的消息将被记录。
没有格式,长度等方面的要求。
在日志显示前可能被格式化对象(formatter)修改。
API接口
- void setText(const std::string& text)
- const std::string& getText() const
2.4 时间戳(MessageTimestamp)
创建的日期和时间信息,微秒精度。
由Poco::Message构造函数自动初始化为当前日期时间。
API接口:
- void setTime(const Timestamp& time)
- const Timestamp& getTime() const
2.5 进程与线程标识符(Process and Thread Identifier)
进程标识符(PID),long int值,存储系统的进程ID。
线程标识符(TID),long int值,存储当前线程的序列号。
此外,存储当前线程名称。
进程标识符(PID)、线程标识符(TID)和线程名称在Poco::Message构造函数中被初始化。
API接口:
- void setThread(const std::string& threadName)
- const std::string& getThread() const
- void setTid(long tid)
- long getTid() const
- void setPid(long pid)
- long getPid() const
2.6 参数(Message Parameters)
消息可以存储任意数量的<名称-值>对。
名称和值可以是任意的字符串。
消息参数可被格式化(formatter)对象应用。
使用索引操作符访问的消息参数。
3日志类(Logger)
POCO::Logger 是日志框架的主入口点。
#include "Poco/Logger.h"
应用程序使用POCO::Logger类的实例生成日志消息。
每一个日志附属通道(channel),通道用于传递消息。
每一个日志都拥有一个名称,名称用于表示消息源。日志名称设置有不能改变。
日志使用优先级过滤消息:
- 只有优先级大于等于配置优先级的消息,才被传递。
- 例如:一个日志的优先级设置为PRIO_ERROR,那么只有优先级为PRIO_ERROR, PRIO_CRITICAL 或 PRIO_FATAL的消息被传递,PRIO_WARNING或更低优先级的消息将被丢弃。
3.1层次结构(Logger Hierarchy)
基于日志名称,组成“树型结构”。
日志名称可由一个或多个日志组件组成,各个日志组件有自己独立的名称范围。
每个日志组件的名称都会包含上级日志组件的名称。
树型结构的根是名称为空的日志组件。
树型结构没有最大深度的限制。
日志层次的例子:
3.2层次结构继承(Logger Hierarchy Inheritance)
日志组件将继承它的上级日志组件的级别和通道在上面的例子中,"HTTPServer.RequestHandler.CGI" 继承"HTTPServer.RequestHandler"的级别和通道
一旦日志组件的实例被创建,新建的实例与任何上下级组件无关。也就是说,改变日志组件的级别和通道,不会影响到已经创建的下级组件的实例。
层次路径上的日志组件的日志级别和通道可能只要在上级组件上设置一次。
日志创建与记录接口
- void log(const Message& msg) //如果消息的优先级不小于日志级别,消息将被传递到传输通道。消息保持不变。
- void log(const Exception& exc) //使用exc创建和记录一条PRIO_ERROR优先级的消息
- void fatal(const std::string& text)
- void critical(const std::string& text)
- void error(const std::string& text)
- void warning(const std::string& text)
- void notice(const std::string& text)
- void information(const std::string& text)
- void debug(const std::string& text)
- void trace(const std::string& text) //使用text创建和记录一条指定优先级的消息
- void dump(const std::string& text,const void* buffer, int length,
Message::Priority prio = Message::PRIO_DEBUG) //使用dump buffer和text创建和记录一条prio优先级的消息
日志级别查询接口
- bool is(int level) const //是否是level级别
- bool fatal() const
- bool critical() const
- bool error() const
- bool warning() const
- bool notice() const
- bool information() const
- bool debug() const
- bool trace() const
3.3访问日志对象
POCO库在内部全局管理日志。
用户不用创建日志对象,只要从POCO库获取日志引用。
POCO根据需要创建新的日志。
static Logger& get(const std::string& name),获取POCO提供的名为name的日志引用,这个日志由POCO内部创建。
保存获得的日志引用是安全的。
举例:
#include "Poco/Logger.h"
using Poco::Logger;
int main(int argc, char** argv)
{
Logger& logger = Logger::get("TestLogger");
logger.information("This is an informational message");
logger.warning("This is a warning message");
return 0;
}
4 通道类(Channels)
Poco::Channel的子类负责传递消息到目的地(如控制台、日志文件)。
Poco::Logger也是Poco::Channel的子类,Poco::Logger与Poco::Channel连接。
POCO提供有多个Poco::Channel子类,使用不同的子类可以把消息发送到控制台、日志文件或系统日志服务。
用户可以定义自己的Channel类。
Channel使用应用计数进行内存管理
4.1 通道属性
通道提供任意数量的可配置属性(“名称-值”对)。
通道属性操作接口在Poco::Configurable中定义,Poco::Configurable是Poco::Channel超类。
void setProperty(const std::string& name, const std::string& value)
std::string getProperty(const sdt::string& name)
4.2 控制台通道(ConsoleChannel)
Poco::ConsoleChannel是最基本的通道实现。
#include "Poco/ConsoleChannel.h"
将消息文本输出到标准输出(std::clog)
不具备配置属性
根日志的默认通道
4.3 Windows控制台通道(WindowsConsoleChannel)
Poco::WindowsConsoleChannel与Poco::ConsoleChannel相似。
#include "Poco/WindowsConsoleChannel.h"
将消息文本输出到windows控制台
不具备配置属性
支持UTF-8编码消息文本
4.4 丢弃通道(NullChannel)
Poco::NullChannel丢弃所有消息。
#include "Poco/NullChannel.h"
忽略所有setProperty()设置的属性。
4.5 简单文件通道(SimpleFileChannel)
Poco::SimpleFileChannel提供一种输出日志到文件的简单实现。
在日志文件中,追加一行消息文本。
支持文件轮换记录,主文件大小超过做大值(可配置)时,新建或替换到备用日志文件。备用文件超过最大值时,替换到主文件。
属性 说明
path 主文件路径
secondaryPath 备用文件路径。默认为<path>.1
rotation 文件轮换模式
never: 不轮换(默认)
<n>: 轮换文件最大<n>字节
<n>K: 轮换文件最大<n>千字节
<n>M: 轮换文件最大<n>兆字节
举例:
#include "Poco/Logger.h"
#include "Poco/SimpleFileChannel.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::SimpleFileChannel;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
AutoPtr<SimpleFileChannel> pChannel(new SimpleFileChannel);
pChannel->setProperty("path", "sample.log");
pChannel->setProperty("rotation", "2 K");
Logger::root().setChannel(pChannel);
Logger& logger = Logger::get("TestLogger"); // inherits root channel
for (int i = 0; i < 100; ++i)
logger.information("Testing SimpleFileChannel");
return 0;
}
4.6 文件通道(FileChannel)
Poco::FileChannel提供一个成熟的文件日志框架。
#include "Poco/FileChannel.h"
在文件中追加一行消息文本。
文件轮换支持基于文件大小或时间间隔方式进行。
支持文件自动归档、压缩和清除
属性 说明
path 主文件路径
rotation 文件轮换模式
never: 不轮换(默认)
<n>: 轮换文件最大<n>字节
<n>K: 轮换文件最大<n>千字节
<n>M: 轮换文件最大<n>兆字节
[day,][hh:][mm]: 轮换文件时间间隔:天/时间
daily/weekly/monthly: 轮换文件时间间隔:每天/每周/每月
<n>hours/weeks/months:轮换文件时间间隔:<n>小时/周/月
archive 归档名称:
number: 自动累加计数,从0开始。
timestamp: 时间戳(YYYYMMDDHHMMSS)追加到日志文件名称。
times 指定时钟格式
compress 自动压缩标志。true or false
purgeAge 指定归档文件的清理期限。超出清理期限的日志文件将被清除。
<n>[seconds]/minutes/hours/days/week/months
purgeCount 指定归档文件的最大数量。文件数量达到最大值,最老得归档文件将被清除。
4.7 事件日志通道(EventLogChannel)
Poco::EventLogChannel仅用于Windows NT系统,输出日志到Windows Event Log服务。
#include "Poco/EventLogChannel.h"
Poco::EventLogChannel将PocoFoundation.dll注册到windows,作为Windows Event Log服务的消息源。
当查看 Windows Event Log时,Event Viewer应用程序必须能找到PocoFoundation.dll,否则日志消息会被丢弃。
属性 说明
name 事件源。通常使用应用程序名称。
loghost,host Windows Event Log服务器名称。默认是localhost。
logfile 日志文件名称。默认是“应用程序”
4.8 Syslog通道(SyslogChannel)
Poco::SyslogChannel仅用于Unix系统,输出日志到本地Syslog服务。
#include "Poco/SyslogChannel.h"
Net库提供一个RemoteSyslogChannel类,支持输出日志到远程Syslog服务,使用基于UDP的Syslog协议。
属性设置参看相关文档。
4.9 异步通道(AsyncChannel)
Poco::AsyncChannel提供运行独立的日志发送线程。将日消息志生成线程和日志消息发送线程分离。
日志消息存储在FIFO队列中。
独立的日志发送线程从消息队列中提取消息,并发送到其它通道。
4.10 分发通道(SplitterChannel)
Poco::SplitterChannel将消息分发到一个或多个通道。
#include "Poco/SplitterChannel.h"
void addChannel(Channel* pChannel)//添加一个新通道到Poco::SplitterChannel
5 日志流(Logging Streams)
5.1 日志流类(LogStream)
Poco::LogStream给Logger提供一个流(ostream)接口。
#include "Poco/LogStream.h"
所有流(ostream)的特性都能用于格式化日志消息。
日志流的消息必须以std::endl(或CR、LF字符)结尾。
- 操作接口:
LogStream& priority(Message::Priority prio) - LogStream& fatal()
- LogStream& critical()
- LogStream& error()
- LogStream& warning()
- LogStream& notice()
- LogStream& information()
- LogStream& debug()
- LogStream& trace()
举例:
#include "Poco/LogStream.h"
#include "Poco/Logger.h"
using Poco::Logger;
using Poco::LogStream;
int main(int argc, char** argv)
{
Logger& logger = Logger::get("TestLogger");
LogStream lstr(logger);
lstr << "This is a test" << std::endl;
return 0;
}
6日志消息格式化(Message Formatting)
6.1 格式化通道类(FormattingChannel)与格式化类(Formatter)
Poco::FormattingChannel与Poco::Formatter用于格式化日志消息。
Poco::FormattingChannel收到消息后,将消息先通过Poco::Formatter处理,然后输出到下一个通道。
#include "Poco/FormattingChannel.h"
#include "Poco/Formatter.h"
Poco::Formatter i是所有格式化类的基类。
channels,formatters都是可配置的。
6.2 模式格式化类(PatternFormatter)
Poco::PatternFormatter使用打印模式格式化消息。
#include "Poco/PatternFormatter.h"
举例:
#include "Poco/ConsoleChannel.h"
#include "Poco/FormattingChannel.h"
#include "Poco/PatternFormatter.h"
#include "Poco/Logger.h"
#include "Poco/AutoPtr.h"
using Poco::ConsoleChannel;
using Poco::FormattingChannel;
using Poco::PatternFormatter;
using Poco::Logger;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
AutoPtr<PatternFormatter> pPF(new PatternFormatter);
pPF->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");
AutoPtr<FormattingChannel> pFC(new FormattingChannel(pPF, pCons));
Logger::root().setChannel(pFC);
Logger::get("TestChannel").information("This is a test");
return 0;
}
7 性能注意事项
创建消息会消耗一些时间(需要判断当前时间、进程ID和线程ID。
创建有意义的消息可能消耗更多的时间,因为需要文本拼接、格式化等必须的操作。
消息文本通过通道链传输。
FormattingChannel和AsyncChannel会创建消息copy。
记录日志经常会为每个日志启用或禁用日志(或者,更正确来说,设置日志级别),如果你拥有一个日志引用,那么使用日志引用来判断日志级别,是一个常数时间操作。
通常,只需要在应用程序中做一次获取日志引用(Logger::get())操作。例如在类的构造函数中获取日志引用,然后使用日志对象的引用。
用户应当避免频繁调用Logger::get()。最好是调用一次、保存下来,然后使用。
日志的性能依赖于通道。
构造日志消息需要一定的时间消耗。
因此,建议在构造消息前,首先使用 is(), fatal(), critical()等接口核实是否需要构建。
POCO提供用于核实构造消息的宏:poco_fatal(msg), poco_critical(msg), poco_error(msg)等等。