C++ 的简单日志记录器






4.88/5 (32投票s)
一个简单且可扩展的 C++ 日志记录器。
概述
最近,我一直在寻找一种相对容易使用的 C++ 应用程序日志记录功能。市面上有很多优秀的日志框架。我寻找的不是使用或复制代码,而是日志记录的方法。然后我了解了 Log 4 C++,它是一个不错的框架。我在此介绍的这个日志类就是基于 Log 4 C++ 的思想,因为它与 Log 4 C++ 的使用模式相似。大多数日志框架提供大量的调整和格式化选项,而我这个小类并没有这些,正因如此,我觉得它非常易于使用。整个代码包含在一个单独的 .h 文件中,易于理解,因此可以轻松修改以满足个人需求。请注意,代码使用了一些 C++11 特性。但是,将其移植到旧规范应该相对容易;例如,将 'enum class
' 替换为 'enum
'。访问 http://en.wikipedia.org/wiki/C%2B%2B11 获取更多信息。示例代码在 Visual Studio 2012 中编译通过。
设计
我们需要关注的主要类是 CLogger
类。它位于 'framework:: Diagnostics
' 命名空间下。CLogger
借助另一个类来实现线程同步功能。目前,有两个类可以帮助实现这一点,它们都位于 framework::Threading
命名空间下。其中一个称为 CIntraProcessLock
。如果将在同一进程中的多个线程之间共享 CLogger
实例,请使用它。如果您不跨线程共享日志记录器对象,请使用 CNoLock
类。创建日志记录器实例时,您需要至少提供其中一个类,或者提供任何其他提供语义正确的 Lock()
和 Unlock()
功能的类。一旦创建了 CLogger
实例,我们就需要添加 ostream
派生对象,它们负责执行将内容写入某处的操作。最后,要记录内容,请使用 WRITELOG
宏。
用法
创建日志记录器。示例
using namespace framework::Diagnostics;
using namespace framework::Threading;
CLogger<CNolock> logger(LogLevel::Info, _T("MyApp"));
构造函数具有以下参数
LogLevel
- 可以是framework::Diagnostics::LogLevel
枚举的enum
值之一。此值影响记录的内容。添加输出流时使用此值。Name
- 分配给日志记录器对象的名称。此名称可以出现在每行日志中。我通常为每个模块创建一个日志记录器对象,并将模块名称在此处指定。logItems
- 这是一个位掩码,列出了我们希望输出到每行日志的项目。值应取自framework::Diagnostics::LogItem
枚举。默认情况下,记录以下项目
日志项 | 默认选中 |
文件名 | 否 |
行号 | 是 |
函数名 | 是 |
日期时间 | 是 |
线程ID | 否 |
日志器名称 | 是 |
日志级别 | 是 |
假设您只想记录日期-时间以及实际的日志语句,您可以像这样创建一个日志记录器对象
CLogger<CNoLock> logger(LogLevel::Info, _T("MyApp"), static_cast<int>(LogItem::DateTime));
请注意,无法阻止实际日志语句被写入,因此没有用于它的 LogItem
。
下一步是添加输出流。对于您添加的每个输出流,您可以指定日志级别。例如,我们可以添加 'cout
' 并指定日志级别为 Error
。这意味着将记录指定级别及所有更高等级的内容。
同样,如果添加了一个文件流并指定 Info
作为日志级别,那么所有高于 Info
的级别以及 Info
本身都将被记录,实际上意味着记录所有内容,因为所有其他级别都高于 Info
(请参阅 LogLevel
enum
)。示例
logger.AddOutputStream(std::wcout, false, LogLevel::Error);
logger.AddOutputStream(new std::wofstream("c:\\temp\\myapp.log"),
true, framework::Diagnostics::LogLevel::Info);
addOutputStream
具有以下参数
os
- 传递任何ostream
派生类,例如cout
或wcout
。own
- 如果为true
,CLogger
实例将使用delete
运算符删除传入的ostream
对象。这通常在创建新的文件流实例并将其传入时很有用(请参见上面的代码)。LogLevel
- 对于此设备(输出流),将写入所有等于或高于指定级别的日志条目。如果未指定,将使用CLogger
对象上指定的日志级别来决定记录什么。最后,要执行日志记录本身,建议使用WRITELOG, LOGINFO, LOGDEBUG, LOGWARN 或 LOGERROR
宏。示例
WRITELOG(logger, framework::Diagnostics::LogLevel::Info, _T("Program starting"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Warn, _T("Something may have gone wrong"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Error, _T("Something did go wrong"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Info, _T("Program Ending"));
// Or use shortcut macros like LOGINFO, LOGDEBUG, LOGWARN, LOGERROR. E.g.
LOGINFO(logger, _T("Program Ending"));
// If you have a pointer to a logger object e.g:
CLogger<cnolock>* loggerPtr = &logger;
LOGINFOP(loggerPtr, _T("Program Ending"));
</cnolock>
如果您有一个指向 logger
对象的指针,可以使用相同的宏,后缀为 'P
'(P 代表指针),即 WRITELOGP
, LOGINFOP
, LOGDEBUGP
, LOGWARNP
或 LOGERRORP
。
整个代码都在一个地方
#include "threading.h"
#include "Logger.h"
#include <fstream>
using namespace framework::Diagnostics;
using namespace framework::Threading;
int _tmain(int argc, _TCHAR* argv[])
{
CLogger<CNolock> logger(LogLevel::Info, _T("MyApp"));
logger.AddOutputStream(std::wcout, false, LogLevel::Error);
logger.AddOutputStream(new std::wofstream("c:\\temp\\myapp.log"),
true, framework::Diagnostics::LogLevel::Info);
WRITELOG(logger, framework::Diagnostics::LogLevel::Info, _T("Program starting"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Warn, _T
("Something may have gone wrong"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Error, _T("Something did go wrong"));
CLogger<CNoLock>* loggerPtr = &logger; // An easy way to use pointer to a logger object
LOGINFOP(loggerPtr, _T("Program Ending"));
return 0;
}
谢谢
用于执行线程同步的模板类的使用基于策略类(请参阅 Andrei Alexander 的《Modern C++ Design》)。Log 4 C++ 启发了该类的整体设计。
感谢我的妻子 Meena 一直以来的支持,以及我的儿子 Neel,他展示了拥有快乐生活有多么简单。
历史
- 2015 年 6 月 6 日:初始版本