Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Asynchronous Low Latency C++ Logging Library

License

NotificationsYou must be signed in to change notification settings

odygrd/quill

Repository files navigation


Quill C++ Logging Library

Quill

Asynchronous Low Latency C++ Logging Library

fedora-ciubuntu-cibsd-cimacos-ciwindows-ci
CodecovCodacyCodeFactor
licenselanguage
Logging Demo

🧭 Table of Contents


✨ Introduction

Quill is ahigh-performance asynchronous logging library written inC++. It is designed for low-latency, performance-critical applications where every microsecond counts.

  • Performance-Focused: Quill consistently outperforms many popular logging libraries.
  • Feature-Rich: Packed with advanced features to meet diverse logging needs.
  • Battle-Tested: Proven in demanding production environments.
  • Extensive Documentation: Comprehensive guides and examples available.
  • Community-Driven: Open to contributions, feedback, and feature requests.

Try it onCompiler Explorer


⏩ Quick Start

Getting started is easy and straightforward. Follow these steps to integrate the library into your project:

Installation

You can install Quill using the package manager of your choice:

Package ManagerInstallation Command
vcpkgvcpkg install quill
Conanconan install quill
Homebrewbrew install quill
Meson WrapDBmeson wrap install quill
Condaconda install -c conda-forge quill
Bzlmodbazel_dep(name = "quill", version = "x.y.z")
xmakexrepo install quill
nixnix-shell -p quill-log

Setup

Once installed, you can start using Quill with the following code:

#include"quill/Backend.h"#include"quill/Frontend.h"#include"quill/LogMacros.h"#include"quill/Logger.h"#include"quill/sinks/ConsoleSink.h"#include<string_view>intmain(){quill::Backend::start();  quill::Logger* logger =quill::Frontend::create_or_get_logger("root", quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"));LOG_INFO(logger,"Hello from {}!", std::string_view{"Quill"});}

🎯 Features

  • High-Performance: Ultra-low latency performance. ViewBenchmarks
  • Asynchronous Processing: Background thread handles formatting and I/O, keeping your main thread responsive.
  • Minimal Header Includes:
    • Frontend: OnlyLogger.h andLogMacros.h needed for logging. Lightweight with minimal dependencies.
    • Backend: Single.cpp file inclusion. No backend code injection into other translation units.
  • Compile-Time Optimization: Eliminate specific log levels at compile time.
  • Custom Formatters: Define your own log output patterns.SeeFormatters.
  • Timestamp-Ordered Logs: Simplify debugging of multithreaded applications with chronologically ordered logs.
  • Flexible Timestamps: Support forrdtsc,chrono, orcustom clocks - ideal for simulations and more.
  • Backtrace Logging: Store messages in a ring buffer for on-demand display.SeeBacktrace Logging
  • Multiple Output Sinks: Console (with color), files (with rotation), JSON, ability to create custom sinks and more.
  • Log Filtering: Process only relevant messages.SeeFilters.
  • JSON Logging: Structured log output.SeeJSON Logging
  • Configurable Queue Modes:bounded/unbounded andblocking/dropping options with monitoring on dropped messages,queue reallocations, and blocked hot threads.
  • Crash Handling: Built-in signal handler for log preservation during crashes.
  • Huge Pages Support (Linux): Leverage huge pages on the hot path for optimized performance.
  • Wide Character Support (Windows): Compatible with ASCII-encoded wide strings and STL containers consisting of widestrings.
  • Exception-Free Option: Configurable builds with or without exception handling.
  • Clean Codebase: Maintained to high standards, warning-free even at strict levels.
  • Type-Safe API: Built on{fmt} library.

🚀 Performance

System Configuration

  • OS: Linux RHEL 9.4

  • CPU: Intel Core i5-12600 (12th Gen) @ 4.8 GHz

  • Compiler: GCC 13.1

  • Benchmark-Tuned System: The system is specifically tuned for benchmarking.

  • Command Line Parameters:

    $ cat /proc/cmdlineBOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.14.0-427.13.1.el9_4.x86_64 root=/dev/mapper/rhel-root ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rhel-swap rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet nohz=on nohz_full=1-5 rcu_nocbs=1-5 isolcpus=1-5 mitigations=off transparent_hugepage=never intel_pstate=disable nosoftlockup irqaffinity=0 processor.max_cstate=1 nosoftirqd sched_tick_offload=0 spec_store_bypass_disable=off spectre_v2=off iommu=pt

You can find the benchmark code on thelogger_benchmarks repository.

