惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
T
The Blog of Author Tim Ferriss
雷峰网
雷峰网
S
Secure Thoughts
GbyAI
GbyAI
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
G
GRAHAM CLULEY
MongoDB | Blog
MongoDB | Blog
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
Martin Fowler
Martin Fowler
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
A
About on SuperTechFans
Hugging Face - Blog
Hugging Face - Blog
T
Threatpost
S
Securelist
T
Tenable Blog
博客园_首页
P
Privacy International News Feed
Cisco Talos Blog
Cisco Talos Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
C
CXSECURITY Database RSS Feed - CXSecurity.com
小众软件
小众软件
美团技术团队
Project Zero
Project Zero
The Cloudflare Blog
L
Lohrmann on Cybersecurity
The Register - Security
The Register - Security
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 热门话题
C
CERT Recently Published Vulnerability Notes
C
Cybersecurity and Infrastructure Security Agency CISA
有赞技术团队
有赞技术团队
IT之家
IT之家
A
Arctic Wolf
Scott Helme
Scott Helme
Latest news
Latest news
T
Tailwind CSS Blog
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
Recent Announcements
Recent Announcements
Cyberwarzone
Cyberwarzone
宝玉的分享
宝玉的分享
The Hacker News
The Hacker News
S
Schneier on Security
Y
Y Combinator Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Quill vs spdlog: Which C++ Logger Is Better for Low-Latency Applications?
Odysseas · 2026-06-19 · via DEV Community

Logging has a habit of ending up in the places you care about most.

It starts as a few lines for visibility. Then those lines appear in request handling, market-data processing, matching loops, telemetry pipelines, and other code where predictable latency matters.

At that point, a log statement is no longer just observability. It is work running on the same thread you are trying to keep fast.

A line like this can look harmless:

LOG_INFO(logger, "order_id={} price={}", order_id, price);

The important question is what happens before the caller continues.

Does it evaluate expensive arguments? Format text? Copy buffers? Allocate? Contend with other producer threads? Wait for queue space?

For many applications, those costs are acceptable. For latency-sensitive systems, they are part of the latency budget.

spdlog is one of the best-known C++ logging libraries and a strong general-purpose choice. It is mature, easy to use, and has a broad feature set.

Quill was designed for a narrower problem:

How little work can a C++ logger leave on the caller thread while still producing rich, human-readable logs?

That is the lens for this comparison. The interesting difference is not which library has more features. It is where each library chooses to spend work.

At a Glance

Area spdlog async Quill
User-message formatting Producer thread Backend thread
Producer handoff Shared thread-pool queue Per-thread SPSC queue
Arguments for runtime-disabled levels Evaluated if the level was not compiled out Skipped by the macro-level runtime check
Native synchronous mode Yes No
Backend workers Configurable thread pool Single backend worker
Primary focus General-purpose flexibility Low producer-side latency

These differences do not make one library universally better. They make each library better suited to different workloads.

Async Logging Is Not One Design

"Async logging" often means "file I/O happens on another thread." That is useful, but it is not enough to describe the cost paid by the caller.

An async logger can still make the application thread:

  • evaluate expensive arguments
  • format the user message
  • allocate or grow buffers
  • contend with other producers
  • block or drop when queues are full

The important point is that "async" describes where some work happens, not all of it. Two async loggers can move very different amounts of work away from the caller.

1. Formatting: Caller Thread or Backend Thread?

This is the first major difference, and it matters even when only one thread logs.

In spdlog's templated logging path, logger::log_ first checks whether the runtime level or backtrace path needs the message. If not, it returns. If the message is needed, spdlog formats the user message on the caller thread using fmt::vformat_to, constructs a log_msg, and then dispatches it.

Stripped down to the relevant operations, that path looks like this:

if (!should_log(level) && !backtrace_enabled) {
  return;
}

fmt::vformat_to(buffer, format_string, args);
sink_it_(log_msg{buffer});

In asynchronous mode, async_logger::sink_it_ posts that already-formatted message to the thread pool. The thread pool wraps it in an async_msg, and log_msg_buffer owns the payload because the original formatted buffer was local to the caller.

So in spdlog async mode:

  • runtime-disabled messages skip formatting inside spdlog
  • enabled messages format the user message on the producer thread
  • the formatted payload is copied into the async queue message
  • the async backend handles sink dispatch, sink pattern formatting, flushing, and I/O

