# Custom Formatting with fmt Library Integration in spdlog: A Complete Guide

> Unlock custom formatting in spdlog using the fmt library. Learn how to tailor log messages and patterns for effective debugging. A complete guide for developers.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: how-to-guide
- Published: 2026-07-25

---

**spdlog delegates all user message formatting to the fmt library via `format_string_t` type-deduced wrappers, while log record formatting (timestamps, levels, logger names) is handled by a customizable pattern formatter that supports custom flags through the `custom_flag_formatter` base class.**

spdlog is a fast, header-only C++ logging library that leverages the **fmt** library for type-safe text formatting. Mastering **custom formatting with fmt library integration in spdlog** enables you to format user-defined types directly in log calls and extend log record layouts with domain-specific context such as request IDs or correlation IDs.

## Understanding spdlog's Two-Level Formatting Architecture

spdlog separates formatting into two independent concerns. In [`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h), variadic logger calls like `spdlog::info("Hello {}", name)` capture arguments through templated overloads using `format_string_t` or `wformat_string_t` wrappers. These forward directly to **fmt**, meaning any type with a `fmt::formatter<T>` specialization works immediately.

Log record metadata (timestamps, log levels, thread IDs) is formatted separately by the **pattern formatter**. The default implementation resides in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) with inline definitions in [`include/spdlog/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter-inl.h). This parser builds a sequence of **flag formatters** (e.g., `%Y` for year, `%l` for level) and assembles the final output string.

| Concern | Implementation | fmt Integration |
|---------|----------------|-----------------|
| **User message** | `spdlog::logger::log` → `spdlog::details::log_msg` | Direct call to `fmt::format` via `format_string_t` |
| **Log record** | `spdlog::pattern_formatter` → `flag_formatter` objects | Uses `fmt` utilities internally; custom flags may call `fmt` directly |

## Message-Level Custom Formatting with fmt::formatter

To format your own types in log calls, provide a template specialization of `fmt::formatter<T>`. This requires implementing a `parse` method for format specifications and a `format` method that writes to the output iterator.

```cpp
// Point.hpp
struct Point { int x, y; };

template <>
struct fmt::formatter<Point> : fmt::formatter<std::string> {
    constexpr auto parse(format_parse_context& ctx) { 
        return ctx.begin(); 
    }

    template <typename FormatContext>
    auto format(const Point& p, FormatContext& ctx) const {
        return fmt::format_to(ctx.out(), "({},{})", p.x, p.y);
    }
};

```

```cpp
#include <spdlog/spdlog.h>
#include "Point.hpp"

int main() {
    Point pt{3, 7};
    spdlog::info("Current position: {}", pt);  // Output: Current position: (3,7)
}

```

The `info` call forwards the format string and arguments to **fmt**, which dispatches to your `fmt::formatter<Point>` specialization to render the value.

## Record-Level Custom Formatting with Pattern Flags

To emit custom context in every log line (such as a hex-encoded thread ID), extend `spdlog::custom_flag_formatter` (defined at lines 56-63 of [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)). You must override `clone()` for copy semantics and `format()` to write into the destination buffer.

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

class thread_id_flag : public spdlog::custom_flag_formatter {
public:
    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return std::make_unique<thread_id_flag>(*this);
    }

    void format(const spdlog::details::log_msg&,
                const std::tm&,
                spdlog::memory_buf_t& dest) override {
        fmt::format_to(dest, "{:x}", std::this_thread::get_id());
    }
};

```

Register your flag using `pattern_formatter::add_flag<T>()` (declared at lines 84-88 of [`pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/pattern_formatter.h)) and apply the pattern:

```cpp
int main() {
    auto console = spdlog::stdout_color_mt("console");
    
    auto formatter = std::make_unique<spdlog::pattern_formatter>();
    formatter->add_flag<thread_id_flag>('T');
    formatter->set_pattern("[%T] %v");  // %T invokes thread_id_flag
    
    console->set_formatter(std::move(formatter));
    console->info("Hello from custom flag");
}

```

Inside the flag's `format` method, you may use any **fmt** facilities such as `fmt::format_to` for complex representations.

## Combining Message and Record Formatting

You can simultaneously use custom type formatters and custom pattern flags:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/pattern_formatter.h>
#include "Point.hpp"  // Contains fmt::formatter<Point>

int main() {
    auto logger = spdlog::stdout_color_mt("app");
    logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] %l %v");

    Point p{42, 13};
    logger->info("Received point {}", p);  // Uses fmt::formatter<Point>
}

```

## Key Implementation Files

Understanding these source locations helps when debugging or extending formatting behavior:

- **[`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h)** – Public logger API defining `format_string_t` overloads that forward to **fmt**.
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** – Declaration of `pattern_formatter` and the `custom_flag_formatter` base class (see lines 56-63 for the interface and lines 84-88 for `add_flag`).
- **[`include/spdlog/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter-inl.h)** – Inline implementations of pattern parsing and default flag formatters.
- **[`src/bundled_fmtlib_format.cpp`](https://github.com/gabime/spdlog/blob/main/src/bundled_fmtlib_format.cpp)** – Provides the bundled **fmt** library when spdlog ships its own copy.
- **[`include/spdlog/fmt/fmt.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/fmt/fmt.h)** – Re-exports **fmt** symbols from the bundled or external installation.

## Summary

- **fmt delegation**: spdlog forwards all user message formatting to **fmt** via `format_string_t` wrappers in [`spdlog.h`](https://github.com/gabime/spdlog/blob/main/spdlog.h), requiring no additional spdlog code for custom types.
- **Type customization**: Implement `fmt::formatter<T>` to format user-defined structures directly in log calls.
- **Pattern extension**: Derive from `custom_flag_formatter` and register with `pattern_formatter::add_flag<T>()` to inject custom metadata into log lines.
- **Architecture independence**: Message formatting and record formatting operate on separate pipelines, allowing you to customize either or both without interference.

## Frequently Asked Questions

### How do I format a custom struct with spdlog?

Specialize `fmt::formatter<T>` for your type within the same namespace or the `fmt` namespace. Implement the `parse` and `format` methods according to the **fmt** API. Once defined, you can pass instances directly to any spdlog function such as `spdlog::info("Value: {}", myStruct)`.

### Can I add custom pattern flags to spdlog's output?

Yes. Create a class inheriting from `spdlog::custom_flag_formatter`, override the `clone()` and `format()` methods, then register the flag using `pattern_formatter::add_flag<T>('X')` where `'X'` is your pattern character. This is defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) at lines 84-88.

### Does spdlog require an external fmt installation?

No. spdlog bundles the **fmt** library in [`src/bundled_fmtlib_format.cpp`](https://github.com/gabime/spdlog/blob/main/src/bundled_fmtlib_format.cpp) and re-exports symbols through [`include/spdlog/fmt/fmt.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/fmt/fmt.h). You can use the bundled version or link against an external **fmt** installation by adjusting your build configuration.

### What is the difference between message formatting and record formatting in spdlog?

**Message formatting** handles the user-supplied format string and arguments (e.g., `"Hello {}", name`), delegated entirely to **fmt**. **Record formatting** handles the surrounding metadata such as timestamps, log levels, and logger names via the pattern formatter and its collection of flag formatters.