Frontend Options

The frontend options provide a flexible way to configure hot path settings at compile time. These options allow for the customisation of queue types and memory allocation strategies, including the use of huge pages on Linux systems for improved performance.

Each frontend thread operates with its own queue, which can be configured with one of the following options:

  • UnboundedBlocking: Starts with a small initial capacity. The queue reallocates up to FrontendOptions::unbounded_queue_max_capacity and then blocks the calling thread until space becomes available.

  • UnboundedDropping: Starts with a small initial capacity. The queue reallocates up to FrontendOptions::unbounded_queue_max_capacity and then discards log messages.

  • BoundedBlocking: Has a fixed capacity and never reallocates. It blocks the calling thread when the limit is reached until space becomes available. A single log record larger than the fixed capacity cannot fit and fails immediately instead of blocking forever.

  • BoundedDropping: Has a fixed capacity and never reallocates. It discards log messages when the limit is reached.

Even though each thread has its own queue, a single queue type must be defined for the entire application. By default the UnboundedBlocking queue type is used.

To modify the queue type, define your own options type by deriving from FrontendOptions and overriding only the values that differ. Then use that type to create a custom FrontendImpl and LoggerImpl.

It is important to consistently use your custom types throughout the application, instead of the default ones.

Warning

FrontendOptions are intended to be an application-wide choice. Mixing different FrontendImpl<T> or LoggerImpl<T> specializations is not a supported configuration. Use one frontend specialization consistently throughout the application.

Error Notifications

The library provides error notifications through the BackendOptions::error_notifier callback:

  • Unbounded queues: Notify when queue reallocations occur, and when the configured maximum capacity is reached causing blocking or dropped messages

  • Bounded queues: Notify when messages are dropped due to full queues, including a count of dropped messages

These notifications are processed by the backend thread and can help monitor queue behavior in production.

Queue Memory Management

Queue Shrinking

For unbounded queues, you can explicitly reduce memory usage on specific threads using Frontend::shrink_thread_local_queue(). This is particularly useful in thread pool scenarios where some threads may experience logging bursts that cause queue growth, but subsequent tasks don’t require the large capacity:

// After a logging-intensive task completes, shrink the queue
quill::Frontend::shrink_thread_local_queue(512 * 1024);  // Shrink to 512KB

Monitoring Queue Allocations

By default, Quill automatically reports queue allocation events to stderr through BackendOptions::error_notifier. You’ll see messages like:

Allocated a new SPSC queue with a capacity of 256 KiB (previously 128 KiB) from thread 3764

These allocation messages are informational, not errors, indicating the queue is dynamically resizing to handle traffic spikes. To customize this behavior, you can override the error notifier:

quill::BackendOptions backend_options{
    .error_notifier = [](const std::string& error_message) {
        // Custom handling - log to your preferred destination
        my_logger.info("Quill: {}", error_message);
    }
};

To completely disable these notifications, set the error notifier to an empty function:

quill::BackendOptions backend_options{
    .error_notifier = {}  // Disable notifications
};

This disables callback notifications from BackendOptions::error_notifier. If Quill later encounters a malformed format string, it will still write the fallback [Could not format log statement ...] entry to the configured sink output.

Mixed Hot/Cold Path Optimization

For applications with both hot path (performance-critical) and cold path (less frequent) logging threads, you can optimize memory allocation by using a larger initial queue size globally, then selectively shrinking queues on cold path threads:

// Configure large initial capacity to avoid hot path allocations
struct MyFrontendOptions : quill::FrontendOptions
{
    static constexpr size_t initial_queue_capacity = 64 * 1024 * 1024;  // 64MB
    // Other FrontendOptions values are inherited unchanged
};

// Hot path threads: Use the large queue (no shrinking needed)
// LOG_INFO(hot_path_logger, "High frequency logging...");

// Cold path threads: Shrink to smaller size after initialization
void cold_path_thread_init() {
    quill::Frontend::shrink_thread_local_queue(128 * 1024);  // Shrink to 128KB
    // LOG_INFO(cold_path_logger, "Infrequent logging...");
}

This approach prevents runtime allocations on performance-critical threads while conserving memory on threads with lighter logging workloads.

Example Usage

For example, to use a BoundedDropping queue while inheriting the remaining defaults, you can follow the steps below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/ConsoleSink.h"

/**
 * This example demonstrates defining and utilizing custom FrontendOptions.
 * It's useful when you need to modify the queue type or capacity.
 * FrontendOptions are compile-time options and must be passed as a template argument.
 */

// Derive from FrontendOptions and override only the values that differ.
struct CustomFrontendOptions : quill::FrontendOptions
{
  static constexpr quill::QueueType queue_type = quill::QueueType::BoundedDropping;
};

// To utilize our custom FrontendOptions, we define a Frontend class using CustomFrontendOptions
using CustomFrontend = quill::FrontendImpl<CustomFrontendOptions>;

// The Logger type must also be defined
using CustomLogger = quill::LoggerImpl<CustomFrontendOptions>;

int main()
{
  // Start the backend thread
  quill::BackendOptions backend_options;
  quill::Backend::start(backend_options); // or quill::Backend::start<CustomFrontendOptions>(backend_options, signal_handler_options);

  // All frontend operations must utilize CustomFrontend instead of quill::Frontend
  auto console_sink = CustomFrontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
  CustomLogger* logger = CustomFrontend::create_or_get_logger("root", std::move(console_sink));

  // log something
  LOG_INFO(logger, "This is a log info example {}", 123);
  LOG_WARNING(logger, "This is a log warning example {}", 123);
}