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