Latency

The results presented in the tables below are measured innanoseconds (ns).

The tables are sorted by the 95th percentile

Logging Numbers

LOG_INFO(logger, "Logging int: {}, int: {}, double: {}", i, j, d).

1 Thread Logging
Library50th75th90th95th99th99.9th
Quill Bounded Dropping Queue7889911
fmtlog9910101213
Quill Unbounded Queue101010101214
PlatformLab NanoLog131416171925
MS BinLog212122225693
XTR7729303353
Reckless262831323549
BqLog292930495671
Iyengar NanoLog8396117125152197
spdlog143147152158165177
g3log116112591329141916021827

numbers_1_thread_logging.webp

4 Threads Logging Simultaneously
Library50th75th90th95th99th99.9th
fmtlog899101113
Quill Bounded Dropping Queue8910101214
XTR789113138
Quill Unbounded Queue101111121315
PlatformLab NanoLog151720232732
MS BinLog2122222362100
Reckless192326283455
BqLog313334556173
Iyengar NanoLog5890123131168242
spdlog210243288313382694
g3log127113371396143716141899

numbers_4_thread_logging.webp

Logging Large Strings

Loggingstd::string over 35 characters to prevent the short string optimization.

LOG_INFO(logger, "Logging int: {}, int: {}, string: {}", i, j, large_string).

1 Thread Logging
Library50th75th90th95th99th99.9th
Quill Bounded Dropping Queue111313141516
fmtlog111213141517
Quill Unbounded Queue141516171819
MS BinLog2223242561100
PlatformLab NanoLog151721273339
XTR8929313554
BqLog293031516071
Reckless91107115118124135
Iyengar NanoLog8697119128159268
spdlog120124128132141151
g3log8819561018108912641494

large_strings_1_thread_logging.webp

4 Threads Logging Simultaneously
Library50th75th90th95th99th99.9th
XTR91113143240
fmtlog111213141619
Quill Bounded Dropping Queue131415161719
Quill Unbounded Queue151617181921
MS BinLog2325272865105
PlatformLab NanoLog162032384451
BqLog323335566476
Reckless7994104107114132
Iyengar NanoLog8593125133168237
spdlog178218261281381651
g3log99210551121117813601600

large_strings_4_thread_logging.webp

Logging Complex Types

Loggingstd::vector<std::string> containing 16 large strings, each ranging from 50 to 60 characters.

Note: some of the previous loggers do not support passing astd::vector as an argument.

LOG_INFO(logger, "Logging int: {}, int: {}, vector: {}", i, j, v).

1 Thread Logging
Library50th75th90th95th99th99.9th
Quill Bounded Dropping Queue485053555862
Quill Unbounded Queue545657586166
MS BinLog6869727479281
XTR284294340346356575
fmtlog711730754770804834
spdlog619162616330638666337320

vector_1_thread_logging.webp

4 Threads Logging Simultaneously
Library50th75th90th95th99th99.9th
Quill Bounded Dropping Queue505254566082
MS BinLog7072757988286
Quill Unbounded Queue97107116122135148
XTR512711761791865945
fmtlog780804823835860896
spdlog646965496641673576319430

vector_4_thread_logging.webp

The benchmark methodology involves logging 20 messages in a loop, calculating and storing the average latency for those20 messages, then waiting around ~2 milliseconds, and repeating this process for a specified number of iterations.

In theQuill Bounded Dropping benchmarks, the dropping queue size is set to262,144 bytes, which is double thedefault size of131,072 bytes.

Throughput

Throughput is measured by calculating the maximum number of log messages the backend logging thread can write to a logfile per second.

The tests were run on the same system used for the latency benchmarks.

Although Quill’s primary focus is not on maximizing throughput, it efficiently manages log messages across multiplethreads. Benchmarking throughput of asynchronous logging libraries presents certain challenges. Some libraries may droplog messages, leading to smaller-than-expected log files, while others only provide asynchronous flushing, making itdifficult to verify when the backend thread has fully processed all messages.

For comparison, we benchmark against other asynchronous logging libraries that offer guaranteed logging with aflush-and-wait mechanism.

Note thatMS BinLog writes log data to a binary file, which requires offline formatting with an additionalprogram—this makes it an unfair comparison, but it is included for reference.

Similarly,BqLog (binary log) uses the compressed binary log appender, and its log files are not human-readable unlessprocessed offline. However, it is included for reference. The other version ofBqLog is using a text appender andproduces human-readable log files.

