User’s API

Config Class

struct Config

Public Members

std::string backend_thread_name = "Quill_Backend"

Custom name for the backend thread.

bool backend_thread_yield = false

Determines whether the backend thread will “busy wait” by spinning around every caller thread’s local spsc queue. If enabled, this option reduces the OS scheduler priority when the backend worker thread is running on a shared CPU. The thread will yield when there is no remaining work to do.

Note

This option only takes effect when backend_thread_sleep_duration is set to 0.

std::chrono::nanoseconds backend_thread_sleep_duration = std::chrono::nanoseconds{500}

Determines the duration for which the backend thread will “busy wait” by spinning around every caller thread’s local spsc queue. If a value is set, each time the backend thread sees that there are no remaining logs to process in the queues, it will sleep for the specified duration.

size_t backend_thread_use_transit_buffer = true

Determines the behavior of the backend worker thread. By default, it will drain all hot queues and buffer the messages. If this option is set to false, the backend thread will simply process the message with the lowest timestamp from the SPSC queues without buffering.

Note

It is generally not recommended to set this to false, unless you want to limit the logging thread’s memory usage.

size_t backend_thread_transit_events_soft_limit = 800

The backend worker thread gives priority to reading messages from the SPSC queues of all the hot threads and temporarily buffers them.

If the hot threads continuously push messages to the queues (e.g., logging in a loop), no logs can ever be processed.

When the soft limit is reached (default: 800), this number of events will be logged to the log files before continuing to read the SPSC queues.

The SPSC queues are emptied on each iteration, so the actual messages from the SPSC queues can be much greater than the backend_thread_transit_events_soft_limit.

Note

This number represents a limit across ALL hot threads.

Note

Applicable only when backend_thread_use_transit_buffer = true.

size_t backend_thread_transit_events_hard_limit = 100'000

The backend worker thread gives priority to reading messages from the SPSC queues of all the hot threads and temporarily buffers them.

If the hot threads continuously push messages to the queues (e.g., logging in a loop), no logs can ever be processed.

As the backend thread buffers messages, it can keep buffering indefinitely if the hot threads keep pushing.

This limit is the maximum size of the backend thread buffer. When reached, the backend worker thread will stop reading the SPSC queues until there is space available in the buffer.

Note

This limit applies PER hot thread.

Note

Applicable only when backend_thread_use_transit_buffer = true.

uint32_t backend_thread_initial_transit_event_buffer_capacity = 64

The backend worker thread pops all log messages from the SPSC queues and buffers them in a local ring buffer queue as transit events. The transit_event_buffer is unbounded, with a customizable initial capacity (in items, not bytes). Each newly spawned hot thread will have its own transit_event_buffer. The capacity must be a power of two.

Note

Applicable only when backend_thread_use_transit_buffer = true.

bool backend_thread_strict_log_timestamp_order = true

The backend worker thread iterates through all active SPSC queues and pops all messages from each queue. It then sorts the messages by timestamp and logs them.

Each active queue corresponds to a thread, and when multiple threads are logging simultaneously, it is possible to read a timestamp from the last queue in the iteration but miss that timestamp when the first queue was read because it was not available at that time.

When this option is enabled, the backend worker thread takes a timestamp (now()) before reading the queues. It uses that timestamp to ensure that each log message’s timestamp from the active queues is less than or equal to the stored now() timestamp, guaranteeing ordering by timestamp.

Messages that fail the above check are not logged and remain in the queue. They are checked again in the next iteration. The timestamp check is performed with microsecond precision.

Enabling this option may cause a delay in popping messages from the SPSC queues.

Note

Applicable only when backend_thread_use_transit_buffer = true.

bool backend_thread_empty_all_queues_before_exit = true

When this option is enabled and the application is terminating, the backend worker thread will not exit until all the SPSC queues are empty. This ensures that all messages are logged.

However, if there is a thread during application destruction that keeps trying to log indefinitely, the backend worker thread will be unable to exit because it keeps popping log messages.

When this option is disabled, the backend worker thread will try to read the queues once and then exit. Reading the queues only once means that some log messages can be dropped, especially when backend_thread_strict_log_timestamp_order is set to true.

