Логирование на стадии ранней инициализации приложения

В сложных программных продуктах этап инициализации не тривиален и состоит как минимум из нескольких стадий. Может оказаться так, что какие-то ошибки происходят до того, до того, как логирование на раннем этапе станет доступным. В этом случае разработчику сложно понять, что именно произошло и в каком месте программы.

Конечно же, систему логирования обычно инициализируют как можно раньше. Но, например, в случаях, когда выбран способ инициализации через загрузку конфигурационного файла, а сам файл находится в базе данных, есть все же шаги, которые придется выполнять до полноценной инициализации системы логирования. Что если база данных удалена или испорчена? Не стоит также забывать, что и сам конфигурационный файл системы логирования может содержать какие-то ошибки, а процесс его загрузки способен генерировать дополнительные ошибки.

Для решения проблемы, обозначенной выше, необходимо наличие boot-логгера. Идея проста: до того, как начнет работать основная система логирования, используется временная. Если что-то произошло, откройте boot-лог и посмотрите диагностику. Если инициализация завершилась успешно, то сообщения из boot-лога можно скопировать в начало основного лога.

Библиотеки логирования, такие как logme, умеют выводить информацию об ошибках, которые произошли в самом коде библиотеки, в специализированный канал. Если канал не создан или не активирован, библиотека будет отбрасывать сообщения. Но если при загрузке конфигурации происходят ошибки, то эту информацию можно будет получить и выяснить, что именно произошло.

Реализация boot-логирования с помощью logme

Задача решается очень просто. В самом начале инициализации добавляем backend для записи в файл (это будет, собственно, boot-лог) в канал по умолчанию. Если интересует внутренняя диагностика logme, то создаем канал CHINT, привязав к нему канал по умолчанию. Код по созданию boot-логгера может выглядеть так:

auto ch = Logme::Instance->GetDefaultChannelPtr();
auto file = std::make_shared<Logme::FileBackend>(ch);
file->Create("path_to_the_boot_log");
ch->AddBackend(file);

auto chi = Logme::Instance->CreateChannel(CHINT);
chi->AddLink(ch);

// Initalization
...

Logme::Instance->LoadConfiguaration(json);

Если все пройдет успешно, программа загрузит конфигурацию системы логирования. Если произойдет ошибка, вы сможете найти ее в «path_to_the_boot_log».

Слияние с основным логом

Как уже упоминалось выше, система может добавлять сообщения из boot-лога в лог, созданный после загрузки конфигурации. Например, если json-конфигурация создает «root_log», то можно добавить в него сообщения стадии ранней инициализации следующим образом:

void FlushBootLog(Logme::BackendPtr& buffer)
{
  auto b = (BufferBackend*)buffer.get();
  if (b->Buffer.empty())
    return;

  auto ch = Logme::Instance->GetDefaultChannelPtr();

  int context{};
  auto file = ch->FindFirstBackend(FileBackend::TYPE_ID, context);
  if (file == nullptr)
    return;

  FileBackend* fileBackend = (FileBackend*)file.get();
  fileBackend->AppendString(&b->Buffer[0], -1);

  b->Buffer.clear();
}

...
auto ch = Logme::Instance->GetDefaultChannelPtr();
auto file = std::make_shared<Logme::FileBackend>(ch);
file->Create("path_to_the_boot_log");
ch->AddBackend(file);

auto buffer = std::make_shared<Logme::BufferBackend>(ch);
ch->AddBackend(buffer);

auto chi = Logme::Instance->CreateChannel(CHINT);
chi->AddLink(ch);

// Initalization
...

Logme::Instance->LoadConfiguaration(json);
FlushBootLog(buffer);

Этот подход можно развивать по-разному. Например, программа может брать имя boot-лога из переменной окружения. Если переменная не задана или файл лога создать не удалось, раннее логирование можно просто не включать.


Выводы

Boot-логирование — это простой, но крайне полезный механизм, который закрывает «слепую зону» в самом начале жизненного цикла приложения. Оно позволяет диагностировать проблемы, возникающие до полной инициализации основной системы логирования, включая ошибки загрузки конфигурации, проблемы с внешними зависимостями и сбои внутри самой logging-библиотеки.

Предложенный подход не требует значительных усилий для внедрения, но при этом существенно повышает наблюдаемость системы и сокращает время на поиск трудноуловимых ошибок. Особенно это актуально для сложных приложений с многоступенчатой инициализацией или внешними источниками конфигурации.

В практическом применении важно определить стратегию использования boot-логов: хранить их отдельно для диагностики или интегрировать в основной лог после успешного старта. Оба варианта имеют право на жизнь и выбираются исходя из требований к эксплуатации и отладке системы.

Добавить комментарий