Merlyn Morgan-Graham Merlyn Morgan-Graham - 2 months ago 26
C++ Question

How do I combine two boost logging macros that generate unique identifiers?

I am using boost_logging (version 2), and I would like to avoid littering these throughout my code:

BOOST_LOG_NAMED_SCOPE("SomeModuleName")
BOOST_LOG_FUNCTION()


My thinking this second is that I'd prefer to combine them in a macro like this:

#define LOG_NAMED_SCOPE_FUNCTION(name)\
BOOST_LOG_NAMED_SCOPE(name)\
BOOST_LOG_FUNCTION()


But I get an error when I try to do that. See below.

I would be extra happy with a solution that allows me to do something like
BOOST_LOG_NAMED_SCOPE("SomeModuleName")
at the module level (which also errors out). Even better would be a solution that does some sort of RAII/AOP to allow me to also append "enter" and "exit" trace messages at the beginning and end of the function, as that's my ultimate goal here.

I am guessing because the unique identifier that gets generated is expanded at the point of macro definition, instead of at macro invocation. I also looked at the DEFERRED and EXPAND helpers, but I'm not sure they're going to help me here.

Here's the error message:

Project/SomeModuleName.cpp:9:5: error: redefinition of '_boost_log_named_scope_sentry_9'
LOG_NAMED_SCOPE_FUNCTION("SomeModuleName")
^
Project/Logging.hpp:24:5: note: expanded from macro 'LOG_NAMED_SCOPE_FUNCTION'
BOOST_LOG_FUNCTION()
^
/usr/local/include/boost/log/attributes/named_scope.hpp:458:36: note: expanded from macro 'BOOST_LOG_FUNCTION'
BOOST_LOG_NAMED_SCOPE_INTERNAL(BOOST_LOG_UNIQUE_IDENTIFIER_NAME(_boost_log_named_scope_sentry_), BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, ::boost::log::attributes::named_scope_entry::function)
^
/usr/local/include/boost/log/utility/unique_identifier_name.hpp:48:5: note: expanded from macro 'BOOST_LOG_UNIQUE_IDENTIFIER_NAME'
BOOST_LOG_UNIQUE_IDENTIFIER_NAME_INTERNAL(prefix, __LINE__)
^
note: (skipping 2 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
/usr/local/include/boost/preprocessor/cat.hpp:22:32: note: expanded from macro 'BOOST_PP_CAT'
# define BOOST_PP_CAT(a, b) BOOST_PP_CAT_I(a, b)
^
/usr/local/include/boost/preprocessor/cat.hpp:29:34: note: expanded from macro 'BOOST_PP_CAT_I'
# define BOOST_PP_CAT_I(a, b) a ## b
^
<scratch space>:89:1: note: expanded from here
_boost_log_named_scope_sentry_9
^
Project/SomeModuleName.cpp:9:5: note: previous definition is here
Project/Logging.hpp:23:5: note: expanded from macro 'LOG_NAMED_SCOPE_FUNCTION'
BOOST_LOG_NAMED_SCOPE(name)\
^
/usr/local/include/boost/log/attributes/named_scope.hpp:449:36: note: expanded from macro 'BOOST_LOG_NAMED_SCOPE'
BOOST_LOG_NAMED_SCOPE_INTERNAL(BOOST_LOG_UNIQUE_IDENTIFIER_NAME(_boost_log_named_scope_sentry_), name, __FILE__, __LINE__, ::boost::log::attributes::named_scope_entry::general)
^
/usr/local/include/boost/log/utility/unique_identifier_name.hpp:48:5: note: expanded from macro 'BOOST_LOG_UNIQUE_IDENTIFIER_NAME'
BOOST_LOG_UNIQUE_IDENTIFIER_NAME_INTERNAL(prefix, __LINE__)
^
note: (skipping 2 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
/usr/local/include/boost/preprocessor/cat.hpp:22:32: note: expanded from macro 'BOOST_PP_CAT'
# define BOOST_PP_CAT(a, b) BOOST_PP_CAT_I(a, b)
^
/usr/local/include/boost/preprocessor/cat.hpp:29:34: note: expanded from macro 'BOOST_PP_CAT_I'
# define BOOST_PP_CAT_I(a, b) a ## b
^
<scratch space>:85:1: note: expanded from here
_boost_log_named_scope_sentry_9
^

Answer

The problem you're having is because BOOST_LOG_FUNCTION is essentially based on BOOST_LOG_NAMED_SCOPE - it adds a scope which name corresponds to the current function signature. Both these macros create a local variable which name is unique for a given line of the source file (it uses __LINE__ to generate that name). As per C/C++ preprocessor rules, all macros expand into a single line, so your LOG_NAMED_SCOPE_FUNCTION expands into two same named local variables in the same scope, hence the compiler error.

One way to work around it is to define your macro so that it directly defines two different variables for the two scopes. The variables should have the named_scope::sentry type, which is a scope guard that automatically pushes and pops the scope to the stack when constructed and destroyed.

#define LOG_NAMED_SCOPE_FUNCTION(name)\
    boost::log::attributes::named_scope::sentry BOOST_LOG_UNIQUE_IDENTIFIER_NAME(scope_sentry1_)(name, __FILE__, __LINE__);\
    boost::log::attributes::named_scope::sentry BOOST_LOG_UNIQUE_IDENTIFIER_NAME(scope_sentry2_)(BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, boost::log::attributes::named_scope_entry::function)

This way each LOG_NAMED_SCOPE_FUNCTION will add two scopes to the list - the named scope and the current function.

However, from your description it looks like that's not actually what you want to achieve. Boost.Log does not allow a direct way to mark log records with the name of the module in which they originated - basically because there is no portable way to know that, and non-portable ways are expensive. But there are ways to emulate this behavior. Here are a few ideas.

The easiest approach is to use your own logging macros that will automatically add the current module name attribute.

// Define the attribute keyword for the module name
BOOST_LOG_ATTRIBUTE_KEYWORD(a_module, "Module", std::string)

#define MY_LOG(lg)\
    BOOST_LOG(lg) << boost::log::add_value(a_module, CURRENT_MODULE)

If you define CURRENT_MODULE to the string naming the current module in your project settings, the MY_LOG macro will automatically append it as an attribute to the record. See the docs about attribute keywords and add_value manipulator.

Another approach is to use channels. If your loggers are not shared by different modules, you can set the current module name as the channel name. Or, if you already use channels, add it as a new separate attribute to every logger you create. You can write your own logger feature to automate that.

If you do share loggers then you can also have a look at how scoped attributes are implemented, BOOST_LOG_SCOPED_THREAD_TAG in particular. Since you're likely calling functions between different modules, BOOST_LOG_SCOPED_THREAD_TAG won't work for you as is (because it will not replace the attribute in the set if one is already present - in your case this means you will only see the name of the module that first set the attribute), but you could implement something similar that would fit your case. The idea is to add the current module name as a thread-specific attribute if none is added or replace the existing one of one is present. This would have to be done in a scope guard that would have to be used in every function that can be called from other modules.