使用全局变量和仿函数实现日志记录

Cha*_*hap 14 c++ logging global-variables functor

我想实现具有以下特征的C++日志记录:

  • 它必须对所有源代码都可用,而不需要每个函数都有一个额外的参数(我认为它需要一个全局参数)
  • 日志记录调用可以指定严重性级别(INFO,DEBUG,WARN等),并且可以在运行时设置日志记录工具以忽略低于特定严重性级别的调用
  • 可以在运行时将日志接收器设置为控制台或文件.

事情我并不需要的是:

  • 在运行时支持多个日志接收器(即,它都可以转到控制台或文件)
  • 支持多线程日志记录
  • 能够在记录器调用中传递cout样式表达式(例如"foo=" << foo).我只会通过一个std::string.

我找到了这个答案,这似乎是为了满足我的需求,但这有点过头了.我认为我的困惑集中在仿函数上.(我读过维基百科的文章,但显然没有沉入其中.)

下面是我的部分明白了:

  • 使用宏(例如LOG_DEBUG)方便地指定严重性级别并调用记录器.
  • 使用#ifdef NDEBUG以保持被编译的记录通话(但我需要能够在运行时设置日志记录).
  • 使用宏来调用记录器,以便它可以自动和无形地增加信息像的基本原理__FILE____LINE__在点记录器被调用.
  • LOG宏包含一个以static_cast<std::ostringstream&>.开头的表达式.我认为这纯粹与评估cout样式格式字符串有关,我不打算支持它.

这是我在努力的地方:

Logger& Debug() {
  static Logger logger(Level::Debug, Console);
  return logger;
}
Run Code Online (Sandbox Code Playgroud)

阅读operator(),它看起来像class Logger用于创建"仿函数".每个Logger仿函数都使用级别和LogSink进行实例化(?).(你是否"实例化"一个仿函数?)LogSink被描述为"后端消费预先格式化的消息",但我不知道它会是什么样子或它是如何"写入"的.在什么时候实例化静态Logger对象?是什么导致它被实例化?

这些宏定义......

#define LOG(Logger_, Message_)                   \
  Logger_(                                       \
    static_cast<std::ostringstream&>(            \
       std::ostringstream().flush() << Message_  \
    ).str(),                                     \
    __FUNCTION__,                                \
    __FILE__,                                    \
    __LINE__                                     \
  );

#define LOG_DEBUG(Message_) LOG(Debug(), Message_)
Run Code Online (Sandbox Code Playgroud)

......以及这行代码......

LOG_DEBUG(my_message);
Run Code Online (Sandbox Code Playgroud)

...得到预处理:

Debug()(my_message, "my_function", "my_file", 42);
Run Code Online (Sandbox Code Playgroud)

执行时会发生什么?

格式化字符串实际写入"日志接收器"的方式和位置?

(注意:有人建议我查看log4cpp - 我发现它比我需要的更大更难理解,更不用说我将第三方库带入我们的环境的政治问题了)


更新:

为了理解上述解决方案的工作原理,我尝试编写一个最小的完整的工作程序.我故意删除了以下内容:

  • 涉及std :: ostringstream的"魔法"
  • #ifdef NDEBUG
  • Logger Level类枚举
  • LogSink ctor参数(现在我将写入std :: cout)

这是完整的源文件:

#include <iostream>
#include <string>

class Logger {
public:
    Logger(int l);
    void operator()(std::string const& message,
                    char const* function,
                    char const* file,
                    int line);
private:
    int _level;
};

Logger::Logger(int l) :
    _level(l)
{ }

#define LOG(Logger_, Message_)  \
    Logger_(                    \
        Message_,               \
        __FUNCTION__,           \
        __FILE__,               \
        __LINE__                \
    )

#define LOG_DEBUG(Message_)     \
    LOG(                        \
        Debug(),                \
        Message_                \
    )

Logger& Debug() {
    static Logger logger(1);
    return logger;
}

// Use of Logger class begins here

int main(int argc, char** argv) {
    LOG_DEBUG("Hello, world!");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译时:

$ c++ main.cpp
Undefined symbols for architecture x86_64:
  "Logger::operator()(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, char const*, char const*, int)", referenced from:
      _main in main-c81cf6.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Run Code Online (Sandbox Code Playgroud)

我看到没有一个函数定义接受这四个参数并将它们写入std::cout,但是我需要定义的函数的名称是什么?

(到现在为止我同意我应该使用Boost :: Log,但是仿函数显然是一个我不太了解的主题.)

Ser*_*eyA 5

该函数Debug返回一个Logger对象(在第一次调用此函数时创建).

这个Logger对象似乎已经operator()()为它定义了(根据宏定义来判断),它确实使它成为一个仿函数.顺便说一句,仿函数并不是什么特别的 - 它operator()()定义了为它定义的任何类型.但是,您的分析似乎不正确.代替,

LOG_DEBUG(my_message);
Run Code Online (Sandbox Code Playgroud)

将扩大为

LOG(Debug(), Message_)
Run Code Online (Sandbox Code Playgroud)

进入

Debug()(Message_, __FUNCTION__, __FILE__, __LINE__);
Run Code Online (Sandbox Code Playgroud)

这里Debug()将返回一个已operator()()定义的对象,该对象将用于调用.

一些QnA

为什么Logger&Debug()签名不指定四个参数?

因为它不需要.Debug()只返回(静态)使用特定参数(日志级别和输出设备)创建的Logger对象.

在什么时候实例化静态Logger对象?是什么导致它被实例化?

当第一次调用Debug()函数时,它初始化它的静态对象.这是静态函数变量的基础.

最后的但并非最不重要的.我个人觉得编写自己的记录器不值得.除非你真的需要一些特别的东西,否则它很乏味且非常无聊.虽然我对Boost.Log和log4cpp并不是很疯狂,但我(实际上)肯定会使用其中一个而不是滚动我自己的记录器.即使是次优的日志记录也比在自己的解决方案上花费数周更好.