This keeps spdlog's backend model straightforward. Sinks receive a ready user message, and the async backend can focus on sink dispatch, flushing, and I/O.

Quill moves the expensive part later.

A normal Quill LOG_* call creates static call-site metadata, checks the logger level, serializes the arguments into the caller thread's queue, and returns. The backend worker later decodes those arguments and performs {fmt} formatting.

The corresponding Quill path is closer to:

if (!should_log_statement<level>()) {
  return;
}

encode(metadata, args, thread_local_queue);
// backend worker later decodes and formats

So in Quill's hot path:

  • call-site metadata is static
  • supported arguments are encoded into the frontend queue
  • user message formatting is deferred to the backend worker
  • sink pattern formatting and I/O also happen on the backend worker

That is why the single-thread benchmark rows later in this article matter. Quill's design is not only about avoiding contention between many producers. Deferring formatting changes the cost profile even for a single producer.

2. Macros: Source Location and Argument Evaluation

Macros are sometimes treated as an API style preference. In logging, they can change runtime behavior.

spdlog's README starts with ordinary function calls:

spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);

That API is clean and familiar. The trade-off is that the convenience functions call the default logger with an empty source_loc. If you want file, line, and function information in spdlog, you usually use macros such as SPDLOG_INFO or pass source_loc explicitly.

spdlog macros provide two main things:

  • source location capture through __FILE__, __LINE__, and function name
  • compile-time filtering through SPDLOG_ACTIVE_LEVEL

The source-location capture can also be disabled with SPDLOG_NO_SOURCE_LOC.

With both spdlog and Quill, a level that is disabled at compile time is removed completely. The corresponding macro expands to (void)0, so its arguments are not evaluated.

The important difference appears when the statement is compiled in but disabled at runtime.

With spdlog:

SPDLOG_DEBUG("payload={}", build_expensive_payload());

build_expensive_payload() is evaluated before control enters spdlog. spdlog can then check the logger's runtime level and skip formatting, but it cannot undo argument evaluation that has already happened.

Quill's recommended API is macro-based:

LOG_INFO(logger, "order_id={} price={}", order_id, price);

That is not just an API style choice. Quill's macro checks the logger's runtime level before calling log_statement(..., args...).

With Quill:

LOG_DEBUG(logger, "payload={}", build_expensive_payload());

when debug logging is disabled at runtime, build_expensive_payload() is not called.

Quill also uses the macro to create static metadata containing the source location, function name, format string, tags, and level. Its default formatter includes %(short_source_location), so file and line are visible in the normal output path.

If a level is disabled at compile time through QUILL_COMPILE_ACTIVE_LOG_LEVEL, the corresponding Quill macro also expands to (void)0.

Quill still provides macro-free logging:

quill::info(logger, "order_id={} price={}", order_id, price);

That mode is useful when ordinary function syntax is preferred or the call site is not performance critical. The trade-off is that macro-free calls always evaluate their arguments, use runtime metadata handling, and cannot be fully compiled out like the macros.

The short version:

  • Compile-time disabled: neither library evaluates the arguments
  • Runtime disabled with spdlog macros: the arguments are still evaluated
  • Runtime disabled with Quill macros: the arguments are not evaluated
  • spdlog function calls are ergonomic and familiar
  • spdlog macros add source location and compile-time removal
  • Quill macros add source metadata, compile-time removal, and runtime conditional argument evaluation
  • Quill functions remain available for less performance-critical paths

3. Producer Queues: Shared MPMC vs Per-Thread SPSC

This is the scaling part of the comparison.

In spdlog async mode, a logger is backed by a thread pool. The default async factory uses a global thread pool; if no global pool exists, it creates one with an 8192-slot queue and one worker thread.

spdlog::init_thread_pool(...) customizes that global pool: queue size, worker count, and optional worker-thread callbacks. It does not give each producer thread its own queue. Async loggers created through the global factory still post into the global thread pool queue.

You can manually construct different async loggers with different thread pools, and then arrange for different application threads to use different loggers. That can reduce sharing if your application is structured that way, but it is an application-level convention. spdlog does not automatically give each producer thread its own queue.

In spdlog 1.17.0, that queue is details::mpmc_blocking_queue<async_msg>. It is a multi-producer, multi-consumer blocking queue implemented with a mutex and condition variables.

