# How to Create User-Defined Formatters with fmt::formatter in spdlog

> Learn to create user-defined formatters in spdlog by specializing fmt::formatter or inheriting spdlog::formatter for custom log message formatting and log line structure.

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

---

**You can add custom formatting to spdlog by specializing `fmt::formatter<T>` for your types to control how they appear in log messages, or by inheriting from `spdlog::formatter` to customize the entire log line structure including timestamps and severity levels.**

spdlog is a header-only C++ logging library built on top of the {fmt} formatting engine. While spdlog provides powerful pattern formatters out of the box, creating **user-defined formatters with fmt::formatter in spdlog** allows you to seamlessly log custom objects and control output layout with compile-time type safety.

## Specializing fmt::formatter for Custom Types

The most efficient way to format your own structs or classes in spdlog is to extend the **fmt** library itself. Because spdlog delegates all argument formatting to `fmt::format`, you simply specialize `fmt::formatter` for your type.

### Implementation Steps

First, define your custom type:

```cpp
struct Person {
    std::string name;
    int age;
};

```

Next, specialize `fmt::formatter` for that type. The specialization requires two methods: `parse()` to handle format specifications, and `format()` to produce the output:

```cpp
template <> struct fmt::formatter<Person> {
    // Parse any format spec (e.g. "{}" or "{:upper}")
    constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

    // format() receives the Person and an output iterator.
    template <typename FormatContext>
    auto format(const Person& p, FormatContext& ctx) const {
        // Use fmt to produce the string; you can honour format specs here.
        return fmt::format_to(ctx.out(), "{} ({} yrs)", p.name, p.age);
    }
};

```

Now use `Person` directly in spdlog calls:

```cpp
spdlog::info("User: {}", Person{"Alice", 30});

```

When spdlog formats the message, it forwards arguments to **fmt**. Since `fmt::formatter<Person>` is defined, `fmt::format_to` inside `spdlog::pattern_formatter` (see [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) at line 65) automatically invokes your specialization, yielding `Alice (30 yrs)`.

## Implementing a Custom spdlog::Formatter

When you need to control *all* parts of the log line (timestamp format, level placement, thread ID, etc.), you must implement a class derived from `spdlog::formatter`. This interface is defined in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h) at line 23.

### Required Virtual Interface

Your custom formatter must implement two pure virtual members:

- **`std::unique_ptr<formatter> clone() const`** – Creates a copy of the formatter when sinks are duplicated.
- **`void format(const spdlog::details::log_msg&, spdlog::memory_buf_t&)`** – Writes the formatted output into the supplied buffer.

The buffer type `spdlog::memory_buf_t` is an alias for `fmt::basic_memory_buffer<char>`, allowing direct use of `fmt::format_to`.

### Complete Implementation Example

```cpp
#include <spdlog/formatter.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/pattern_formatter.h>   // optional, for reuse of existing parts
#include <fmt/chrono.h>                // fmt helpers for time

class my_formatter : public spdlog::formatter {
public:
    // Required clone for sink duplication
    std::unique_ptr<formatter> clone() const override {
        return std::make_unique<my_formatter>(*this);
    }

    // Core formatting routine
    void format(const spdlog::details::log_msg& msg,
                spdlog::memory_buf_t& dest) override {
        // Example: "[%Y-%m-%d %H:%M:%S.%e] [level] message"
        using clock = std::chrono::system_clock;
        std::time_t t = clock::to_time_t(msg.time);
        std::tm tm = *std::localtime(&t);

        // Use fmt to build the prefix
        fmt::format_to(std::back_inserter(dest),
                       "[{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}] ",
                       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
                       tm.tm_hour, tm.tm_min, tm.tm_sec,
                       std::chrono::duration_cast<std::chrono::milliseconds>(msg.time.time_since_epoch()).count() % 1000);

        // Level name
        fmt::format_to(std::back_inserter(dest), "[{}] ", spdlog::level::to_string_view(msg.level));

        // The original message payload (already formatted by fmt)
        fmt::format_to(std::back_inserter(dest), "{}", fmt::string_view(msg.payload.data(), msg.payload.size()));
    }
};

```

### Installing Your Formatter

Install the formatter globally to affect all sinks:

```cpp
spdlog::set_formatter(std::make_unique<my_formatter>());

```

This function is implemented in [`include/spdlog/spdlog-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog-inl.h) at line 23.

Alternatively, install per-sink by calling `set_formatter` on the sink object. Each sink inherits from `spdlog::sinks::base_sink`, which stores its own `std::unique_ptr<formatter>` (see [`include/spdlog/sinks/base_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink.h) at line 22):

```cpp
sink->set_formatter(std::make_unique<my_formatter>());

```

## Key Source Files

The spdlog formatting hierarchy relies on these specific implementation files:

- **[`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h)** (line 23): Defines the abstract `spdlog::formatter` base class.
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** (line 65): Contains the default `spdlog::pattern_formatter` implementation.
- **[`include/spdlog/sinks/base_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink.h)** (line 22): The `base_sink` class template that holds per-sink formatters.
- **[`include/spdlog/spdlog-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog-inl.h)** (line 23): Implements `spdlog::set_formatter()` for global configuration.
- **[`include/spdlog/details/os.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/os.h)**: Indirectly defines `spdlog::memory_buf_t` via the fmt library type alias.

## Summary

- **Specialize `fmt::formatter<T>`** when you want spdlog to automatically format your custom types in log statements; this leverages the fmt library directly and requires no additional spdlog code.
- **Inherit from `spdlog::formatter`** when you need complete control over the log line layout, including timestamps, log levels, and message structure.
- The `format()` method receives a `spdlog::details::log_msg` containing metadata (time, level, thread info) and a `spdlog::memory_buf_t` buffer for output.
- Install custom formatters globally via `spdlog::set_formatter()` or per-sink via `sink->set_formatter()`.
- All formatting ultimately delegates to the **fmt** library, so you can use any fmt API (`fmt::format_to`, `fmt::format`, etc.) within your implementations.

## Frequently Asked Questions

### What is the difference between fmt::formatter and spdlog::formatter?

A specialization of `fmt::formatter<T>` tells the **fmt** library how to convert a specific type `T` into text during the formatting of a log message argument. `spdlog::formatter` is an abstract base class that controls the entire log line output, including metadata like timestamps and severity levels. Use `fmt::formatter` for custom types; use `spdlog::formatter` for custom line layouts.

### How do I handle custom format specifiers in my fmt::formatter specialization?

The `parse()` method in your `fmt::formatter` specialization receives a `format_parse_context` containing any text between the `:` and the `}` in a format string (e.g., `{:upper}`). Parse this text to store configuration flags, then return an iterator pointing past the closing `}`. The `format()` method can then access these settings to alter output behavior.

### Can I use a custom spdlog::formatter and custom fmt::formatters together?

Yes. A custom `spdlog::formatter` implementation typically calls `fmt::format_to` internally to render the message payload. If the payload contains user-defined types with `fmt::formatter` specializations, fmt will automatically invoke them. This allows you to customize the line structure while still benefiting from type-specific formatters for individual arguments.

### Why does my custom spdlog::formatter need a clone() method?

The `clone()` method creates a copy of the formatter instance. spdlog calls this when it needs to duplicate sinks (for example, when creating thread-local copies or copying configuration). Because formatters are stored as `std::unique_ptr<formatter>`, the clone pattern enables proper deep copying of polymorphic formatter objects.