Внедрение ведения журнала с использованием глобальных переменных и функторов
Я хочу реализовать регистрацию С++ со следующими характеристиками:
- Он должен быть доступен для всех исходных кодов без необходимости для каждой функции иметь дополнительный параметр (который, как я полагаю, требует глобального)
- Вызов журнала может определять уровень серьезности (INFO, DEBUG, WARN и т.д.), а средство ведения журнала можно установить во время выполнения, чтобы игнорировать вызовы ниже определенного уровня серьезности.
- Входная станция журнала может быть установлена во время выполнения на консоль или в файл.
Мне не нужны:
- Поддержка нескольких стоков журналов во время выполнения (т.е. все идет либо на консоль, либо в файл)
- Поддержка многопоточного ведения журнала
- Возможность передать
cout
-строчные выражения (например, "foo=" << foo
) в вызове журнала. Я передам только std::string
.
Я нашел этот ответ, который, по-видимому, удовлетворяет мои потребности, но это довольно немного над моей головой. Я думаю, что мое замешательство сосредоточено вокруг функторов. (Я читал статью в Википедии, но явно не погрузился.)
Вот те части, которые я понимаю:
- Использование макроса (например, LOG_DEBUG) для удобного определения уровня серьезности и вызова журнала.
- Использование
#ifdef NDEBUG
, чтобы вести компиляцию вызовов ведения журнала (хотя мне нужно иметь возможность устанавливать ведение журнала во время выполнения).
- Обоснование использования макроса для вызова регистратора, чтобы он мог автоматически и незаметно добавлять информацию, такую как
__FILE__
и __LINE__
, в точке, где вызывается регистратор.
- Макрос LOG содержит выражение, начинающееся с
static_cast<std::ostringstream&>
. Я думаю, что это просто связано с оценкой строки форматирования cout
, которую я не намерен поддерживать.
Здесь, где я боюсь:
Logger& Debug() {
static Logger logger(Level::Debug, Console);
return logger;
}
Чтение о operator()
, похоже, что class Logger
используется для создания "функторов". Каждый функтор Logger создается (?) Как с уровнем, так и с LogSink. (Вы "создаете экземпляр" функтора?) LogSink описывается как "бэкэнд, потребляющий предварительно отформатированные сообщения", но я не знаю, как это будет выглядеть или как оно "написано". В какой момент создается экземпляр статического объекта Logger? Что вызывает его создание?
Эти макроопределения...
#define LOG(Logger_, Message_) \
Logger_( \
static_cast<std::ostringstream&>( \
std::ostringstream().flush() << Message_ \
).str(), \
__FUNCTION__, \
__FILE__, \
__LINE__ \
);
#define LOG_DEBUG(Message_) LOG(Debug(), Message_)
... и эта строка кода...
LOG_DEBUG(my_message);
... получить предварительную обработку:
Debug()(my_message, "my_function", "my_file", 42);
Что происходит, когда это выполняется?
Как и где форматированная строка, фактически записанная в "сток-лог"?
(Примечание: было высказано предположение о том, что я смотрю log4cpp - мне было гораздо сложнее понять, чем нужно, не говоря уже о политических проблемах, партийной библиотеки в нашу среду)
ОБНОВЛЕНИЕ:
Чтобы понять, как работает это решение, я попытался написать минимально полную рабочую программу. Я намеренно удалил следующее:
- "магия" с участием std:: ostringstream
- #ifdef NDEBUG
- перечисление класса уровня Logger
- аргумент LogSink ctor (теперь я просто напишу std:: cout)
Вот полный исходный файл:
#include <iostream>
#include <string>
class Logger {
public:
Logger(int l);
void operator()(std::string const& message,
char const* function,
char const* file,
int line);
private:
int _level;
};
Logger::Logger(int l) :
_level(l)
{ }
#define LOG(Logger_, Message_) \
Logger_( \
Message_, \
__FUNCTION__, \
__FILE__, \
__LINE__ \
)
#define LOG_DEBUG(Message_) \
LOG( \
Debug(), \
Message_ \
)
Logger& Debug() {
static Logger logger(1);
return logger;
}
// Use of Logger class begins here
int main(int argc, char** argv) {
LOG_DEBUG("Hello, world!");
return 0;
}
При компиляции:
$ c++ main.cpp
Undefined symbols for architecture x86_64:
"Logger::operator()(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, char const*, char const*, int)", referenced from:
_main in main-c81cf6.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Я вижу, что нет определения функции, которая принимает эти четыре аргумента и записывает их в std::cout
, но каково имя функции, которую мне нужно определить?
(К настоящему моменту я согласен с тем, что должен использовать Boost:: Log, но функторы, очевидно, являются темой, которую я не совсем понимаю.)
Ответы
Ответ 1
Функция Debug
возвращает объект Logger (который создается при первом вызове этой функции).
Этот объект Logger
, по-видимому, имеет operator()()
, определенный для него (судя по определению макроса), что делает его функтором. Кстати, функтор не является чем-то особенным - он определяется определенным типом, который имеет operator()()
. Однако ваш анализ не кажется правильным. Вместо этого
LOG_DEBUG(my_message);
будет разложено на
LOG(Debug(), Message_)
И что в
Debug()(Message_, __FUNCTION__, __FILE__, __LINE__);
Здесь Debug()
возвращает объект, который имеет operator()()
, и этот объект будет использоваться для вызова.
Некоторые QnA
Почему Logger & Подписи отладки() указывают четыре аргумента?
Потому что это не нужно. Debug() просто возвращает (статический) объект Logger, созданный с помощью определенных аргументов (уровень журнала и выходное устройство).
В какой момент создается экземпляр статического объекта Logger? Что вызывает его создание?
Когда функция Debug() вызывается в первый раз, она инициализирует статические объекты. Это основная часть статических переменных функции.
Последнее, но не самое меньшее. Я лично считаю, что это не стоит усилий для написания собственного регистратора. Это утомительно и очень скучно, если вам действительно не нужно что-то особенное. Хотя я не очень сумасшедший как в Boost.Log, так и в log4cpp, я бы (на самом деле), безусловно, использовал один из них вместо того, чтобы катавать собственный регистратор. Даже субоптимальное ведение журнала лучше, чем потратить недели на собственное решение.