I have a log setup in which I have 2 types of log messages:
These attributes are defined as follows:
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)
I want to create a filter function that allows a message to be added to my log based on either of the 2 criteria (note that the log messages based on the custom tag attribute are always printed with severity level info, based on the trivial logger's severity levels).
So I want to have a filter, which allows a message based on if a message has the custom tag, and if it does not have it, based on the severity of the message.
I have tried to have a relative simple filter which does the following:
sink_->set_filter(
trivial::severity >= severityLevel
|| (expr::has_attr(tag_attr) && tag_attr == "JSON" && logJson_)
);
But as it is possible that the severityLevel can be either Debug, Info, Warning, Error or Fatal, if the level is configured as either Debug or Info, the custom tag attribute is ignored by the filter.
I have tried using a c++11 lambda, as following:
sink_->set_filter([this, severityLevel](const auto& attr_set) {
if (<condition for custom tag first>) {
return true;
} else if (<condition for severity level second>) {
return true;
} else {
return false;
}
});
But then I don't have an idea on how to actually check for my conditions. I have tried the following:
if (attr_set["Tag"].extract<std::string>() == "JSON" && logJson_) {
return true;
} else if (attr_set["Severity"].extract<trivial::severity_level>() >= severityLevel) {
return true;
} else {
return false;
}
But the compiler throws several errors about this:
Core/Source/Log/Logger.cpp: In lambda function:
Core/Source/Log/Logger.cpp:127:48: error: expected primary-expression before '>' token
if (attr_set["Tag"].extract<std::string>() == "JSON" && logJson_) {
^
Core/Source/Log/Logger.cpp:127:50: error: expected primary-expression before ')' token
if (attr_set["Tag"].extract<std::string>() == "JSON" && logJson_) {
^
Core/Source/Log/Logger.cpp:129:72: error: expected primary-expression before '>' token
} else if (attr_set["Severity"].extract<trivial::severity_level>() >= severityLevel) {
^
Core/Source/Log/Logger.cpp:129:74: error: expected primary-expression before ')' token
} else if (attr_set["Severity"].extract<trivial::severity_level>() >= severityLevel) {
^
Core/Source/Log/Logger.cpp: In lambda function:
Core/Source/Log/Logger.cpp:134:5: error: control reaches end of non-void function [-Werror=return-type]
});
^
cc1plus: all warnings being treated as errors
scons: *** [obj/release/Core/Source/Log/Logger.os] Error 1
====5 errors, 0 warnings====
I have been scouring the boost log documentation about extracting the attributes myself, but I cannot find the information I need.
EDIT:
For posterity, I'll add how I've solved my issue (with thanks to the given answer by Andrey):
sink_->set_filter([this, severityLevel](const auto& attr_set) {
if (attr_set[tag_attr] == "JSON") {
return logJson_;
} else if (attr_set[severity] >= severityLevel) {
return true;
} else {
return false;
}
});
The filter can be written in multiple ways, I will demonstrate a few alternatives.
First, using expression templates you can write it this way:
sink_->set_filter(
(expr::has_attr(tag_attr) && tag_attr == "JSON" && logJson_) ||
trivial::severity >= severityLevel
);
Following the normal short-circuiting rules of C++, the tag attribute will be tested first and if that condition succeeds, the severity will not be tested. If the tag is not present or not JSON or logJson_
is not true, then severity level is tested.
Note that the filter above will save copies of its arguments (including logJson_
and severityLevel
) at the point of construction, so if you change logJson_
later on the filter will keep using the old value. This is an important difference from your later attempts with C++14 lambdas, which access logJson_
via the captured this
pointer. If you actually want to save a reference to your member logJson_
in the filter, you can use phoenix::ref
:
sink_->set_filter(
(expr::has_attr(tag_attr) && tag_attr == "JSON" && boost::phoenix::ref(logJson_)) ||
trivial::severity >= severityLevel
);
However, you should remember that the filter can be called concurrently in multiple threads, so the access to logJson_
is unprotected. You will have to implement your own thread synchronization if you want to update logJson_
in run time.
Barring multithreading issues, your second attempt with a lambda is almost correct. The compiler is complaining because the lambda function is a template, and the result of attr_set["Tag"]
expression depends on one of the template parameters (namely, the type of attr_set
). In this case, the programmer has to qualify that the following extract<std::string>()
expression is a template instantiation and not a sequence of comparisons. This is done by adding a template
keyword:
if (attr_set["Tag"].template extract<std::string>() == "JSON" && logJson_) {
return true;
} else if (attr_set["Severity"].template extract<trivial::severity_level>() >= severityLevel) {
return true;
} else {
return false;
}
Note that you could use a standalone function to the same effect, which wouldn't require the template qualification:
if (boost::log::extract<std::string>("Tag", attr_set) == "JSON" && logJson_) {
return true;
} else if (boost::log::extract<trivial::severity_level>("Severity", attr_set) >= severityLevel) {
return true;
} else {
return false;
}
Finally, the preferred way to extract attribute values is to leverage attribute keywords, which you declared previously. Not only this allows to avoid the template qualification quirk but it also removes a lot of code duplication.
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)
if (attr_set[tag_attr] == "JSON" && logJson_) {
return true;
} else if (attr_set[severity] >= severityLevel) {
return true;
} else {
return false;
}
The attribute value name and type are inferred from the keyword declaration in this case. This use of attribute keywords is documented at the end of this section.