c++loggingboostboost-log

Boost Log: Filtering named scopes using a settings file


I'm using Boost Log in my project and I want to be able to filter the scopes that I'm currently interested in the console sink, within the settings file.

I have the names scope attribute added using:

BOOST_LOG_ATTRIBUTE_KEYWORD(scope_attr, "Scope", boost::log::attributes::named_scope::value_type)

boost::log::core::get()->add_global_attribute("Scope", boost::log::attributes::named_scope());

In code I'm using the BOOST_LOG_NAMED_SCOPE macro, and passing the following settings with boost::log::init_from_stream:

[Core]
DisableLogging = false
Filter = "%Severity% >= DEBUG"
[Sinks.1]
Destination = Console
Format = "[%Severity%] [%TimeStamp%] [%Scope%] [%Tag%]: %Message%"
Filter = "%Scope% = \"TargetScope\" & %Severity% >= DEBUG"
[Sinks.2]
Destination = TextFile
FileName = textfile.log
AutoFlush = true
Format = "[%Severity%] [%TimeStamp%] [%Scope%] [%Tag%]: %Message%"

Without the filter activated the "Scope" attribute works fine and prints properly adding "->" between scopes and there are logs where only the target scope get printed as "... [TargetScope] ...". But if I activate the filter nothing gets printed.

Filtering a named scope is supported by default? or one should write an extension for the filter parsing system?


Solution

  • docs

    It must be noted that by default the library only supports those attribute value types which are known at the library build time. User-defined types will not work properly in parsed filters and formatters until registered in the library. It is also possible to override formatting rules of the known types, including support for additional formatting parameters in the string template. More on this is available in the Extending the library section.

    Taking from Boost logging, filter by named scope you can get a filter function (simplified by me):

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

    Parsing The Filter

    Here we have to Extend library settings support.

    Adding support for user-defined types to the filter parser gives you some steps to follow.

    I opted for the custom (non-simple) filter factory, and combining it with generalized matching functions like my_filter just shown, named all and any:

    struct scope_filter_factory : bl::filter_factory<char> {
        bl::filter on_equality_relation(bl::attribute_name const& name, string_type const& arg) override {
            return px::bind(any<std::equal_to<>>,                               //
                            expr::attr<attr::named_scope_list>(name).or_none(), //
                            arg);
        }
    
        bl::filter on_inequality_relation(bl::attribute_name const& name, string_type const& arg) override {
            return px::bind(all<std::not_equal_to<>>,                           //
                            expr::attr<attr::named_scope_list>(name).or_none(), //
                            arg);
        }
    
      private:
        using list = attr::named_scope_list;
        template <typename Rel>
        static bool any(bl::value_ref<list> const& attr, std::string const& target_scope) {
            if (attr.empty())
                return false;
    
            return std::any_of(begin(attr.get()), end(attr.get()), [&, cmp = Rel{}](auto const& scope) {
                return cmp(scope.scope_name, target_scope);
            });
        }
        template <typename Rel>
        static bool all(bl::value_ref<list> const& attr, std::string const& target_scope) {
            if (attr.empty())
                return true;
    
            return std::all_of(begin(attr.get()), end(attr.get()), [&, cmp = Rel{}](auto const& scope) {
                return cmp(scope.scope_name, target_scope);
            });
        }
    };
    

    You register it with register_filter_factory, so e.g.:

    void init_logging() {
        bl::core::get()->add_global_attribute("Scope", attr::named_scope());
        bl::register_filter_factory("Scope", boost::make_shared<scope_filter_factory>());
        std::ifstream ifs("logging.cfg");
        bl::init_from_stream(ifs);
    }
    

    Then we can test with a few functions like

    void bar(log_level level) {
        BOOST_LOG_NAMED_SCOPE(__FUNCTION__);
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 222;
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 333;
    }
    
    void foo(log_level level) {
        BOOST_LOG_NAMED_SCOPE(__FUNCTION__);
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 333;
        bar(level);
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 444;
    }
    
    int main() {
        init_logging();
    
        for (auto level : {log_level::trace, log_level::debug, log_level::info, //
                           log_level::warning, log_level::error, log_level::fatal})
            foo(level);
    }
    

    enter image description here

    Full Listing

    Live On Coliru

    #include <boost/log/attributes/named_scope.hpp>
    #include <boost/log/attributes/scoped_attribute.hpp>
    #include <boost/log/trivial.hpp>
    #include <boost/log/utility/setup.hpp>
    #include <boost/phoenix.hpp>
    #include <fstream>
    namespace bl    = boost::log;
    namespace attr  = bl::attributes;
    namespace expr  = bl::expressions;
    namespace px    = boost::phoenix;
    using log_level = bl::trivial::severity_level;
    bl::sources::severity_logger<log_level> glg;
    
    BOOST_LOG_ATTRIBUTE_KEYWORD(severity,   "Severity", bl::trivial::severity_level)
    BOOST_LOG_ATTRIBUTE_KEYWORD(scope_attr, "Scope",    attr::named_scope::value_type)
    
    // ============================================================================
    // Custom filter factory
    struct scope_filter_factory : bl::filter_factory<char> {
        bl::filter on_equality_relation(bl::attribute_name const& name, string_type const& arg) override {
            return px::bind(any<std::equal_to<>>,                               //
                            expr::attr<attr::named_scope_list>(name).or_none(), //
                            arg);
        }
    
        bl::filter on_inequality_relation(bl::attribute_name const& name, string_type const& arg) override {
            return px::bind(all<std::not_equal_to<>>,                           //
                            expr::attr<attr::named_scope_list>(name).or_none(), //
                            arg);
        }
    
      private:
        using list = attr::named_scope_list;
        template <typename Rel>
        static bool any(bl::value_ref<list> const& attr, std::string const& target_scope) {
            if (attr.empty())
                return false;
    
            return std::any_of(begin(attr.get()), end(attr.get()), [&, cmp = Rel{}](auto const& scope) {
                return cmp(scope.scope_name, target_scope);
            });
        }
        template <typename Rel>
        static bool all(bl::value_ref<list> const& attr, std::string const& target_scope) {
            if (attr.empty())
                return true;
    
            return std::all_of(begin(attr.get()), end(attr.get()), [&, cmp = Rel{}](auto const& scope) {
                return cmp(scope.scope_name, target_scope);
            });
        }
    };
    
    void init_logging() {
        bl::core::get()->add_global_attribute("Scope", attr::named_scope());
        bl::register_filter_factory("Scope", boost::make_shared<scope_filter_factory>());
        std::ifstream ifs("logging.cfg");
        bl::init_from_stream(ifs);
    }
    
    // ============================================================================
    
    void bar(log_level level) {
        BOOST_LOG_NAMED_SCOPE(__FUNCTION__);
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 222;
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 333;
    }
    
    void foo(log_level level) {
        BOOST_LOG_NAMED_SCOPE(__FUNCTION__);
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 333;
        bar(level);
        BOOST_LOG_SEV(glg, level) << __FUNCTION__ << ":" << 444;
    }
    
    int main() {
        init_logging();
    
        for (auto level : {log_level::trace, log_level::debug, log_level::info, //
                           log_level::warning, log_level::error, log_level::fatal})
            foo(level);
    }