提升日志记录,按命名范围

时间:2016-05-03 19:20:45

标签: c++ logging boost

我在我的应用程序中使用boost log,虽然配置起来很棘手,但它通常运行良好。

现在,我想在我的应用程序中添加一些更高级的过滤逻辑,但我无法弄明白。

我想要的是拥有两个"等级"过滤:

  1. 我已经在使用"严重度记录器"具有不同级别,例如debugwarnnote等。这是设置和工作。
  2. 我想通过查看"命名范围"来添加另一种方法来过滤记录。该记录来自。
  3. 因此,我希望能够仅在note NAMED_SCOPE内查看严重性为> = monthly,AND的记录。

    我已经成功地使用了BOOST_LOG_NAMED_SCOPE()宏,并且可以在日志消息中看到范围堆栈。

    我尝试使用boost::phoenix实现自定义过滤器,但我无法使其正常运行。

    我在这里粘贴的代码在我的应用程序中编译(我正在努力剥离它,所以我可以在这里包含一个完整的工作最小例子),但my_filter(..)函数中似乎没有任何内容。我认为我没有正确地将范围堆栈传递给绑定函数,因为如果我在范围堆栈中的循环中放置std::cout语句,我看不到任何打印。

    这里的最小例子:

    // Excerpt from MyLogger.cpp class
    
    bool my_filter(attrs::named_scope_list const& scopeList) {
      for (attrs::named_scope_list::const_iterator iter = scopeList.begin(); iter != scopeList.end(); ++iter) {
        if ( (*iter).scope_name == "monthly") {
          return true;
        }
      }
      return false;
    }
    
    void setup_logging(std::string lvl) {
    
      logging::core::get()->add_global_attribute(
        "Scope", attrs::named_scope()
      );
    
      logging::add_console_log(
        std::clog,
        keywords::format = (
          expr::stream
            << "[" << severity << "] "
            << "[" << named_scope << "] "
            << expr::smessage
        )
      );
    
      try {
        // set the severity level...
        EnumParser<severity_level> parser;
        logging::core::get()->set_filter(
          (
            severity >= parser.parseEnum(lvl) &&
            ( expr::has_attr("Scope") && ( boost::phoenix::bind(&my_filter, attrs::named_scope::get_scopes() ) ) )
          )
        );
      } catch (std::runtime_error& e) {
        std::cout << e.what() << std::endl;
        std::cout << "'" << lvl << "' is an invalid --log-level! Must be one of "
                  << "[debug, info, note, warn, err, fatal]\n";
        exit(-1);
      }
    
    }
    

    编辑使用最少的工作示例进行了更新:

    TEMLogger.h

    #ifndef _TEMLOGGER_H_
    #define _TEMLOGGER_H_
    
    #include <string>
    #include <iostream>
    #include <sstream>
    #include <cstdlib>
    #include <exception>
    #include <map>
    #include <iomanip>
    
    #include <boost/log/core.hpp>
    #include <boost/log/trivial.hpp>
    #include <boost/log/sources/global_logger_storage.hpp>
    #include <boost/log/sources/severity_feature.hpp>
    #include <boost/log/sources/severity_logger.hpp>
    #include <boost/log/expressions.hpp>
    #include <boost/log/utility/setup/console.hpp>
    
    #include <boost/log/attributes/current_process_id.hpp>
    #include <boost/log/attributes/scoped_attribute.hpp>
    
    
    namespace logging = boost::log;
    namespace src = boost::log::sources;
    namespace attrs = boost::log::attributes;
    namespace keywords = boost::log::keywords;
    namespace expr = boost::log::expressions;
    namespace sinks = boost::log::sinks;
    
    /** Define the "severity levels" for Boost::Log's severity logger. */
    enum severity_level {
      debug, info, note, warn, err, fatal
    };
    
    /** Convert from string to enum integer value.
     *
     * Inspired by: http://stackoverflow.com/questions/726664/string-to-enum-in-c
     */
    template <typename T>
    class EnumParser {
        std::map <std::string, T> enumMap;
    public:
        EnumParser(){};
    
        T parseEnum(const std::string &value) { 
            typename std::map<std::string, T>::const_iterator iValue = enumMap.find(value);
            if (iValue == enumMap.end())
                throw std::runtime_error("Value not found in enum!");
            return iValue->second;
        }
    };
    
    
    BOOST_LOG_GLOBAL_LOGGER(my_logger, src::severity_logger< severity_level >);
    
    /** Send string representing an enum value to stream 
     */
    std::ostream& operator<< (std::ostream& strm, severity_level lvl);
    
    void setup_logging(std::string lvl);
    
    #endif /* _TEMLOGGER_H_ */
    

    TEMLogger.cpp

    #include <boost/log/expressions/formatters/named_scope.hpp>
    #include <boost/log/expressions.hpp>
    #include <boost/phoenix.hpp>
    
    #include "TEMLogger.h"
    
    // Create the global logger object
    src::severity_logger< severity_level > glg;
    
    // Add a bunch of attributes to it
    BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
    BOOST_LOG_ATTRIBUTE_KEYWORD(named_scope, "Scope", attrs::named_scope::value_type)
    
    /** Initialize the enum parser map from strings to the enum levels.*/
    template<>
    EnumParser< severity_level >::EnumParser() {
        enumMap["debug"] = debug;
        enumMap["info"] = info;
        enumMap["note"] = note;
        enumMap["warn"] = warn;
        enumMap["err"] = err;
        enumMap["fatal"] = fatal;
    }
    
    std::ostream& operator<< (std::ostream& strm, severity_level level) {
        static const char* strings[] = { 
          "debug", "info", "note", "warn", "err", "fatal"
        };
    
        if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
            strm << strings[level];
        else
            strm << static_cast< int >(level);
    
        return strm;
    }
    
    bool my_filter(boost::log::value_ref< attrs::named_scope > const& theNamedScope) {
    
      // I think I want something like this:
      // for (attrs::named_scope_list::const_iterator iter = scopeList.begin(); iter != scopeList.end(); ++iter) {
      //   if ( (*iter).scope_name == "monthly"){
      //     return true;
      //   }
      // }
      return true;
    }
    
    void setup_logging(std::string lvl) {
    
      logging::core::get()->add_global_attribute(
        "Scope", attrs::named_scope()
      );
    
      logging::add_console_log(
        std::clog,
        keywords::format = (
          expr::stream
            << "[" << severity << "] "
            << "[" << named_scope << "] "
            << expr::smessage
        )
      );
    
      try {
        // set the severity level...
        EnumParser<severity_level> parser;
        logging::core::get()->set_filter(
          (
            severity >= parser.parseEnum(lvl) &&
            ( expr::has_attr("Scope") && ( boost::phoenix::bind(&my_filter, expr::attr< attrs::named_scope >("Scope").or_none()) ) )
          )
        );
      } catch (std::runtime_error& e) {
        std::cout << e.what() << std::endl;
        std::cout << "'" << lvl << "' is an invalid --log-level! Must be one of "
                  << "[debug, info, note, warn, err, fatal]\n";
        exit(-1);
      }
    
    }
    

    主程序

    #include "TEMLogger.h"
    
    extern src::severity_logger< severity_level > glg;
    
    void func1() {
      BOOST_LOG_NAMED_SCOPE("monthly");
      for (int i=0; i<5; ++i) {
        BOOST_LOG_SEV(glg, note) << "doing iteration " << i << "within monthly scope!";
      }
    
    }
    
    int main(int argc, char* argv[]) {
    
      std::cout << "Setting up logging...\n";
    
      setup_logging("debug");
    
      BOOST_LOG_SEV(glg, note) << "Some message in the main scope";
    
      func1();
    
      BOOST_LOG_SEV(glg, note) << "Another message in the main scope";
    
      return 0;
    }
    

    编译

    (我在Mac上,由于我安装Boost的方式,我必须指定编译器,以及链接Boost库的方法.YMMV)

    g++-4.8 -o TEMLogger.o -c -g -DBOOST_ALL_DYN_LINK TEMLogger.cpp
    g++-4.8 -o log-filter-example.o -c -g -DBOOST_ALL_DYN_LINK log-filter-example.cpp
    g++-4.8 -o a.out log-filter-example.o TEMLogger.o -L/usr/local/lib -lboost_system-mt -lboost_filesystem-mt -lboost_thread-mt -lboost_log-mt
    

    运行

    $ ./a.out 
    Setting up logging...
    [note] [] Some message in the main scope
    [note] [monthly] doing iteration 0within monthly scope!
    [note] [monthly] doing iteration 1within monthly scope!
    [note] [monthly] doing iteration 2within monthly scope!
    [note] [monthly] doing iteration 3within monthly scope!
    [note] [monthly] doing iteration 4within monthly scope!
    [note] [] Another message in the main scope
    

