Внедрение ведения журнала с использованием глобальных переменных и функторов

Я хочу реализовать регистрацию С++ со следующими характеристиками:

  • Он должен быть доступен для всех исходных кодов без необходимости для каждой функции иметь дополнительный параметр (который, как я полагаю, требует глобального)
  • Вызов журнала может определять уровень серьезности (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, я бы (на самом деле), безусловно, использовал один из них вместо того, чтобы катавать собственный регистратор. Даже субоптимальное ведение журнала лучше, чем потратить недели на собственное решение.