uint16_t backend_thread_cpu_affinity = (std::numeric_limits<uint16_t>::max)()

Pins the backend thread to the specified CPU.

By default, Quill does not pin the backend thread to any CPU unless a value is specified. Use std::numeric_limits<uint16_t>::max() as an undefined value to avoid setting CPU affinity.

std::string default_logger_name = "root"

Sets the name of the root logger.

backend_worker_notification_handler_t backend_thread_notification_handler

The background thread might occasionally throw an exception that cannot be caught in the user threads. In that case, the backend worker thread will call this callback instead.

Set up a custom notification handler to be used if the backend thread encounters any error. This handler is also used to deliver messages to the user, such as when the unbounded queue reallocates or when the bounded queue becomes full.

When not set here, the default is: backend_thread_notification_handler = [](std::string const& s) { std::cerr << s << std::endl; }

To disable notifications, use: backend_thread_notification_handler = [](std::string const&) { }

TimestampClock *default_custom_timestamp_clock = nullptr

Sets a custom clock that will be used to obtain the timestamp. This is useful, for example, during simulations where you need to simulate time.

TimestampClockType default_timestamp_clock_type = TimestampClockType::Tsc

Sets the clock type that will be used to obtain the timestamp. Options: rdtsc or system clock.

  • rdtsc mode: TSC clock provides better performance on the caller thread. However, the initialization time of the application is longer as multiple samples need to be taken in the beginning to convert TSC to nanoseconds.

    When using the TSC counter, the backend thread will periodically call std::chrono::system_clock::now() to resync the TSC based on the system clock. The backend thread constantly keeps track of the difference between TSC and the system wall clock to provide accurate timestamps.

  • system mode: std::chrono::system_clock::now() is used to obtain the timestamp.

By default, rdtsc mode is enabled.

Note

You need to have an invariant TSC for this mode to work correctly. Otherwise, use TimestampClockType::System.

std::vector<std::shared_ptr<Handler>> default_handlers = {}

Resets the root logger and recreates the logger with the given handler. This function can also be used to change the format pattern of the logger. If the vector is empty, the stdout handler is used by default.

bool enable_console_colours = false

Enables colors in the terminal. This option is only applicable when the default_handlers vector is empty (the default stdout handler is used). If you set up your own stdout handler with a custom pattern, you need to enable the colors yourself. See example_console_colours_with_custom_formatter.cpp and example_console_colours.cpp for examples.

std::chrono::milliseconds rdtsc_resync_interval = std::chrono::milliseconds{500}

This option is only applicable if the RDTSC clock is enabled. When the system clock is used, this option can be ignored.

Controls the frequency at which the backend thread recalculates and syncs the TSC by obtaining the system time from the system wall clock. The TSC clock drifts slightly over time and is not synchronized with NTP server updates. A smaller value results in more accurate log timestamps. Decreasing this value further provides more accurate timestamps with the system_clock. Changing this value only affects the performance of the backend worker thread.

uint32_t default_queue_capacity = {131'072}

Quill uses an unbounded/bounded SPSC queue per spawned thread to forward the log messages to the backend thread. During high logging activity, if the backend thread cannot consume logs fast enough, the queue may become full. In this scenario, the caller thread does not block but instead allocates a new queue with the same capacity. If the backend thread is falling behind, consider reducing the sleep duration of the backend thread or pinning it to a dedicated core to keep the queue less congested. The queue size can be increased or decreased based on user needs. The queue is shared between two threads and should not exceed the size of the LLC cache.

Note

This capacity automatically doubles when the unbounded queue is full.

Warning

The configured queue size must be in bytes, a power of two, and a multiple of the page size (4096). For example: 32,768; 65,536; 131,072; 262,144; 524,288.

bool enable_huge_pages_hot_path = {false}

When set to true, enables huge pages for all queue allocations on the hot path. Make sure you have huge pages enabled on your Linux system for this to work.

To check if huge pages are enabled: cat /proc/meminfo | grep HugePages

To set the number of huge pages: sudo sysctl -w vm.nr_hugepages=<number_of_hugepages>

Note

This option is only supported on Linux.

Log Levels

enum quill::LogLevel

Log level enum

Values:

enumerator TraceL3
enumerator TraceL2
enumerator TraceL1
enumerator Debug
enumerator Info
enumerator Warning
enumerator Error
enumerator Critical
enumerator Backtrace

This is only used for backtrace logging. Should not be set by the user.

enumerator None
enumerator Dynamic

This is only used for dynamic logging. Should not be set by the user.

Logger Class

class Logger

Thread safe logger. Logger must be obtained from LoggerCollection get_logger(), therefore constructors are private

Public Functions

Logger(Logger const&) = delete

Deleted

inline void *operator new(size_t i)

We align the logger object to it’s own cache line. It shouldn’t make much difference as the logger object size is exactly 1 cache line

inline LogLevel log_level() const noexcept
Returns:

The log level of the logger

inline void set_log_level(LogLevel log_level)

Set the log level of the logger

Parameters:

log_level – The new log level

template<LogLevel log_statement_level>
inline bool should_log() const noexcept

Checks if the given log_statement_level can be logged by this logger

Template Parameters:

log_statement_level – The log level of the log statement to be logged

Returns:

bool if a message can be logged based on the current log level

inline bool should_log(LogLevel log_statement_level) const noexcept

Checks if the given log_statement_level can be logged by this logger

Parameters:

log_statement_level – The log level of the log statement to be logged

Returns:

bool if a message can be logged based on the current log level

template<typename TMacroMetadata, typename TFormatString, typename ...FmtArgs>
inline void log(LogLevel dynamic_log_level, TFormatString format_string, FmtArgs&&... fmt_args)

Push a log message to the spsc queue to be logged by the backend thread. One spsc queue per caller thread. This function is enabled only when all arguments are fundamental types. This is the fastest way possible to log

Note

This function is thread-safe.

Parameters:
  • format_string – format

  • fmt_args – arguments

inline void init_backtrace(uint32_t capacity, LogLevel backtrace_flush_level = LogLevel::None)

Init a backtrace for this logger. Stores messages logged with LOG_BACKTRACE in a ring buffer messages and displays them later on demand.

Parameters:
  • capacity – The max number of messages to store in the backtrace

  • backtrace_flush_level – If this loggers logs any message higher or equal to this severity level the backtrace will also get flushed. Default level is None meaning the user has to call flush_backtrace explicitly

inline void flush_backtrace()

Dump any stored backtrace messages

Handler Base Class

class Handler

Base class for handlers

Subclassed by quill::NullHandler, quill::StreamHandler

Public Functions

Handler() = default

Constructor Uses the default pattern formatter

virtual ~Handler() = default

Destructor

inline void set_pattern(std::string const &log_pattern, std::string const &time_format = std::string{"%H:%M:%S.%Qns"}, Timezone timezone = Timezone::LocalTime)

Set a custom formatter for this handler

Warning

This function is not thread safe and should be called before any logging to this handler happens Prefer to set the formatter in the constructor when possible via the FileHandlerConfig

Parameters:
  • log_pattern – format pattern see PatternFormatter

  • time_format – defaults to “%H:%M:%S.%Qns”

  • timezone – defaults to PatternFormatter::Timezone::LocalTime

inline PatternFormatter &formatter()

Returns the owned formatter by the handler

Note

: Accessor for backend processing

Returns:

reference to the pattern formatter of this handler

virtual void write(fmt_buffer_t const &formatted_log_message, quill::TransitEvent const &log_event) = 0

Logs a formatted log message to the handler

Note

: Accessor for backend processing

Parameters:
  • formatted_log_message – input log message to write

  • log_event – transit event

virtual void flush() noexcept = 0

Flush the handler synchronising the associated handler with its controlled output sequence.

inline virtual void run_loop() noexcept

Executes periodically by the backend thread, providing an opportunity for the user to perform custom tasks. For example, batch committing to a database, or any other desired periodic operations.

Note

It is recommended to avoid performing heavy operations within this function as it may adversely affect the performance of the backend thread.

inline void set_log_level(LogLevel log_level)

Sets a log level filter on the handler. Log statements with higher or equal severity only will be logged

Note

thread safe

Parameters:

log_level – the log level severity

inline LogLevel get_log_level() const noexcept

Looks up the existing log level filter that was set by set_log_level and returns the current log level

Note

thread-safe

Returns:

the current log level of the log level filter

void add_filter(std::unique_ptr<FilterBase> filter)

Filters Adds a new filter for this handler. Filters can be added at any time to the handler.

Note

: thread-safe

Parameters:

filter – instance of a filter class as unique ptr

bool apply_filters(char const *thread_id, std::chrono::nanoseconds log_message_timestamp, LogLevel log_level, MacroMetadata const &metadata, fmt_buffer_t const &formatted_record)

Apply all registered filters.

Note

: called internally by the backend worker thread.

Returns:

result of all filters

Filter Base Class

class FilterBase

Base filter class. Filters can be added to Handlers

Public Functions

inline explicit FilterBase(std::string filter_name)

Constructor

Parameters:

filter_name – unique filter name

virtual ~FilterBase() = default

Destructor

virtual bool filter(char const *thread_id, std::chrono::nanoseconds log_message_timestamp, MacroMetadata const &metadata, fmt_buffer_t const &formatted_record) noexcept = 0

Filters a log message

Parameters:
  • thread_id – thread id

  • log_message_timestamp – timestamp

  • metadata – log message

  • formatted_record – formatted log message

Returns:

true if the log message should be written to the file, false otherwise

inline virtual std::string const &get_filter_name() const noexcept

Gets the name of the filter. Only useful if an existing filter is needed to be looked up

Returns:

the name of the filter

PatternFormatter Class

class PatternFormatter

Public Types

enum TimestampPrecision

Public classes Stores the precision of the timestamp

Values:

enumerator None
enumerator MilliSeconds
enumerator MicroSeconds
enumerator NanoSeconds

Public Functions

inline PatternFormatter()

Main PatternFormatter class Constructor

inline PatternFormatter(std::string const &format_pattern, std::string const &timestamp_format, Timezone timezone)

Constructor for a PatterFormatter with a custom format

Parameters:
  • format_pattern – format_pattern a format string. Must be passed using the macro QUILL_STRING(“format string”);

  • timestamp_format – The for format of the date. Same as strftime() format with extra specifiers Qms Qus Qns

  • timezone – The timezone of the timestamp, local_time or gmt_time

~PatternFormatter() = default

Destructor

Internal Classes

class HandlerCollection

Creates and manages active handlers

Public Functions

HandlerCollection(HandlerCollection const&) = delete

Deleted

std::shared_ptr<Handler> stdout_console_handler(std::string const &stdout_handler_name = std::string{"stdout"}, ConsoleColours const &console_colours = ConsoleColours{})

The handlers are used by the backend thread, so after their creation we want to avoid mutating their member variables. So here the API returns pointers to the base class to somehow restrict the user from creating a handler and calling a set() function on the handler after it’s creation. Currently no built-in handlers have setters function.

template<typename THandler, typename ...Args>
inline std::shared_ptr<Handler> create_handler(std::string const &handler_name, Args&&... args)

Create a handler

std::shared_ptr<Handler> get_handler(std::string const &handler_name)

Get an existing a handler

Parameters:

handler_name – the name of the handler

Throws:

std::runtime_error – if the handler does not exist

Returns:

a shared_ptr to the handler

void subscribe_handler(std::shared_ptr<Handler> const &handler_to_insert)

Subscribe a handler to the vector of active handlers so that the backend thread can see it Called each time a new Logger instance is created. If the Handler already exists then it is not added in the collection again Objects that are added to _active_handlers_collection never get removed again

Parameters:

handler_to_insert – a handler to add

std::vector<std::weak_ptr<Handler>> active_handlers() const

Get a list of all the active subscribed handlers. The list contains each handler only once regardless the amount of Logger instances using it This is not used for logging by the backend but only in special cases when e.g. it needs to iterate through all handlers for e.g. to flush

Returns:

a vector containing all the active handlers

void remove_unused_handlers()

Called by the backend worker thread only to remove any handlers that are not longer in use