In the same way,Platformlab Nanolog also outputs binary logs and is expected to deliver high throughput. However, forreasons unexplained, the benchmark runs significantly slower (10x longer) than the other libraries, so it is excludedfrom the table.

Logging 4 million times the message"Iteration: {} int: {} double: {}"

Librarymillion msg/secondelapsed time
MS BinLog (binary log)63.8062 ms
BqLog (binary log)15.92251 ms
Quill5.70701 ms
BqLog4.93811 ms
spdlog3.541128 ms
fmtlog2.901378 ms
Reckless2.721471 ms
XTR2.611534 ms

throughput_chart.webp

Compilation Time

Compile times are measured usingclang 15 and forRelease build.

Below, you can find the additional headers that the library will include when you need to log, followingtherecommended_usageexample

quill_v5_1_compiler_profile.speedscope.png

There is also a compile-time benchmark measuring the compilation time of 2000 auto-generated log statements withvarious arguments. You can findithere. It takesapproximately 30 seconds to compile.

quill_v5_1_compiler_bench.speedscope.png

Verdict

Quill excels in hot path latency benchmarks and supports high throughput, offering a rich set of features that outshinesother logging libraries.

The human-readable log files facilitate easier debugging and analysis. While initially larger, they compressefficiently, with the size difference between human-readable and binary logs becoming minimal once zipped.

For example, for the same amount of messages:

ms_binlog_backend_total_time.blog (binary log): 177 MBms_binlog_backend_total_time.zip (zipped binary log): 35 MB
quill_backend_total_time.log (human-readable log): 448 MBquill_backend_total_time.zip (zipped human-readable log): 47 MB

If Quill were not available, MS BinLog would be a strong alternative. It delivers great latency on the hot path andgenerates smaller binary log files. However, the binary logs necessitate offline processing with additional tools, whichcan be less convenient.


🧩 Usage

Also, see theQuick Start Guide for a brief introduction.

#include"quill/Backend.h"#include"quill/Frontend.h"#include"quill/LogMacros.h"#include"quill/Logger.h"#include"quill/sinks/ConsoleSink.h"#include"quill/std/Array.h"#include<string>#include<utility>intmain(){// Backend  quill::BackendOptions backend_options;quill::Backend::start(backend_options);// Frontendauto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");  quill::Logger* logger =quill::Frontend::create_or_get_logger("root",std::move(console_sink));// Change the LogLevel to print everything  logger->set_log_level(quill::LogLevel::TraceL3);// A log message with number 123int a =123;  std::string l ="log";LOG_INFO(logger,"A {} message with number {}", l, a);// libfmt formatting language is supported 3.14e+00doublepi =3.141592653589793;LOG_INFO(logger,"libfmt formatting language is supported {:.2e}",pi);// Logging STD types is supported [1, 2, 3]  std::array<int,3> arr = {1,2,3};LOG_INFO(logger,"Logging STD types is supported {}", arr);// Logging STD types is supported [arr: [1, 2, 3]]LOGV_INFO(logger,"Logging STD types is supported", arr);// A message with two variables [a: 123, b: 3.17]double b =3.17;LOGV_INFO(logger,"A message with two variables", a, b);for (uint32_t i =0; i <10; ++i)  {// Will only log the message once per secondLOG_INFO_LIMIT(std::chrono::seconds{1}, logger,"A {} message with number {}", l, a);LOGV_INFO_LIMIT(std::chrono::seconds{1}, logger,"A message with two variables", a, b);  }LOG_TRACE_L3(logger,"Support for floats {:03.2f}",1.23456);LOG_TRACE_L2(logger,"Positional arguments are {1} {0}","too","supported");LOG_TRACE_L1(logger,"{:>30}", std::string_view {"right aligned"});LOG_DEBUG(logger,"Debugging foo {}",1234);LOG_INFO(logger,"Welcome to Quill!");LOG_WARNING(logger,"A warning message.");LOG_ERROR(logger,"An error message. error code {}",123);LOG_CRITICAL(logger,"A critical error.");}

Output

example_output.png

External CMake

Building and Installing Quill

To get started with Quill, clone the repository and install it using CMake:

git clone http://github.com/odygrd/quill.gitmkdir cmake_buildcd cmake_buildcmake ..make install
  • Custom Installation: Specify a custom directory with-DCMAKE_INSTALL_PREFIX=/path/to/install/dir.
  • Build Examples: Include examples with-DQUILL_BUILD_EXAMPLES=ON.

Next, add Quill to your project usingfind_package():

