包含在函数中的调试宏的惰性参数评估

时间:2017-03-30 21:07:09

标签: c++ logging boost macros boost-log

我目前正在开发一个小而简单的日志记录库,它充当Boost.Log v2 C ++库的Facade。

我的库已经完成,我已经成功封装了Boost.Log,即:

  1. 我的库的API没有Boost.Log类型,即用户只需要处理我的类型
  2. 我的库的ABI没有Boost.Log类型,即依赖项不必链接到Boost.Log。
  3. 目前还有一件我无法解决的问题:懒惰参数评估

    我将给出Boost.Log行为的简短用法示例:

    #include <chrono>
    #include <thread>
    
    #include <boost/log/core.hpp>
    #include <boost/log/expressions.hpp>
    #include <boost/log/trivial.hpp>
    
    std::string time_costly_function(
        const std::chrono::seconds seconds = std::chrono::seconds{1}) {
      std::this_thread::sleep_for(seconds);
      return "DONE with time_costly_function";
    }
    
    int main() {
      boost::log::core::get()->set_filter(boost::log::trivial::severity >=
                                          boost::log::trivial::warning);
      BOOST_LOG_TRIVIAL(warning) << "This is evaluated: " << time_costly_function();
      BOOST_LOG_TRIVIAL(info) << "This is NOT evaluated: "
                              << time_costly_function();
    }
    

    如果您运行上面的示例,则可以看到仅评估第一个BOOST_LOG_TRIVIAL调用的参数,即time_costly_function()仅被称为一次

    这正是我在我的库中想要的行为,但正如我所提到的,我不希望用户必须直接处理Boost.Log,而是使用我的小Facade。

    以下代码说明了问题(非常简化以解决实际问题):

    #include <boost/log/core.hpp>
    #include <boost/log/expressions.hpp>
    #include <boost/log/trivial.hpp>
    
    #include <chrono>
    #include <thread>
    
    std::string time_costly_function(
        const std::chrono::seconds seconds = std::chrono::seconds{1}) {
      std::this_thread::sleep_for(seconds);
    
      return "DONE with time_costly_function";
    }
    
    // Encapsulates Boost.Log. The API of my logging library does not know anything
    // about Boost.Log (e.g. the Pimpl idiom is used).
    //
    // In reality this is a member function of a class in my library with the
    // following signature:
    //
    // void log(const SeverityLevel level, const std::string& message) override;
    //
    void log(const std::string& message) { BOOST_LOG_TRIVIAL(warning) << message; }
    
    // A custom logging macro.
    //
    // In reality this is a macro taking the object of a class with the member
    // function illustrated above:
    //
    // FW_LOG(logger, level, message)
    //
    #define FW_LOG(message) log(message)
    
    int main() {
      // TODO(wolters): This does not work, since everything is evaluated outside of
      // the Boost.Log macro(s). I want to call my macro as follows.
      //     FW_LOG(logger, level) << message << time_costly_function();
      // I.e. it should work like the Boost.Log macro(s) illustrated above.
      FW_LOG("foo" + time_costly_function());
    }
    

    我在代码中发现了以下问题:

    1. 函数log可能不会引用std::string引用,因为那样 始终会导致该功能之外的评估。
    2. 必须以支持以下语法的方式重写宏FW_LOGFW_LOG(logger, level) << message << time_costly_function();
    3. 我已经考虑过使用模板功能来使用流和/或完美转发,但是我还没有想出一个可行的解决方案。

      要求:代码必须使用MSVC 12.0,Boost.Log 1.59.0进行编译。允许C + 11。

      所以我的实际问题是:

      如何在第二个示例中重写代码(使用宏和函数)以获得与第一个示例中相同的行为?

2 个答案:

答案 0 :(得分:1)

这样的东西
#define FW_LOG(logger, level, message) \
    do { if (logger.check_level(level)) logger.log(message); while (false)

答案 1 :(得分:0)

在使用我的代码后,我想出了一个解决方案,感谢@ Jarod42和@Nicolás(https://stackoverflow.com/a/2196183/891439)。

  1. 我已经在我的bool isLevelEnabledForAtLeastOneHandler(const SeverityLevel level) const类中添加了一个成员函数Logger,以检查是否为添加到{的至少一个Handler类启用了日志记录宏提供的严重性级别{1}}实例。
  2. 我已经修改了@Nicolás的解决方案(Logger类扩展了我的库中的Logger 接口类):

    LoggerInterface
  3. 我修改了@ Jarod42提供的宏:

    class LoggerStream final : public std::ostringstream {
     public:
      LoggerStream(LoggerInterface& logger, const SeverityLevel level) : logger_{logger}, level_{level} {
        // NOOP
      }
    
      ~LoggerStream() {
        logger_.log(level_, str());
      }
    
     private:
      LoggerInterface& logger_;
      SeverityLevel level_;
    };
    
  4. 我更改了#define FW_LOG(logger, level) \ if (logger.isLevelEnabledForAtLeastOneHandler(level)) LoggerStream(logger, level) 宏调用:

    FW_LOG
  5. 如果我使用严重性级别配置添加到FW_LOG(logger, SeverityLevel::WARNING) << "IS EVALUATED " << time_costly_function(); FW_LOG(logger, SeverityLevel::DEBUG) << "IS NOT EVALUATED " << time_costly_function(); 的处理程序 Logger,第二次调用WARNING会导致以下行为:

    1. FW_LOG中的条件if (logger.isLevelEnabledForAtLeastOneHandler(level))评估为FW_LOG
    2. 未评估插入宏false的“流参数”<< "IS NOT EVALUATED << time_costly_function(),因为没有创建FW_LOG类的实例。
    3. 因此,我当前解决方案的唯一开销是调用LoggerStream

      不是一个完美的解决方案,但比以前好多了。我甚至不必使用此解决方案的isLevelEnabledForAtLeastOneHandler成员函数修改LoggerInterface 接口类

      我的原始问题仍然存在:是否可以将log宏“流参数”以某种方式转发给成员函数FW_LOG,Boost.Log宏可以完成所有工作,即所有参数都在成员函数内部进行了lazely评估,而不是在宏内部?

      直接使用Boost.Log宏不会产生与提供的解决方案相同的开销。因此感觉就像我重新发明了车轮的某些部分......