Boot logging during early application initialization
In complex software systems, the initialization phase is rarely trivial and usually consists of several stages. Errors may occur before the main logging system becomes available, leaving developers without visibility into what exactly went wrong and where.
In practice, logging is initialized as early as possible. However, there are cases where configuration is loaded from an external source, such as a database. This inevitably introduces steps that must run before the logging system is fully operational. What if the database is unavailable or corrupted? It’s also worth noting that the logging configuration itself may contain errors, and the process of loading it can produce additional failures.
To address this gap, a boot logging mechanism is required. The idea is straightforward: before the primary logging system is up and running, a temporary logger is used. If something goes wrong, the boot log can be inspected for diagnostics. If initialization completes successfully, boot log messages can be merged into the the main log.
Logging libraries such as logme are capable of reporting internal errors through a dedicated channel. If that channel is not configured or enabled, those messages are simply discarded. With boot logging in place, errors occurring during configuration loading can still be captured, making it much easier to understand what happened.
Implementing boot logging with logme
The solution is simple. At the very beginning of initialization, a file backend is added to the default channel — this becomes the boot log. If internal diagnostics from logme are required, the CHINT channel can be created and linked to the default one. The setup may look like this:
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);
If everything goes well, the application loads the logging configuration as expected. If an error occurs, it will be available in "path_to_the_boot_log".
Merging with the main log
As mentioned earlier, boot messages can be copied into the main log after configuration loading. For example, if the JSON configuration defines a root_log, early-stage messages can be appended like this:
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);
This approach can be extended in different ways. For instance, the application may read the boot log file path from an environment variable. If the variable is not set or the file cannot be created, early-stage logging can simply be disabled.