我想为范围声明标识符,这些标识符将用于自动填充最内层范围内的任何日志记录语句的字段。它们通常但不总是(例如 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
可以定义 a __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 中相同的问题,因为在被推送到之后的向量需要被赋予一个不同的名称,这需要在嵌套范围内专门引用。
使用预处理器计数器生成适用的外部范围的名称。
这将在我上面的简单用法示例中起作用,但在相应类之外的命名空间或方法定义中存在不连续声明时会失败。
如何在嵌套范围内访问此信息?