That gives spdlog a compact async handoff model:

  1. Check runtime level.
  2. Format the user message.
  3. Copy the formatted payload into an async message.
  4. Push it into the thread-pool queue.
  5. Let backend worker threads write to sinks.

The trade-off is where producers meet. When many threads log concurrently through the same thread pool, they meet at the shared queue.

Quill uses a different handoff.

Each frontend thread owns a thread-local SPSC queue. The application thread is the single producer for its queue, and the backend worker is the consumer. Thread A writes to thread A's queue. Thread B writes to thread B's queue.

Producer threads do not contend with each other on one shared logging queue.

The backend worker drains all active queues, decodes events, orders them by timestamp within Quill's timestamp-ordering model, formats messages, forwards metrics, and writes to sinks.

If your application has one producer thread, the queue contention advantage is less important. If your application has many hot threads, producer isolation becomes a much bigger part of the design.

4. Ordering, Backpressure, and Backend Threads

Backend design also affects ordering and backpressure.

spdlog's async thread pool can be configured with more than one worker. That can be useful when backend throughput or sink work is the bottleneck. The handoff still goes through the thread pool's shared queue.

Quill deliberately uses a single backend worker. That worker drains the frontend queues, applies Quill's timestamp-ordering model, formats log messages, forwards metrics, and writes to sinks.

That design is less about maximizing the number of backend worker threads and more about keeping producer threads isolated and making the backend pipeline predictable.

Both libraries let you choose what happens when async logging cannot keep up.

spdlog exposes async overflow policies such as block, overrun_oldest, and discard_new.

Quill exposes the frontend queue model itself: unbounded or bounded, blocking or dropping. The default unbounded blocking queue starts small, can grow, and eventually blocks if it reaches the configured maximum capacity.

5. Native Sync Logging vs Async-First Logging

This is one area where the libraries make different choices.

spdlog supports synchronous logging natively. The default factory creates synchronous loggers. A synchronous spdlog logger formats and writes through its sinks on the calling thread. There is no backend logging thread, no async queue, and no need to coordinate with a worker before the log line reaches its destination.

That is often exactly right for:

  • command-line tools
  • build utilities
  • desktop applications
  • tests
  • small services
  • debugging workflows

Quill is async-first. Frontend threads capture log events, and the backend thread performs formatting and I/O. Quill does not provide a native synchronous mode.

For advanced integrations, Quill also has ManualBackendWorker, which lets an application drive the backend from a user-managed thread or event loop. That can avoid Quill spawning its own backend thread, but it is still the same deferred backend pipeline rather than native synchronous logging.

Separately, you can simulate synchronous behavior with logger->set_immediate_flush(1) or explicit flush_log() calls. That makes the caller wait until the backend has processed and flushed the relevant log messages. It is useful for debugging, crash-sensitive areas, and controlled points in the program, but it is still an async architecture being synchronized.

This is deliberate. Quill optimizes the normal logging path for hot threads. If you specifically want every log call to format and write on the caller thread, spdlog fits that model more directly.

6. Metrics Through the Same Backend Pipeline

Quill also supports a feature most logging libraries do not attempt: metric publishing through the same async backend pipeline used for logs.

You register MetricMetadata once, bind a logger to a metric-capable sink, keep the returned metadata pointer, and publish double samples from hot threads:

quill::MetricMetadata const* requests_total = quill::Frontend::create_metric(
  "requests_total_post_200", "requests_total", {{"method", "POST"}, {"status", "200"}});

metrics_logger->publish_metric(requests_total, 1.0);

On the hot path, Quill queues a compact sample. Metric names and labels are not serialized for every sample. Inside Quill there is no second metrics worker; the backend worker forwards metric samples to metric-capable sinks through Sink::write_metric().

The built-in PrometheusSink integrates with prometheus-cpp and supports counters, gauges, histograms, and summaries. Custom sinks can route the same samples to StatsD, OpenTelemetry, Graphite, or an in-process collector.

That makes Quill more than "just log lines" for systems where logs and metrics are both produced from hot application threads.

7. User-Defined Types Are a Deliberate Trade-off

Custom types are one place where spdlog can be simpler.

Because spdlog formats the user message on the caller thread, a type that is formattable by {fmt} can usually be logged directly. That is convenient and fits spdlog's general-purpose API.