1 个答案:

答案 0 :(得分:2)

分析

您的问题在于如何使用phoenix::bind创建过滤表达式。

boost::phoenix::bind(&my_filter
    , attrs::named_scope::get_scopes())

编写方式时,它将绑定到绑定时get_scopes() 返回的值。相反,我们需要延迟评估,这将在每个消息调用my_filter函数之前发生。这是由boost::log::expressions::attribute_actor完成的,我们可以使用boost::log::expressions::attr(...)

创建
boost::phoenix::bind(&my_filter
    , expr::attr<attrs::named_scope>("Scope").or_none())

下一个问题在于boost::log::attributes::named_scope。正如文档所说

  

basic_named_scope属性本质上是作用域列表的特定于线程的实例的钩子。

我们实际上对提取给定消息的当前作用域堆栈感兴趣,而不是这个虚拟属性。根据{{​​1}},这是boost::log::attributes::named_scope_list的一个实例。因此,我们应该将代码更改为

value_type

并调整boost::phoenix::bind(&my_filter , expr::attr<attrs::named_scope_list>("Scope").or_none()) 的签名以匹配:

my_filter(...)

现在,由于我们使用bool my_filter(boost::log::value_ref<attrs::named_scope_list> const& scopes) 来创建.or_none(),我们可以从过滤器表达式中删除对“Scope”属性的存在的检查

