又一个日志库






4.88/5 (11投票s)
消息日志库
引言
我的天。这个世界真的需要另一个打印跟踪消息的库吗?也许不需要,但这个库解决了那些困扰着数百万其他库的某些问题。
- 这个库允许你使用流式语法,也就是说,你可以这样写消息:
LOG_MSG( "答案是 " << iFoo ) ;
- 你可以精确控制哪些消息被打印,哪些不被打印。许多库都有低、中、高优先级消息的概念,但我不愿再花大量时间在低优先级消息中寻找我真正想要的那一条了。
- 使用一个 ostream 派生类。这有多酷可能需要单独介绍 - 继续看...
良好的日志记录是任何开发人员工具包的重要组成部分。你并不总是有机会从 IDE 运行你的应用程序(例如,NT 服务或 CGI 进程),如果你不确切知道你的程序在做什么,嗯,你就麻烦了!此外,虽然这个库提供了禁用所有日志代码编译的选项,但我一直倾向于在发布版本中保留它。这样,当(而不是如果!)你的客户开始遇到问题时,你只需通过一些隐藏的开关打开日志记录,就能至少对正在发生的事情有所了解。
总而言之,这个库提供了以下功能:
- 日志消息可以在运行时启用/禁用。
- 输出可以发送到任何 ostream 派生目标。
- 可选包含消息编号、日期/时间戳和源文件位置。
- 消息自动缩进(对于嵌套或递归调用很有用)。
- 编译时开关可启用/禁用日志代码的编译。
为什么基于 ostream 的解决方案很酷
首先,为那些不熟悉流的读者做一个快速的入门介绍。`ostream`(或输出流)仅仅是你可以在其中发送数据的地方。仅此而已!好吧,也不完全是,但我不会在这里详细介绍流和 streambuf 之间的区别。自己查一下。关键在于,发送数据的代码不必知道数据如何到达目的地,甚至不知道目的地是哪里。它只需要给 ostream 一堆数据,然后说“自己处理!”。
所以,如果你写一个这样的函数:
void foo( ostream& os )
{
os << "Hello world!" << endl ;
}
这个函数接受一个 ostream 并将“Hello world!”消息发送给它。
`cout` 是一个特殊的 ostream,它将输出发送到 stdout,所以写:
foo( cout ) ;
将在控制台打印“Hello world!”。
类似地,`ofstream` 是一个 ostream 派生类,它将输出发送到文件,所以这将消息发送到指定的文件:
ofstream outputFile( "greeting.txt" ) ;
foo( outputFile ) ;
那么,这一切与本文有什么关系呢?`CMessageLog` 是我的类,它通过将日志消息转发给你指定的 `ostream` 对象来管理日志消息。通过将 `cout` 传递给 `CMessageLog` 构造函数,你可以将跟踪消息打印到控制台,但通过安装 `ofstream` 对象,你可以将跟踪消息发送到文件。但等等,还有更多!我过去曾写过一个 ostream 派生类,可以将数据通过套接字发送,因此只需一行代码,你就可以将其中一个插入到这个库中,实现即时远程日志记录。酷!或者你可以安装一个 stringstream 将日志消息保存在内存中。或者一个将日志消息记录为数据库行的。我的一位同事想为 `OutputDebugString()` 编写一个 ostream 包装器,这样我们就可以将日志消息发送到调试器(嗨 Pete - 准备好了吗?)。我甚至写了一个具有流式接口的 PDF 生成库,并尝试将其集成到这个库中。将跟踪消息发送到 PDF:完全没用,但却能巧妙地验证流的力量 :-)
示例
是时候举例说明了。
这是使用该库的最简单形式:
#define _LOG // need this defined somewhere to enable logging to be compiled
#include "log/log.hpp"
// create and configure a message log
CMessageLog myLog( cout ) ;
myLog.enableTimeStamps( true ) ;
myLog.enableDateStamps( true ) ;
// log a message
myLog << "Hello world!" << endl ;
这会产生以下输出:
01jan02 12:48:19 | Hello world!
大多数应用程序通常只需要一个日志,因此提供了一个全局实例作为便利。可以通过全局函数 `theMessageLog()` 访问此对象。还定义了一些宏来向此全局对象发送消息:
// let's send our output to a file this time
ofstream logFile( "log.txt" ) ;
theMessageLog().setOutputStream( logFile ) ;
// log the message (to the file)
LOG_MSG( "Hello world!" ) ;
现在我们将创建一些消息组,也就是说,可以单独在运行时启用或禁用的消息组。
// create our message groups
CMessageGroup gMsgGroup1 ;
CMessageGroup gMsgGroup2 ;
CMessageGroup gMsgGroup3 ;
// enable/disable our message groups
gMsgGroup1.enableMsgGroup( true ) ;
gMsgGroup2.enableMsgGroup( true ) ;
gMsgGroup3.enableMsgGroup( false ) ;
// output some messages
LOG_GMSG( gMsgGroup1 , "This is a message from group 1." ) ;
LOG_GMSG( gMsgGroup2 , "This is a message from group 2." ) ;
LOG_GMSG( gMsgGroup3 , "This is a message from group 3." ) ;
在此示例中,只有前两条消息会显示。第三条不会显示,因为它的组已被禁用。注意,我使用了 `LOG_GMSG()` 宏而不是 `LOG_MSG()`。前者会检查消息组是否已启用,然后再输出消息,而后者不进行任何检查,无条件记录消息。
完整示例
现在来看一个实际的例子。假设我正在编写一个服务器应用程序,它在套接字上接受请求,进行一些处理,然后发送回响应。我可能会设置三个消息组,一个用于记录传入请求,一个用于处理,一个用于记录发送回的响应。使用辅助宏,我可能会像这样定义它们:
DEFINE_MSG_GROUP( gReqMsgGroup , "req" , "Log incoming requests." )
DEFINE_MSG_GROUP( gProcMsgGroup , "proc" , "Log request processing." )
DEFINE_MSG_GROUP( gRespMsgGroup , "resp" , "Log outgoing responses." )
请注意,我为每个组提供了一个名称,除了自动分配的数字 ID 外,还可以用来标识每个组。每个组还有一个简短的描述,如果你调用 `CMessageGroup::dumpMsgGroups()`,该描述将被打印出来。请查看演示,了解它是如何工作的。
我通常还会定义一些我自己的辅助宏来记录消息:
#define LOG_REQ_MSG( msg ) LOG_GMSG( gReqMsgGroup , msg )
#define LOG_PROC_MSG( msg ) LOG_GMSG( gProcMsgGroup , msg )
#define LOG_RESP_MSG( msg ) LOG_GMSG( gRespMsgGroup , msg )
现在,我的服务器可能如下所示:
void main( int argc , char* argv[] )
{
// enable any message groups specified in the command line
CMessageGroup::disableAllMsgGroups( true ) ;
if ( argc > 1 )
CMessageGroup::enableMsgGroups( argv[1] , true ) ;
// main loop
for ( ; ; )
{
// wait for the next request (let's assume it's just a string)
string req = acceptRequest() ;
LOG_REQ_MSG( "Received a request: " << req ) ;
// process the request
string resp = processRequest( req ) ;
// return the response
LOG_RESP_MSG( "Sending response: " << resp ) ;
}
}
string processRequest( const string& req )
{
// process the request
LOG_PROC_MSG( "Processing request: " << req ) ;
// return the response (just the same string as the request)
return req ;
}
现在,当我启动我的服务器应用程序时,我可以指定我想要启用哪些消息组:
server.exe req,resp <== log requests & responses only, no processing
我还会添加命令行开关来启用日期/时间戳等。
当然,你也可以添加一个 UI 来通过调用适当的 `CMessageGroup` 对象的 `enableMsgGroup()` 来动态启用或禁用消息组。或者也许定期从 INI 文件重新加载设置。
摘要
我长期以来一直在 CodeProject 上潜水,觉得是时候站出来做点贡献了。这是我工具包中工作最努力的库之一,虽然实现有点笨拙 - 它是在 97 年写的,那时我 C++ 的水平还很初级 - 但我希望大家觉得它有用。祝好 :-)
修订历史
- 2002 年 11 月 4 日 - 初次编辑。