Quill defers formatting to the backend thread, so it must know how to safely carry enough data across threads before formatting happens. Built-in and standard types are handled by Quill, but user-defined types sometimes need an explicit choice:

  • use DeferredFormatCodec<T> when copying the object to the backend is acceptable
  • use DirectFormatCodec<T> when the object should be formatted immediately on the caller thread
  • write a custom quill::Codec<T> when only selected fields should be encoded

That is more explicit than a plain {fmt} formatter, but the explicitness is also the point. Quill lets you decide whether a custom type belongs on the deferred-formatting path or whether formatting it on the hot path is the right compromise.

8. Benchmark Results: Hot-Path Latency

After the design differences, the main number for Quill's target audience is producer-thread latency:

How much time did the logging call add to the application thread?

Quill's README publishes latency benchmarks measured in nanoseconds on a Linux system. Always benchmark your own workload, compiler, CPU, sink configuration, queue mode, and formatting patterns. The results are still useful because they reflect the architectural differences above.

Benchmark Quill 50th Quill 99.9th spdlog 50th spdlog 99.9th
Numbers, 1 thread 8 ns 12 ns 242 ns 294 ns
Numbers, 4 threads 11 ns 21 ns 528 ns 973 ns
Large strings, 1 thread 11 ns 20 ns 216 ns 259 ns
Large strings, 4 threads 8 ns 27 ns 515 ns 939 ns
std::vector<std::string>, 1 thread 137 ns 189 ns 6749 ns 7863 ns
std::vector<std::string>, 4 threads 101 ns 158 ns 6822 ns 8903 ns

9. Benchmark Results: Backend Throughput

The Quill README also reports backend throughput :

Library Throughput Elapsed
Quill 4.62 million msg/s 866 ms
spdlog 3.46 million msg/s 1156 ms

Throughput answers a backend-side question: how many messages per second can the logging system format and write under this benchmark setup?

That matters when the application produces logs faster than the backend can drain them for a sustained period. If that happens, frontend queues can grow, allocate, block, or drop messages depending on the configured queue mode.

But throughput is not the same as hot-path latency. If the backend is keeping up, the producer thread mostly pays the cost of capturing the event into its queue. For low-latency systems, that producer-side cost is usually the first number to inspect.

What Quill Looks Like

The simple setup is small:

#include "quill/LogMacros.h"
#include "quill/SimpleSetup.h"

int main()
{
  auto* logger = quill::simple_logger();

  LOG_INFO(logger, "Hello from {}!", "Quill");
}

For applications that want explicit backend and sink configuration:

#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/ConsoleSink.h"

int main()
{
  quill::Backend::start();

  quill::Logger* logger = quill::Frontend::create_or_get_logger(
    "root",
    quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink"));

  LOG_INFO(logger, "order_id={} price={}", 42, 123.45);
}

The code still reads like normal structured logging. The important difference is what happens after the macro is called: the caller captures the event, and the backend does the heavier work.

Where spdlog Fits Best

Pick spdlog when you want a widely adopted general-purpose C++ logger with a familiar API, broad package-manager support, native synchronous logging, and many ready-made sinks.

For command-line tools, build utilities, desktop applications, tests, and services with moderate logging volume, spdlog is often the pragmatic choice. Its _st and _mt sink variants are also convenient when you want explicit single-threaded or thread-safe sink types.

Where Quill Fits Best

Pick Quill when logging is part of the performance model, not an afterthought.

If you are looking for a spdlog alternative for a latency-sensitive C++ system, Quill is designed specifically around reducing the work performed on producer threads.

That usually means you care about:

  • minimizing producer-thread latency
  • avoiding expensive argument evaluation when a level is disabled at runtime
  • keeping {fmt} formatting off hot threads
  • logging from multiple hot threads without shared producer-queue contention
  • publishing metrics through the same backend pipeline

The core idea is simple:

The application thread captures enough information to log later. The backend does the expensive work.

Takeaway

spdlog is a strong general-purpose C++ logger.

Quill is a specialized low-latency C++ logger for applications where logging must be designed around the hot path.

The difference is where formatting happens, when arguments are evaluated, how producers hand work to the backend, how queues apply backpressure, and what happens when many threads log at once.

If this is the kind of C++ logging problem you are solving, try Quill, run the benchmarks on your own machine, and inspect the implementation.

If the design looks useful for your systems, please consider starring the project so more C++ developers looking for low-latency logging can find it.

Source Notes

The comparison above was reviewed against Quill 12.0.0 and spdlog 1.17.0.