attribute_actor

并在我们的过滤函数中执行此测试

expr::has_attr("Scope") // This goes away

最后,我们应该有办法配置我们想要过滤的范围。所以,让我们在过滤函数中添加一个参数:

if (!scopes.empty()) { // ...
} else { return false; }

并将其绑定到所需的值

bool my_filter(boost::log::value_ref<attrs::named_scope_list> const& scopes
    , std::string const& target_scope)
{ // ...
}

示例代码

包含,类型定义:

std::string target_scope("scope_2"); // Or read from config

// .....
    (boost::phoenix::bind(&my_filter
        , expr::attr<attrs::named_scope_list>("Scope").or_none()
        , target_scope)) 

过滤功能:

#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>

#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>

// NB: The following two are just convenience
// Reduce them to just the headers needed to reduce compile time
#include <boost/log/attributes.hpp>
#include <boost/log/expressions.hpp>

#include <boost/phoenix.hpp>
// ============================================================================
namespace bl = boost::log;
// ----------------------------------------------------------------------------
typedef bl::sources::severity_logger <bl::trivial::severity_level> logger_t;
typedef bl::trivial::severity_level log_level;
// ----------------------------------------------------------------------------
BOOST_LOG_ATTRIBUTE_KEYWORD(my_named_scope, "Scope", bl::attributes::named_scope::value_type)
// ============================================================================

记录器初始化:

// ============================================================================
bool my_filter(bl::value_ref<bl::attributes::named_scope_list> const& scopes
    , std::string const& target_scope)
{
    bool matched(false);
    if (!scopes.empty()) {
        for (auto& scope : scopes.get()) {
            if (scope.scope_name == target_scope) {
                matched = matched || true; // Any scope name matches...
            }
        }
    }
    return matched;
}
// ============================================================================

最后测试一下:

// ============================================================================
void init_logging()
{
    bl::core::get()->add_global_attribute(
        "Scope", bl::attributes::named_scope()
        );

    bl::add_console_log(std::clog
        , bl::keywords::format = (
            bl::expressions::stream
                << "[" << bl::trivial::severity << "] "
                << "[" << my_named_scope << "] "
                // Alternative way to format this:
                // << bl::expressions::format_named_scope("Scope", bl::keywords::format = "[%n] ")
                << bl::expressions::smessage
        ));

    // Hard-coded, determine this as appropriate from config
    log_level target_severity(log_level::info);
    std::string target_scope("scope_2");

    bl::core::get()->set_filter(
        (bl::trivial::severity >= target_severity)
        &&  (boost::phoenix::bind(&my_filter
            , bl::expressions::attr<bl::attributes::named_scope_list>("Scope").or_none()
            , target_scope)) 
        );
}
// ============================================================================

测试运行

没有过滤的输出:

// ============================================================================
void log_it(logger_t& log, int n)
{
    BOOST_LOG_SEV(log, log_level::debug) << "A" << n;
    BOOST_LOG_SEV(log, log_level::trace) << "B" << n;
    BOOST_LOG_SEV(log, log_level::info) << "C" << n;
    BOOST_LOG_SEV(log, log_level::warning) << "D" << n;
    BOOST_LOG_SEV(log, log_level::error) << "E" << n;
    BOOST_LOG_SEV(log, log_level::fatal) << "F" << n;
}
// ============================================================================
int main()
{
    init_logging();

    logger_t log;

    log_it(log, 1);
    {
        BOOST_LOG_NAMED_SCOPE("scope_1");
        log_it(log, 2);
    }
    {
        BOOST_LOG_NAMED_SCOPE("scope_2");
        log_it(log, 3);
        {
            BOOST_LOG_NAMED_SCOPE("scope_3");
            log_it(log, 4);
        }
        log_it(log, 5);
    }

    return 0;
}
// ============================================================================

使用过滤输出(如示例代码中所示,仅信息级别和更高级别,以及仅具有名为“scope_2”的某些范围的那些):

[debug] [] A1
[trace] [] B1
[info] [] C1
[warning] [] D1
[error] [] E1
[fatal] [] F1
[debug] [scope_1] A2
[trace] [scope_1] B2
[info] [scope_1] C2
[warning] [scope_1] D2
[error] [scope_1] E2
[fatal] [scope_1] F2
[debug] [scope_2] A3
[trace] [scope_2] B3
[info] [scope_2] C3
[warning] [scope_2] D3
[error] [scope_2] E3
[fatal] [scope_2] F3
[debug] [scope_2->scope_3] A4
[trace] [scope_2->scope_3] B4
[info] [scope_2->scope_3] C4
[warning] [scope_2->scope_3] D4
[error] [scope_2->scope_3] E4
[fatal] [scope_2->scope_3] F4
[debug] [scope_2] A5
[trace] [scope_2] B5
[info] [scope_2] C5
[warning] [scope_2] D5
[error] [scope_2] E5
[fatal] [scope_2] F5