Я хочу объявить идентификаторы для областей, которые будут использоваться для автоматического заполнения поля любых операторов записи в самой внутренней области видимости. Они обычно, но не всегда (например, лямбды, блоки, введенные с {}
), соответствует «имени» вмещающего блока.
Использование будет выглядеть примерно так:
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__
), но я хочу более общее решение. Вот ограничения, которые я могу придумать, чтобы сделать вещи проще:
Я рассмотрел следующие подходы:
using __parent_context__ = __log_context__; struct __log_context__ ...
надеясь, что __parent_context__
выбирает внешний контекст, но я получаю ошибки компилятора, указывающие на то, что имя типа должно однозначно ссылаться на один тип в той же области видимости. Это ограничение применяется только при использовании в теле класса, в противном случае это будет работать для функций и пространств имен.
Отслеживание структур, применимых к области в чем-то вроде boost::mpl::vector
Приведенные в учебнике примеры заставляют меня поверить в то, что я столкнусь с той же проблемой, что и в 1, поскольку вектору, после которого нужно выдвинуть объект, нужно дать отдельное имя, к которому нужно будет обращаться конкретно во вложенных областях.
Генерация имени применимого внешнего контекста с помощью счетчика препроцессора.
Это сработало бы в моем простом примере использования выше, но не сработало бы при наличии прерывистых объявлений в пространстве имен или определениях методов за пределами соответствующего класса.
Как получить доступ к этой информации во вложенных областях?
Хорошо, я нашел решение.
Хитрость в том, что decltype(var)
за 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*
в качестве значения дозорного и специализироваться на нем в вспомогательных классах, используемых для построения выходной строки.
C ++ 11 требуется для decltype
и разрешить передачу локальных классов в параметры шаблона.
#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
может вызываться где угодно, поэтому, если мы создадим несколько статических объектов, порядок их построения неизвестен. __LINE__
LOG_CONTEXT
может создавать статические объекты LoggingContext
struct, которая самостоятельно регистрируется в создаваемом локальном контейнере. (Под локальным я имею в виду уникальный в объекте компиляции, может быть достигнуто с помощью анонимного пространства имен)LOG_*
должен взять их текущую строку и взять самый последний LoggingContext из локального регистра. (Или несколько последних, если нужно)constexpr
сематика (но довольно сложная задача)Открытые проблемы:
__FUNCTION__
должно сработать)?PS. Я постараюсь реализовать это на выходных