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. -
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.
Can you provide an approach?
Aucun commentaire:
Enregistrer un commentaire