# How spdlog Integrates Message Formatting with the fmt Library

> Discover how spdlog seamlessly integrates message formatting with the fmt library by delegating runtime formatting to fmt::vformat_to, ensuring efficient and powerful logging.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: internals
- Published: 2026-07-20

---

**spdlog delegates all runtime message formatting to the fmt library through a thin wrapper in the `log_` method that forwards format strings and arguments to `fmt::vformat_to` while writing directly into internal buffers.**

spdlog is a fast, header-only C++ logging library that relies on the **fmt** library (formerly libfmt) for all runtime message formatting. According to the gabime/spdlog source code, the integration centers on the `spdlog::logger` class in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h), which acts as a lightweight bridge between user-provided format strings and fmt's type-safe formatting engine. This architecture allows spdlog to combine high-performance buffer management with fmt's expressive syntax.

## Core Integration Architecture

### The log_ Method as fmt Bridge

The heart of spdlog's fmt integration resides in the private `log_` method defined in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) (lines 33-38). This method receives variadic template arguments from public logging interfaces and forwards them to fmt's formatting engine.

When processing a log statement, `log_` performs three critical operations:

1. **Argument packing** – Uses `fmt::make_format_args` (or `fmt_lib::make_format_args` when using the std-format branch) to pack variadic template arguments into a type-erased object consumable by fmt.
2. **Buffer allocation** – Allocates a `memory_buf_t`, which is a thin wrapper around a dynamic buffer.
3. **Format invocation** – Calls `fmt::vformat_to` (or `fmt_lib::vformat_to`) with an appender that writes directly into spdlog's internal buffer.

This design avoids intermediate string copies by passing `fmt::appender(buf)` directly to the formatting function.

### Conditional std::format Support

spdlog supports both the classic fmt API and the C++20 `<format>` standard through conditional compilation. If the macro `SPDLOG_USE_STD_FORMAT` is defined, spdlog uses the C++20-compatible API (`fmt_lib::vformat_to`); otherwise, it falls back to the classic fmt API (`fmt::vformat_to`). This choice is evaluated at compile time within the `log_` implementation, allowing the same codebase to target different C++ standards without modification.

## Buffer Management and Performance Optimization

### Direct Buffer Writing

Spdlog achieves zero-copy formatting by integrating tightly with fmt's buffer API. Instead of creating temporary `std::string` objects, spdlog:

- Allocates a `memory_buf_t` (defined in the details namespace)
- Passes `fmt::appender(buf)` to `vformat_to`
- Allows fmt to write formatted output directly into spdlog's buffer

This approach eliminates heap allocations for intermediate strings and reduces memory fragmentation during high-throughput logging.

### Unicode and Wide Character Support

When compiled with `SPDLOG_WCHAR_TO_UTF8_SUPPORT`, spdlog extends its fmt integration to handle wide-character strings. The logger allocates a `wmemory_buf_t` for wide-character formatting, invokes the same fmt functions on the wide buffer, and then converts the result to UTF-8 before passing the message to sinks. This conversion happens transparently within the formatting pipeline defined in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) (lines 45-61).

## Bundled fmt Distribution

Spdlog ships with a bundled copy of fmt under `include/spdlog/fmt/` to ensure out-of-the-box functionality without external dependencies. The core header [`include/spdlog/fmt/fmt.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/fmt/fmt.h) re-exports the public fmt interface, allowing users to include either `<spdlog/fmt/fmt.h>` or rely on spdlog's internal includes. This bundling ensures version compatibility while still permitting users to link against an external fmt library if desired.

## Separation of Concerns: Pattern vs Message Formatting

Spdlog maintains a strict separation between log metadata formatting and message content formatting. The `pattern_formatter` class (defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)) handles only log-event metadata such as timestamps, log levels, and thread IDs. The actual message text—the content provided by the user between the format string and arguments—is always produced by fmt, not by the pattern formatter.

This separation, visible in how `log_msg` (defined in [`include/spdlog/details/log_msg.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/log_msg.h)) carries formatted messages to sinks, allows spdlog to combine high-performance metadata formatting with the full expressive power of fmt's syntax for user messages.

## Practical Examples

The following examples demonstrate spdlog's fmt integration in practice:

```cpp
#include <spdlog/spdlog.h>

int main() {
    // Simple fmt-style formatting – spdlog forwards to fmt internally
    spdlog::info("Hello {}! The answer is {:03}", "world", 42);
    // => "Hello world! The answer is 042"

    // Using named arguments (fmt feature) – also works through spdlog
    spdlog::debug("User {user} logged in from {ip}",
                  "user"_a = "alice", "ip"_a = "10.0.0.5");
    // => "User alice logged in from 10.0.0.5"

    // Wide-character support (requires SPDLOG_WCHAR_TO_UTF8_SUPPORT)
    spdlog::error(L"Unicode error: {}", L"⚠️");
}

```

## Summary

- **fmt delegation**: spdlog uses `fmt::vformat_to` (or `fmt_lib::vformat_to`) via the `log_` method in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) to handle all message formatting.
- **Zero-copy optimization**: The library writes directly into `memory_buf_t` buffers using `fmt::appender`, avoiding intermediate string copies.
- **Standard flexibility**: Conditional compilation via `SPDLOG_USE_STD_FORMAT` enables C++20 `<format>` compatibility while maintaining backward support.
- **Bundled dependency**: fmt is included under `include/spdlog/fmt/` for immediate use without external installation.
- **Clear separation**: Pattern formatters handle metadata; fmt handles message content exclusively.

## Frequently Asked Questions

### Does spdlog require an external fmt installation?

No. spdlog bundles fmt under `include/spdlog/fmt/`, specifically via [`include/spdlog/fmt/fmt.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/fmt/fmt.h), which re-exports the fmt interface. You can use spdlog immediately without installing fmt separately, though you may also link against an external fmt library if your project requires a specific version.

### How does spdlog support C++20 std::format?

When you define the `SPDLOG_USE_STD_FORMAT` macro during compilation, spdlog switches its internal calls from `fmt::vformat_to` to `fmt_lib::vformat_to`, using the C++20 `<format>`-compatible API. This allows spdlog to leverage standard library formatting on compilers that support C++20 while maintaining the same public API.

### Can I use fmt's named arguments with spdlog?

Yes. Because spdlog forwards all formatting operations directly to fmt, advanced fmt features like named arguments (`"user"_a = "alice"`) work transparently through spdlog's logging methods. The argument packing via `fmt::make_format_args` preserves these features without additional wrapper code.

### Does spdlog support wide character (wchar_t) formatting?

Yes, when compiled with `SPDLOG_WCHAR_TO_UTF8_SUPPORT`. In this mode, spdlog allocates a `wmemory_buf_t`, invokes fmt's formatting functions on wide-character strings, and converts the result to UTF-8 before logging. This happens within the same `log_` method that handles standard narrow-character formatting.