【问题标题】:How to declare static information in scopes accessible to nested lexical scopes in C++?如何在 C++ 中嵌套词法范围可访问的范围内声明静态信息?
【发布时间】:2018-08-30 23:24:53
【问题描述】:

我想为范围声明标识符,这些标识符将用于自动填充最内部范围内的任何日志记录语句的字段。它们通常(但并非总是)(例如 lambdas、以 {} 引入的块)匹配封闭块的“名称”。

用法如下所示:

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" );
}

结果类似于:

[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

我们可以通过定义LOG_CONTEXT 宏来获得最内层的作用域,以便它声明一个结构。然后在LOG_* 宏中,我们调用它的静态方法来检索名称。我们将整个事情传递给一个可调用对象,例如:

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" );

我遇到的困难是构建上下文堆栈以在定义最内层__log_context__ 时使用。我们可以使用不同名称的结构和宏约定来添加 1 或 2 个级别(例如 LOG_MODULE 可以定义 __log_module__),但我想要一个更通用的解决方案。以下是我能想到的让事情变得更容易的限制:

  1. 范围嵌套级别可能有合理的界限,但用户不必提供当前级别/代码可以移动到不同的范围而无需更改。也许 16 级就足够了(这给了我们 orgname::app::module::subsystem::subsubsystem::detail::impl::detail::util 一些空间......)
  2. 一个范围内(在单个翻译单元中)的下一级范围的数量可能是有界的,但应该比 1 的值大得多。也许 256 是合理的,但我相信有人会有一个反例。
  3. 理想情况下,相同的宏可用于任何上下文。

我考虑了以下方法:

  1. using __parent_context__ = __log_context__; struct __log_context__ ...

    希望__parent_context__ 获取外部上下文,但我收到编译器错误,表明类型名称必须明确引用同一范围内的单个类型。此限制仅在用于类的主体时适用,否则这将适用于函数和命名空间。

  2. 跟踪适用于 boost::mpl::vector 之类的范围的结构

    本教程中的示例让我相信我会遇到与 1 中相同的问题,因为在被推送到之后的向量需要被赋予一个不同的名称,这需要在嵌套范围内专门引用。

  3. 使用预处理器计数器生成适用的外部范围的名称。

    这适用于我上面的简单用法示例,但如果名称空间中存在不连续的声明或相应类之外的方法定义,则会失败。

如何在嵌套范围内访问这些信息?

【问题讨论】:

  • 我想我会实现这个基于 RAII 的,比如lock_guard 等,在记录器中保留一个堆栈,并在上下文创建和删除时从堆栈中推送和弹出。如果适用,需要注意多线程,也许可以使用thread_localstorage class
  • @jdehesa 听起来更接近调用堆栈,这非常有用,但不是我想要做的。在这里,我希望从父名称生成范围的“名称”,而不必在代码中限定它们。在示例中:app.Connector.send() 来自 app 命名空间、Connector 类和 send() 方法中的定义。我认为运行时解决方案在这里不适用(例如,你怎么知道你“在”给定的命名空间)。
  • 啊,我明白了,我没有正确理解问题,感谢您再次澄清。

标签: c++ c++11 logging spdlog


【解决方案1】:

好的,我找到了解决办法。

诀窍是var 在外部范围内可见的decltype(var) 将解析为外部范围var 的类型,即使我们稍后在同一范围内定义var。这允许我们隐藏外部类型,但仍然可以通过外部类型的其他未使用变量访问它,同时允许我们定义一个同名的变量以在内部范围内访问。

我们的一般结构是这样的

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__;

唯一的其他细节是我们在向上迭代上下文链时需要一个终止条件,因此我们使用void* 作为标记值,​​并在用于构造输出字符串的帮助器类中专门针对它。

decltype 需要 C++11 并允许将本地类传递给模板参数。

#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" );

与我最初帖子中的代码近似

#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" );
}

产量

[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

根据需要。

问题示例中提到的有条件地继承外部范围留作练习。

【讨论】:

  • 可以在声明阴影__log_context__ 之前使用using __prev_log_context__ = __log_context__ ;代替变量声明吗?
  • 这在原帖中作为选项 1 被提及。问题在于,在类的主体中,__log_context__ 必须明确地引用单一类型。以下失败,例如:class A {}; class B { using C = A; class A {}; };。见here,错误为declaration of 'struct B::A' changes meaning of 'A'
  • 很酷的东西,但仅供参考:您不应该使用以_ 开头的名称(例如__log_context__)。它们是为实现而保留的。 (link)
【解决方案2】:

写一个例子需要一些时间,但我会分享我是如何解决这个问题的。

  • 您的LOG_CONTEXT 可以在任何地方调用,因此如果我们创建多个静态对象,那么它们的构造顺序是未知的。
  • 您的上下文可以按行号排序,可通过__LINE__ 访问
  • LOG_CONTEXT 可以创建LoggingContext struct 的静态对象,该对象自注册到正在创建的本地容器中。 (本地是指编译对象中的唯一性,可以通过匿名命名空间实现)
  • LOG_* 应该使用他们的当前行并从本地寄存器中获取最新的 LoggingContext。 (或最后几个,如果需要)
  • 我认为使用constexpr sematics 可以实现所有这些(但相当具有挑战性)

未解决的问题:

  • 函数中的静态对象(在第一次调用中创建)
  • 上下文嵌套(也许比较 __FUNCTION__ 会起作用)?

PS。我会尝试在周末实现它

【讨论】:

  • 如果LOG_CONTEXT 定义了一个静态对象,那么就不可能在类主体中将其用作独立语句,假设需要对其进行初始化以提供名称。你能告诉我这比定义本地__log_context__ 结构的现有实现如何给我们更多吗?
猜你喜欢
  • 1970-01-01
  • 2014-12-24
  • 2015-03-14
  • 2012-06-28
  • 1970-01-01
  • 2021-02-07
  • 2010-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多