Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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
Appearance settings

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
build2libquill

Setup

Once installed, you can start using Quill with the macro-based logging interface, which is the recommended approach foroptimal performance.

#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"});}

Alternatively, you can use the macro-free mode.Seehere for details on performancetrade-offs.

#include"quill/Backend.h"#include"quill/Frontend.h"#include"quill/LogFunctions.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"));quill::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 Queue89991113
Quill Unbounded Queue89991113
fmtlog8910101213
PlatformLab NanoLog131416182226
MS BinLog202121226095
XTR7729313354
Quill Unbounded Queue - Macro Free Mode262728282932
Reckless262831323442
BqLog55576187152167
Iyengar NanoLog8598119127350409
spdlog148151154157165173
g3log119112881367148516001889

numbers_1_thread_logging.webp

4 Threads Logging Simultaneously
Library50th75th90th95th99th99.9th
fmtlog999101213
Quill Bounded Dropping Queue8910101215
Quill Unbounded Queue8910101315
XTR789103139
PlatformLab NanoLog141619222630
MS BinLog2121222361102
Reckless182225273149
Quill Unbounded Queue - Macro Free Mode282930313441
BqLog57606495162179
Iyengar NanoLog6393127134228337
spdlog215251320357449734
g3log127613471409146216771954

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 Queue101213131516
fmtlog101213131516
Quill Unbounded Queue121314151719
MS BinLog232324256397
PlatformLab NanoLog141618203034
XTR8828293353
Quill Unbounded Queue - Macro Free Mode313233343537
BqLog57606488160173
Reckless90107113116122131
Iyengar NanoLog8899119128357424
spdlog126129132135142151
g3log8999751037112112671453

large_strings_1_thread_logging.webp

4 Threads Logging Simultaneously
Library50th75th90th95th99th99.9th
fmtlog101213141619
Quill Bounded Dropping Queue121314151618
XTR81213153040
Quill Unbounded Queue131416172023
MS BinLog2324252665105
Quill Unbounded Queue - Macro Free Mode303133343641
PlatformLab NanoLog161928364451
BqLog60646895170186
Reckless7993101105112131
Iyengar NanoLog8795128135195330
spdlog197224276306394689
g3log100010621131120313741617

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 Queue48525659123158
MS BinLog6769727379280
Quill Unbounded Queue126136145151160172
XTR287295342347355576
fmtlog649668702723753790
spdlog116591175811848119051286613543

vector_1_thread_logging.webp

4 Threads Logging Simultaneously
Library50th75th90th95th99th99.9th
Quill Bounded Dropping Queue515457596278
MS BinLog6972747682299
Quill Unbounded Queue76839095105119
fmtlog675700742759790822
XTR580121013091371198694222254
spdlog121281224712363124601391015902

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)62.1264 ms
BqLog (binary log)15.24262 ms
XTR8.25484 ms
Quill5.15776 ms
spdlog4.32925 ms
fmtlog2.771443 ms
Reckless2.721471 ms
Quill - Macro Free Mode2.651510 ms
BqLog2.531580 ms

throughput.webp

Compilation Time

Compile times are measured usingclang 17 and forRelease build.

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

quill_v10_0_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_v10_0_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+00double pi =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.gitcd quillmkdir 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(quillREQUIRED)target_link_libraries(your_targetPUBLICquill::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(quillREQUIRED)add_executable(examplemain.cpp)target_link_libraries(examplePUBLICquill::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(VERSION3.1.0)project(my_project)set(CMAKE_CXX_STANDARD17)set(CMAKE_CXX_STANDARD_REQUIREDON)add_subdirectory(quill)add_executable(my_projectmain.cpp)target_link_libraries(my_projectPUBLICquill::quill)

Android NDK

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

-DQUILL_NO_THREAD_NAME_SUPPORT:BOOL=ON

For timestamps, usequill::ClockSourceType::System. Quill also includes anAndroidSink, which integrates withAndroid'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