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?
Okay, I found a solution.
The trick is that a decltype(var)
for var
visible in the outer scope will resolve to the type of that outer scope var
even if we define var
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
struct __log_context__
{
typedef decltype(__log_context_var__) prev;
static const char * name() { return name_; }
static ::logging::log_context context()
{
return ::logging::log_context(
name(), chain<__log_context__>::get() );
}
};
static __log_context__ __log_context_var__;
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.
#include <spdlog/spdlog.h>
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;
}
class log_context
{
public:
log_context( const char * name,
const std::string & scope_name )
: name_ ( name )
, scope_( scope_name )
{}
const char * name() const
{ return name_; }
const char * scope() const
{ return scope_.c_str(); }
private:
const char * name_;
std::string scope_;
};
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_.scope(),
std::forward<U>( u )... );
}
private:
spdlog::logger & logger_;
spdlog::level::level_enum level_;
const log_context & context_;
};
} // namespace logging
// Helpers for walking up the lexical scope chain.
template<class T, class Prev = typename T::prev>
struct chain
{
static std::string get()
{
return (chain<Prev, typename Prev::prev>::get() + ".")
+ T::name();
}
};
template<class T>
struct chain<T, void*>
{
static std::string get()
{
return T::name();
}
};
#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_IMPL(prev_type,name_) \
struct __log_context__ \
{ \
typedef prev_type prev; \
static const char * name() { return name_; } \
static ::logging::log_context context() \
{ \
return ::logging::log_context( \
name(), chain<__log_context__>::get() ); \
} \
}; \
static __log_context__ __log_context_var__
#define LOG_CONTEXT(name_) \
LOG_CONTEXT_IMPL(decltype(__log_context_var__),name_)
#define ROOT_CONTEXT(name_) \
LOG_CONTEXT_IMPL(void*,name_)
// We include the root definition here to ensure that
// __log_context_var__ is always defined for any uses of
// LOG_CONTEXT.
ROOT_CONTEXT( "global" );
which with an approximation of the code in my initial post
#include <logging.hpp>
namespace app {
LOG_CONTEXT( "app" );
class Connector {
LOG_CONTEXT( "Connector" );
public:
void send( const std::string & msg )
{
LOG_CONTEXT( "send()" );
LOG_TRACE( msg );
}
};
} // namespace app
void fn()
{
LOG_DEBUG( "in fn" );
}
int main()
{
LOG_CONTEXT( "main()" );
LOGGER.set_level( spdlog::level::trace );
LOG_INFO( "starting app" );
fn();
app::Connector c;
c.send( "hello world" );
}
yields
[2018-03-22 22:35:06.746] [console] [info] [global.main()] starting app
[2018-03-22 22:35:06.747] [console] [debug] [global] in fn
[2018-03-22 22:35:06.747] [console] [trace] [global.app.Connector.send()] hello world
as desired.
Conditionally inheriting an outer scope as mentioned in the question example is left as an exercise.
Writing an example would take some time, but I'll share how I'd approach this problem.
- Your
LOG_CONTEXT
can be invoked anywhere, so if we create multiple static objects then order of their construction is unknown. - Your contexts can be sorted by line number, which is accessible with
__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)- I think all of it is possible with
constexpr
sematics (but quite a challange)
Open problems:
- Static objects in functions (created in first call)
- Nesting of contexts (maybe comparing
__FUNCTION__
would work)?
PS. I'll try to implement it on weekend
来源:https://stackoverflow.com/questions/49413154/how-to-declare-static-information-in-scopes-accessible-to-nested-lexical-scopes