How to declare static information in scopes accessible to nested lexical scopes in C++?

匆匆过客 提交于 2019-12-05 19:53:19

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 of LoggingContext 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!