spdlog Header-Only vs Compiled Library: Tradeoffs and Compile Time Analysis

Use the header-only variant for rapid prototyping to eliminate linking steps, but choose the compiled library for large projects to minimize incremental build times and binary bloat.

The gabime/spdlog repository provides two distinct consumption models that share an identical API but differ significantly in build integration and compile-time performance. Understanding when to use each variant prevents unnecessary rebuild overhead in large codebases while maintaining flexibility for smaller utilities.

Build System Architecture

The root CMakeLists.txt defines two mutually exclusive targets that determine how the library is consumed:

  • spdlog_header_only: An interface target that adds include/ directories and sets SPDLOG_HEADER_ONLY. No compiled artifacts are produced.
  • spdlog: A static or shared library target compiled from sources in src/, producing libspdlog.a or equivalent.

Both targets handle the bundled fmt library dependency automatically, though SPDLOG_FMT_EXTERNAL_HO can force header-only fmt usage when needed.

Compile Time Tradeoffs

Header-Only Characteristics

When consuming spdlog as header-only, the compiler processes the full implementation in every translation unit that includes spdlog/spdlog.h. In include/spdlog/spdlog.h, the code conditionally includes implementation files when SPDLOG_HEADER_ONLY is defined, effectively pasting the contents of src/spdlog.cpp and related files directly into your object files.

This approach eliminates the linking step entirely, making initial project setup instantaneous. However, for projects with hundreds of translation units, this causes quadratic compilation overhead as the templated logging code is reinstantiated repeatedly.

Compiled Library Benefits

The compiled variant builds the core implementation once into libspdlog.a (or .so/.dll), emitting object code only a single time. Translation units including spdlog headers see only declarations, reducing their individual compile time significantly. Incremental builds after modifying non-logging code become substantially faster, as the logging library does not require recompilation.

Binary size also benefits when linking the compiled library statically across multiple executables, as the linker can deduplicate the single library instance rather than merging redundant template instantiations from each translation unit.

Runtime Performance and Binary Size

Despite the build-time differences, both variants generate identical machine code for logging operations. The header-only version is literally the same source that compiles into the library; no performance penalties or optimizations are sacrificed when switching between modes. The primary distinction lies in when and where compilation occurs, not what the processor executes.

How to Choose Between Variants

Select header-only when:

  • Prototyping single-file utilities or scripts where build simplicity outweighs compile speed
  • Distributing header-only frameworks that cannot depend on external libraries
  • Working in environments where running a linker is problematic (certain embedded toolchains)

Select compiled library when:

  • Managing large codebases with many translation units
  • Sharing logging code across multiple binaries via shared libraries
  • Controlling symbol visibility and ABI boundaries explicitly
  • Reducing CI build times where incremental compilation matters

Implementation Details

The dual-mode mechanism relies on preprocessor conditionals in the public headers. The main entry point include/spdlog/spdlog.h contains logic that either includes declarations alone or pulls in implementation headers from include/spdlog/*/inlines.h when SPDLOG_HEADER_ONLY is defined.

The compiled variant sources live in src/:

When building the spdlog target, CMake compiles these .cpp files separately. When using spdlog_header_only, these same files are effectively #included into every translation unit through the header structure.

Practical CMake Integration

Header-Only Consumption

cmake_minimum_required(VERSION 3.12)
project(QuickDemo)

add_subdirectory(third_party/spdlog)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE spdlog_header_only)
// main.cpp
#include "spdlog/spdlog.h"

int main() {
    spdlog::info("Zero-link compilation");
    // Compile: g++ -std=c++11 -I third_party/spdlog/include main.cpp
}

Compiled Library Consumption

cmake_minimum_required(VERSION 3.12)
project(ProductionApp)

add_subdirectory(third_party/spdlog)
add_executable(server server.cpp)
target_link_libraries(server PRIVATE spdlog)
// server.cpp
#include "spdlog/spdlog.h"
#include "spdlog/sinks/rotating_file_sink.h"

int main() {
    auto logger = spdlog::rotating_logger_mt("file", "logs/app.log", 1048576*5, 3);
    logger->info("Using pre-compiled library");
}

Summary

  • Header-only (spdlog_header_only target) eliminates linking but recompiles implementation in every translation unit, ideal for small projects or prototypes.
  • Compiled library (spdlog target) builds once and links, dramatically improving incremental build times for large codebases.
  • Both variants share the same public API and runtime performance; switching requires only changing the CMake target name.
  • The mechanism relies on SPDLOG_HEADER_ONLY controlling whether src/ files are included directly or compiled separately.

Frequently Asked Questions

Does the header-only version produce slower runtime code than the compiled library?

No. The header-only variant generates identical machine code because it uses the same source files found in src/. The compiler emits the same instructions whether processing the code during your build or during the library's compilation.

How do I switch from header-only to compiled library without changing my C++ code?

Change your CMake configuration from target_link_libraries(your_target PRIVATE spdlog_header_only) to target_link_libraries(your_target PRIVATE spdlog). The public API remains identical, requiring no modifications to your #include directives or logging calls.

Why are my compile times slow when using spdlog header-only in a large project?

Every .cpp file including spdlog/spdlog.h instantiates the full template-heavy implementation. In include/spdlog/spdlog.h, this triggers compilation of formatting logic, registry management, and sink implementations per translation unit. Switch to the compiled spdlog target to compile these once and link them.

Can I mix header-only and compiled spdlog usage in the same project?

No. Mixing modes causes One Definition Rule (ODR) violations because the header-only variant defines symbols that the compiled library also provides. Choose one consumption model per linked artifact, or use the compiled library as a shared object with careful symbol visibility control.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →