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 storednow()
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.
-
std::string backend_thread_name = "Quill_Backend"
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.
-
enumerator TraceL3
Logger Class
-
class Logger
Thread safe logger. Logger must be obtained from LoggerCollection get_logger(), therefore constructors are private
Public Functions
-
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 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
-
inline void *operator new(size_t i)
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
-
Handler() = default
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
-
inline explicit FilterBase(std::string filter_name)
PatternFormatter Class
-
class PatternFormatter
Public Types
Public Functions
-
inline PatternFormatter()
Main PatternFormatter class Constructor
-
inline PatternFormatter(std::string const &format_pattern, std::string const ×tamp_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
-
inline PatternFormatter()
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.
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
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
-
HandlerCollection(HandlerCollection const&) = delete