I want to declare identifiers for scopes which will be used to automatically populate a field of any logging statements within the innermost scope. They will usually, but not always (e.g. lambdas, blocks introduced with {}
), match the "name" of the enclosing block.
Usage would look something like this:
namespace app {
LOG_CONTEXT( "app" );
class Connector {
LOG_CONTEXT( "Connector" );
void send( const std::string & msg )
{
LOG_CONTEXT( "send()" );
LOG_TRACE( msg );
}
};
} // namespace app
// not inherited
LOG_CONTEXT( "global", false );
void fn()
{
LOG_DEBUG( "in fn" );
}
int main()
{
LOG_CONTEXT( "main()" );
LOG_INFO( "starting app" );
fn();
Connector c;
c.send( "hello world" );
}
with the result being something like:
[2018-03-21 10:17:16.146] [info] [main()] starting app
[2018-03-21 10:17:16.146] [debug] [global] in fn
[2018-03-21 10:17:16.146] [trace] [app.Connector.send()] hello world
We can get the inner-most scope by defining the LOG_CONTEXT
macro such that it declares a struct. Then in the LOG_*
macros we call a static method on it to retrieve the name. We pass the whole thing to a callable object, e.g.:
namespace logging {
spdlog::logger & instance()
{
auto sink =
std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
decltype(sink) sinks[] = {sink};
static spdlog::logger logger(
"console", std::begin( sinks ), std::end( sinks ) );
return logger;
}
// TODO: stack-able context
class log_context
{
public:
log_context( const char * name )
: name_( name )
{}
const char * name() const
{ return name_; }
private:
const char * name_;
};
class log_statement
{
public:
log_statement( spdlog::logger & logger,
spdlog::level::level_enum level,
const log_context & context )
: logger_ ( logger )
, level_ ( level )
, context_( context )
{}
template<class T, class... U>
void operator()( const T & t, U&&... u )
{
std::string fmt = std::string( "[{}] " ) + t;
logger_.log(
level_,
fmt.c_str(),
context_.name(),
std::forward<U>( u )... );
}
private:
spdlog::logger & logger_;
spdlog::level::level_enum level_;
const log_context & context_;
};
} // namespace logging
#define LOGGER ::logging::instance()
#define CHECK_LEVEL( level_name ) \
LOGGER.should_log( ::spdlog::level::level_name )
#define CHECK_AND_LOG( level_name ) \
if ( !CHECK_LEVEL( level_name ) ) {} \
else \
::logging::log_statement( \
LOGGER, \
::spdlog::level::level_name, \
__log_context__::context() )
#define LOG_TRACE CHECK_AND_LOG( trace )
#define LOG_DEBUG CHECK_AND_LOG( debug )
#define LOG_INFO CHECK_AND_LOG( info )
#define LOG_WARNING CHECK_AND_LOG( warn )
#define LOG_ERROR CHECK_AND_LOG( err )
#define LOG_CRITICAL CHECK_AND_LOG( critical )
#define LOG_CONTEXT( name_ ) \
struct __log_context__ \
{ \
static ::logging::log_context context() \
{ \
return ::logging::log_context( name_ ); \
} \
}
LOG_CONTEXT( "global" );
Where I'm stuck is building the stack of contexts to use when defining an inner-most __log_context__
. We may use a differently-named structure and macro convention to add 1 or 2 levels (e.g. LOG_MODULE
can define a __log_module__
), but I want a more general solution. Here are the restrictions I can think of to make things easier:
- Scope nesting level may be reasonably bounded, but the user should not have to provide the current level/code may be moved to a different scope without being changed. Maybe 16 levels is enough (that gives us orgname::app::module::subsystem::subsubsystem::detail::impl::detail::util with some room to spare...)
- Number of next-level scopes within a scope (in a single translation unit) may be bounded, but should be much larger than the value for 1. Maybe 256 is reasonable, but I'm sure someone will have a counterexample.
- Ideally the same macro could be used for any context.
I have considered the following approaches:
using __parent_context__ = __log_context__; struct __log_context__ ...
hoping that
__parent_context__
picks up the outer context, but I was getting compiler errors indicating that a type name must refer unambiguously to a single type in the same scope. This restriction applies only when used in the body of a class, this would otherwise work for functions and namespaces.Tracking the structures applicable to a scope in something like
boost::mpl::vector
The examples in the tutorial lead me to believe I would run into the same issue as in 1, since the vector after being pushed to needs to be given a distinct name which would need to be referred to specifically in nested scopes.
Generating the name of the applicable outer scope with a preprocessor counter.
This would work in my simple usage example above, but would fail in the presence of discontinuous declarations in a namespace or method definitions outside of the corresponding class.
How can this information be accessed in the nested scopes?
Writing an example would take some time, but I'll share how I'd approach this problem.
LOG_CONTEXT
can be invoked anywhere, so if we create multiple static objects then order of their construction is unknown.__LINE__
LOG_CONTEXT
can create static objects ofLoggingContext
struct, which self-registers to local container under creation. (By local I mean unique in compilation object, might be achieved with anonymous namespace)LOG_*
should take their current line and take the most recent LoggingContext from local register. (Or few last, if needed)constexpr
sematics (but quite a challange)Open problems:
__FUNCTION__
would work)?PS. I'll try to implement it on weekend
Okay, I found a solution.
The trick is that a
decltype(var)
forvar
visible in the outer scope will resolve to the type of that outer scopevar
even if we definevar
later in the same scope. This allows us to shadow the outer type but still access it, via an otherwise unused variable of the outer type, while allowing us to define a variable of the same name to be accessed in inner scopes.Our general construction looks like
The only other detail is that we need a terminating condition when iterating up the context chain, so we use
void*
as a sentinel value and specialize for it in the helper classes used for constructing the output string.C++11 is required for
decltype
and to allow local classes to be passed to template parameters.which with an approximation of the code in my initial post
yields
as desired.
Conditionally inheriting an outer scope as mentioned in the question example is left as an exercise.