find_package(quill REQUIRED)target_link_libraries(your_targetPUBLIC quill::quill)

Sample Directory Structure

Organize your project directory like this:

my_project/├── CMakeLists.txt├── main.cpp

Sample CMakeLists.txt

Here’s a sampleCMakeLists.txt to get you started:

# If Quill is in a non-standard directory, specify its path.set(CMAKE_PREFIX_PATH /path/to/quill)# Find and link the Quill library.find_package(quill REQUIRED)add_executable(example main.cpp)target_link_libraries(examplePUBLIC quill::quill)

Embedded CMake

For a more integrated approach, embed Quill directly into your project:

Sample Directory Structure

my_project/├── quill/            # Quill repo folder├── CMakeLists.txt├── main.cpp

Sample CMakeLists.txt

Use thisCMakeLists.txt to include Quill directly:

cmake_minimum_required(VERSION 3.1.0)project(my_project)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIREDON)add_subdirectory(quill)add_executable(my_project main.cpp)target_link_libraries(my_projectPUBLIC quill::quill)

Android NDK

When building Quill for Android, you might need to add this flag during configuration, but in most cases, it works without it:

-DQUILL_NO_THREAD_NAME_SUPPORT:BOOL=ON

For timestamps, usequill::ClockSourceType::System. Quill also includes anAndroidSink, which integrates with Android's logging system.

Minimal Example to Start Logging on Android

quill::Backend::start();auto sink = quill::Frontend::create_or_get_sink<quill::AndroidSink>("app", [](){    quill::AndroidSinkConfig asc;    asc.set_tag("app");    asc.set_format_message(true);return asc;}());auto logger = quill::Frontend::create_or_get_logger("root", std::move(sink),                                                    quill::PatternFormatterOptions {},                                                     quill::ClockSourceType::System);LOG_INFO(logger,"Test {}",123);

Meson

Using WrapDB

Easily integrate Quill with Meson’swrapdb:

meson wrap install quill

Manual Integration

Copy the repository contents to yoursubprojects directory and add the following to yourmeson.build:

quill=subproject('quill')quill_dep= quill.get_variable('quill_dep')my_build_target=executable('name','main.cpp',dependencies: [quill_dep],install:true)

Bazel

Using Blzmod

Quill is available onBLZMOD for easy integration.

Manual Integration

For manual setup, add Quill to yourBUILD.bazel file like this:

cc_binary(name="app",srcs= ["main.cpp"],deps= ["//quill_path:quill"])

📐 Design

Frontend (caller-thread)

When invoking aLOG_ macro:

  1. Creates a static constexpr metadata object to storeMetadata such as the format string and source location.

  2. Pushes the data SPSC lock-free queue. For each log message, the following variables are pushed

VariableDescription
timestampCurrent timestamp
Metadata*Pointer to metadata information
Logger*Pointer to the logger instance
DecodeFuncA pointer to a templated function containing all the log message argument types, used for decoding the message
Args...A serialized binary copy of each log message argument that was passed to theLOG_ macro

Backend

Consumes each message from the SPSC queue, retrieves all the necessary information and then formats the message.Subsequently, forwards the log message to all Sinks associated with the Logger.

design.jpg


🚨 Caveats

Quill may not work well withfork() since it spawns a background thread andfork() doesn't work well withmultithreading.

If your application usesfork() and you want to log in the child processes as well, you should callquill::start()after thefork() call. Additionally, you should ensure that you write to different files in the parent and childprocesses to avoid conflicts.

For example :

#include"quill/Backend.h"#include"quill/Frontend.h"#include"quill/LogMacros.h"#include"quill/Logger.h"#include"quill/sinks/FileSink.h"intmain(){// DO NOT CALL THIS BEFORE FORK// quill::Backend::start();if (fork() ==0)  {quill::Backend::start();// Get or create a handler to the file - Write to a different fileauto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>("child.log");        quill::Logger* logger =quill::Frontend::create_or_get_logger("root",std::move(file_sink));QUILL_LOG_INFO(logger,"Hello from Child {}",123);  }else  {quill::Backend::start();// Get or create a handler to the file - Write to a different fileauto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>("parent.log");        quill::Logger* logger =quill::Frontend::create_or_get_logger("root",std::move(file_sink));QUILL_LOG_INFO(logger,"Hello from Parent {}",123);  }}

📝 License

Quill is licensed under theMIT License

Quill depends on third party libraries with separate copyright notices and license terms.Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses.


[8]ページ先頭

©2009-2025 Movatter.jp