我目前的方法是使用一种依赖注入,使用 C++ 的优势而不是魔法。它不需要任何特定于 C++11 的内容(如果您希望成为标准,则__thread
可以将其替换为扩展)。thread_local
class LoggerEngine {
public:
static LoggerEngine* Current() { return CurrentE; }
virtual bool isActive(Level) { return true; }
virtual void log(char const* function,
char const* file,
int line,
std::string message) = 0;
// cuz' decorators rock
LoggerEngine* previous() const { return _previous; }
protected:
LoggerEngine(): _previous(CurrentE) { CurrentE = this; }
~LoggerEngine() { CurrentE = _previous; }
private:
static __thread LoggerEngine* CurrentE;
LoggerEngine* const _previous;
}; // class LoggerEngine
// in some .cpp file:
__thread LoggerEngine* LoggerEngine::CurrentE = 0;
然后,提供宏(捕获函数、文件和行):
#define LOG(L_, Message_) \
do { if (LoggerEngine* e = LoggerEngine::Current() and e->isActive(L_)) { \
std::ostringstream _28974986589657165; \
_28974986589657165 << Message_; \
e->log(__func__, __FILE__, __LINE__, _28974986589657165.str()); \
}} while(0);
但是,使用 shims 肯定可以做得更好,因为即使它阻止任何计算以防关卡不活动,它仍然需要格式化完整消息(以及必要的内存分配),即使它要截断消息无论如何(例如因为它使用固定大小的缓冲区)并且不容易允许自定义格式。
堆栈引擎(并使用 RAII 自动弹出)与线程本地行为的组合非常简洁。大多数代码只看到一个接口,而不必通过线程处理它(当你有 4/5 个不同的引擎时很酷),并且堆栈的任何级别都可以将引擎切换到更合适的东西。
有一个警告,因为在定义第一个引擎之前不会发生日志记录。出于这个原因,我经常考虑在没有设置引擎的情况下默认写入控制台,但是......我主要改变了我的风格以避免在main
调用之前计算,因为在这个阶段我不能依赖注入(这很尴尬如果异常触发...)
用法是这样的:
void benchmark() {
LOG(INFO, "Hello, World!");
Timer t;
{
MySinkLogger const _; (void)_; // a logger with "isActive" always false
for (size_t i = 0; i != 10000; ++i) {
LOG(INFO, "Flood!");
}
}
LOG(INFO, "Elapsed: " << t.elapsed());
}
int main() {
MyFileLoggerEngine const _("somefile.log"); (void)_; // a file logger
benchmark();
}
通常这可以创建一个文件“somefile.log”,其中包含:
2013-10-03T18:38:04.645512 mylaptop INFO <test.cpp#42> Hello, World!
2013-10-03T18:38:04.865765 mylaptop INFO <test.cpp#47> Elapsed: 0.220213s