I have code where I log different categories of information. Each piece of code has a specific tag that has been made for them. (e.g : database.message
, database.odbc
, event.cevent
...)
Moreover, I've made a function that reads a json file and their value which corresponds to their according severity filter
{
"database" :
{
"message": "INFO",
"odbc": "DEBUG",
},
"event" :
{
"cevent" : "INFO",
},
}
My problem is, I want to set a "basic filter", (for instance only log messages that are "INFO" or above) for all items whose tags were not set in this file.
Right now, I'm adding filters this way:
logging::core::get()->add_global_attribute("Tag", attrs::constant<std::string>(""));
logging::add_common_attributes();
std::vector<std::string> tags; // Suppose it's already filled with the tags and values from the json
...
for (const auto& tag :tags)
{
boost::shared_ptr<text_sink> sink(new text_sink(backend)); // same as the doc
auto level = logging::trivial::info; // just an example for more clarity
sink->set_filter(expr::attr<std::string>("Tag") == tag && expr::attr<logging::trivial::severity_level>("Severity") >= level);
logging::core::get()->add_sink(sink);
}
This piece of code works and correctly reads and sets filter according to the json file.
So to add this "basic filter", I also added this once every filter has been set:
boost::shared_ptr<text_sink> basic_sink(new text_sink(backend));
auto filter = logging::trivial::severity >= logging::trivial::info;
for (const auto& tag : tags)
{
filter = filter && expr::attr<std::string>("Tag") != tag;
}
basic_sink->set_filter(filter);
logging::core::get()->add_sink(basic_sink);
But it duplicates messages that are defined in the json, when I thought this would filter out tags stored. Do you have any ideas on how to avoid such duplication or do I have to implement such a sink as mentionned in this post
CodePudding user response:
You weren't duplicating messages. You were adding duplicate sinks.
As someone else posted, you want to combine into one filter instead of duplicating your sinks.
However, since the filter expression is a compile-time static template expression that describes a deferred invocation, you need a deferred function to work with it.
I'd use boost::phoenix::function
to make it simple:
boost::phoenix::function matching = [tags](logging::value_ref<std::string> actual) {
return tags.contains(actual.get());
};
Now you can use the single filter expression:
auto filter = matching(_tag) && (logging::trivial::severity >= level);
C 11
The above assumed C 17, but you can spell it out for older beasts:
struct match_impl { std::set<std::string> target_tags; using result_type = bool; result_type operator()(logging::value_ref<std::string> actual_tag) const { return 0 < target_tags.count(actual_tag.get()); } }; boost::phoenix::function<match_impl> matching{match_impl{std::move(tags)}};
If Boost Log supports C 03 I know how to get that done too, but hopefully that's not required.
Full Demo
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <boost/phoenix.hpp>
#include <set>
namespace logging = boost::log;
namespace sinks = logging::sinks;
namespace attrs = logging::attributes;
void init_logging(logging::trivial::severity_level level, std::set<std::string> tags) {
auto core = logging::core::get();
using text_sink = sinks::synchronous_sink<sinks::text_ostream_backend>;
core->add_global_attribute("Tag", attrs::constant<std::string>(""));
logging::add_common_attributes();
auto backend = boost::make_shared<sinks::text_ostream_backend>();
auto sink = boost::make_shared<text_sink>(backend);
sink->locked_backend()->add_stream(
boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter{}));
#if __cplusplus < 201703L
struct match_impl {
std::set<std::string> target_tags;
using result_type = bool;
result_type operator()(logging::value_ref<std::string> actual_tag) const {
return 0 < target_tags.count(actual_tag.get());
}
};
boost::phoenix::function<match_impl> matching{match_impl{std::move(tags)}};
#else
boost::phoenix::function matching = [tags](logging::value_ref<std::string> actual) {
return tags.contains(actual.get());
};
#endif
auto _tag = boost::log::expressions::attr<std::string>("Tag");
auto filter = matching(_tag) && (logging::trivial::severity >= level);
sink->set_filter(filter);
core->add_sink(sink);
}
int main() {
init_logging(logging::trivial::severity_level::error, {"foo", "bar", "qux"});
for (std::string tag : {"foo", "bogus", "bar"}) {
BOOST_LOG_SCOPED_THREAD_ATTR("Tag", attrs::constant<std::string>(tag));
BOOST_LOG_TRIVIAL(debug) << "debug tagged with " << tag;
BOOST_LOG_TRIVIAL(error) << "error tagged with " << tag;
BOOST_LOG_TRIVIAL(fatal) << "fatal tagged with " << tag;
BOOST_LOG_TRIVIAL(info) << "info tagged with " << tag;
BOOST_LOG_TRIVIAL(trace) << "trace tagged with " << tag;
BOOST_LOG_TRIVIAL(warning) << "warning tagged with " << tag;
}
}
Prints the expected
error tagged with foo
fatal tagged with foo
error tagged with bar
fatal tagged with bar