The main problem with your code is that get_global_attributes()
returns a copy of the global attribute set. Whatever changes you do to the copy does not affect the attributes you see attached to log records. And if your code is multi-threaded then using global attributes is wrong in the first place because the execution context is thread-specific. Also, assuming that attribute_cast
succeeds (i.e. does not return an empty attribute) presumes that the attribute is never changed anywhere in the program. You should at least check the result of attribute_cast
.
A better approach to achieve what you want is to add the Line and Function attributes to the thread-specific attribute set in the Tracer constructor instead of your logging initialization code, and restore their previous state in destructor. The restoring part is important is you plan to nest calls marked with Tracer.
using namespace boost::log;
class Tracer
{
enum restore_action
{
noop,
restore_prev,
remove
};
public:
Tracer(
Logger const& logger,
std::string const& function,
std::string const& file,
int line)
:
_logger(logger),
_function_name(function),
_prev_line_number(0),
_function(set_attribute("Function", function, _prev_function_name)),
_file(set_attribute("File", file, _prev_file_name)),
_line(set_attribute("Line", line, _prev_line_number))
{
BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
<< "Entering " << _function_name;
}
~Tracer()
{
BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
<< "Leaving " << _function_name;
restore_attribute(_function, _prev_function_name);
restore_attribute(_file, _prev_file_name);
restore_attribute(_line, _prev_line_number);
}
private:
template< typename T >
static std::pair< attribute_set::iterator, restore_action >
set_attribute(attribute_name name, T const& value, T& prev_value)
{
typedef attributes::mutable_constant< T > mutable_constant_t;
core_ptr p = core::get();
std::pair< attribute_set::iterator, bool > res =
p->add_thread_attribute(name, mutable_constant_t(value));
if (res.second)
return std::make_pair(res.first, remove);
mutable_constant_t prev =
attribute_cast< mutable_constant_t >(res.first->second);
if (!prev)
return std::make_pair(res.first, noop);
prev_value = prev.get();
prev.set(value);
return std::make_pair(res.first, restore_prev);
}
template< typename T >
static void restore_attribute(
std::pair< attribute_set::iterator, restore_action > const& state,
T const& prev_value)
{
typedef attributes::mutable_constant< T > mutable_constant_t;
switch (state.second)
{
case restore_prev:
{
mutable_constant_t attr =
attribute_cast< mutable_constant_t >(state.first->second);
if (attr)
attr.set(prev_value);
}
break;
case remove:
core::get()->remove_thread_attribute(state.first);
break;
case noop:
default:
break;
}
}
private:
Logger _logger;
std::string _function_name;
std::string _prev_function_name, _prev_file_name;
int _prev_line_number;
std::pair< attribute_set::iterator, restore_action > _function;
std::pair< attribute_set::iterator, restore_action > _file;
std::pair< attribute_set::iterator, restore_action > _line;
};
Note that this code will mark all records made by this thread with Function, File and Line attributes as they are set by the Tracer. If that is not what you actually want and you only need to mark the messages about entering and leaving the function then you should add Function, File and Line to the logger attributes instead of thread-specific ones. This will significantly simplify code as well since you won't have to bother with restoring the previous state of the attributes.
I shall also add that there are better tools for maintaining call stack and in particular marking the current execution context. Named scopes allow you to mark the current scope or function in a much cheaper way than what is written above. The markup does not produce log records, but that is simple to add.
class Tracer
{
public:
Tracer(
Logger const& logger,
string_literal const& function,
string_literal const& file,
unsigned int line)
:
_logger(logger),
_function_name(function),
_scope_sentry(function, file, line)
{
BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
<< "Entering " << _function_name;
}
~Tracer()
{
BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
<< "Leaving " << _function_name;
}
private:
Logger _logger;
string_literal _function_name;
attributes::named_scope::sentry _scope_sentry;
};
Compared to the original code this version has the additional limitation that the function and file names must be string literals and not just arbitrary strings. You will also have to set up the formatter differently.