【发布时间】: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__),但我想要一个更通用的解决方案。以下是我能想到的让事情变得更容易的限制:
- 范围嵌套级别可能有合理的界限,但用户不必提供当前级别/代码可以移动到不同的范围而无需更改。也许 16 级就足够了(这给了我们 orgname::app::module::subsystem::subsubsystem::detail::impl::detail::util 一些空间......)
- 一个范围内(在单个翻译单元中)的下一级范围的数量可能是有界的,但应该比 1 的值大得多。也许 256 是合理的,但我相信有人会有一个反例。
- 理想情况下,相同的宏可用于任何上下文。
我考虑了以下方法:
-
using __parent_context__ = __log_context__; struct __log_context__ ...希望
__parent_context__获取外部上下文,但我收到编译器错误,表明类型名称必须明确引用同一范围内的单个类型。此限制仅在用于类的主体时适用,否则这将适用于函数和命名空间。 -
跟踪适用于
boost::mpl::vector之类的范围的结构本教程中的示例让我相信我会遇到与 1 中相同的问题,因为在被推送到之后的向量需要被赋予一个不同的名称,这需要在嵌套范围内专门引用。
-
使用预处理器计数器生成适用的外部范围的名称。
这适用于我上面的简单用法示例,但如果名称空间中存在不连续的声明或相应类之外的方法定义,则会失败。
如何在嵌套范围内访问这些信息?
【问题讨论】:
-
我想我会实现这个基于 RAII 的,比如
lock_guard等,在记录器中保留一个堆栈,并在上下文创建和删除时从堆栈中推送和弹出。如果适用,需要注意多线程,也许可以使用thread_localstorage class。 -
@jdehesa 听起来更接近调用堆栈,这非常有用,但不是我想要做的。在这里,我希望从父名称生成范围的“名称”,而不必在代码中限定它们。在示例中:
app.Connector.send()来自app命名空间、Connector类和send()方法中的定义。我认为运行时解决方案在这里不适用(例如,你怎么知道你“在”给定的命名空间)。 -
啊,我明白了,我没有正确理解问题,感谢您再次澄清。