# How to Create Custom Formatters for spdlog: A Complete Implementation Guide

> Learn to create custom spdlog formatters by inheriting from spdlog::formatter. Implement format() and clone() then attach to your logger for tailored log output. Full guide.

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

---

**To create a custom formatter for spdlog, inherit from `spdlog::formatter` and implement the pure virtual `format()` and `clone()` methods, then attach the instance to a logger via `set_formatter()`.**

spdlog is a header-only C++ logging library renowned for its speed and extensibility. While it ships with a powerful pattern-based formatter, many applications require specialized output formats such as JSON or structured logging. This guide explains how to create custom formatters for spdlog by leveraging the library's architecture defined in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h) and [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h).

## Understanding the spdlog Formatter Architecture

The formatting pipeline centers on the abstract base class **`spdlog::formatter`** defined in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h). Every logger maintains a `std::unique_ptr<formatter>` that transforms `details::log_msg` objects into text buffers.

To implement a custom formatter, you must override two pure virtual methods:

- **`void format(const details::log_msg &msg, memory_buf_t &dest)`**: Converts the incoming log message into text and appends it to the destination buffer.
- **`std::unique_ptr<formatter> clone() const`**: Returns a deep copy of the formatter instance. spdlog invokes this when duplicating loggers to ensure thread-safe, independent formatter states.

The **`details::log_msg`** structure passed to `format()` contains pre-processed metadata including timestamps, log levels, thread IDs, source file locations, and the message payload. Because spdlog may clone formatters across threads, your implementation must remain stateless or store mutable state only within the instance itself.

## Creating a Full Custom Formatter

When you need complete control over output serialization—such as emitting JSON or binary formats—inherit directly from `spdlog::formatter`. The following example implements a JSON formatter that outputs structured log records.

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/fmt/fmt.h>

class json_formatter : public spdlog::formatter {
public:
    void format(const spdlog::details::log_msg &msg,
                spdlog::memory_buf_t &dest) override {
        fmt::format_to(
            dest,
            R"({{"ts":"{:%FT%TZ}","lvl":"{}","msg":"{}"}}\n)",
            msg.time,
            spdlog::level::to_string_view(msg.level),
            fmt::string_view{msg.payload.data(), msg.payload.size()});
    }

    std::unique_ptr<spdlog::formatter> clone() const override {
        return std::make_unique<json_formatter>(*this);
    }
};

```

This formatter accesses `msg.time` (a `std::chrono::system_clock::time_point`) and uses fmt library syntax to produce ISO-8601 timestamps. The `clone()` method performs a deep copy via `std::make_unique`, satisfying spdlog's requirements for logger duplication.

## Extending the Pattern Formatter with Custom Flags

If you only need to add custom placeholders to the existing pattern syntax, subclass **`spdlog::custom_flag_formatter`** from [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) instead of rewriting the entire formatter. This approach allows you to register new `%` specifiers while retaining the default pattern formatting logic.

Implement `format_flag()` to append your custom data, and override `clone()` to return a copy:

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

class pid_flag : public spdlog::custom_flag_formatter {
public:
    void format_flag(const spdlog::details::log_msg&, 
                     fmt::format_context::iterator out) override {
        fmt::format_to(out, "{}", static_cast<int>(::getpid()));
    }

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

```

Register the flag with a `pattern_formatter` instance before attaching it to a logger:

```cpp
auto formatter = std::make_unique<spdlog::pattern_formatter>("%+ [%P]");
formatter->add_flag<pid_flag>('P', "process id");
logger->set_formatter(std::move(formatter));

```

After registration, the `%P` specifier outputs the current process ID alongside standard log data.

## Attaching Custom Formatters to Loggers

Once instantiated, attach your custom formatter using the **`set_formatter()`** method available on all spdlog logger instances. This operation replaces any existing formatter immediately.

```cpp
auto logger = spdlog::stdout_color_mt("my_logger");
logger->set_formatter(std::make_unique<json_formatter>());

logger->info("Application started");

```

You can switch formatters at runtime to accommodate different logging modes—such as switching from human-readable text during development to JSON in production:

```cpp
// Development mode
logger->set_pattern("[%H:%M:%S] [%l] %v");

// Production mode
logger->set_formatter(std::make_unique<json_formatter>());

```

## Summary

- **Inherit from `spdlog::formatter`** defined in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h) to create fully custom output formats.
- **Implement `format()`** to serialize `details::log_msg` into the provided `memory_buf_t` buffer using the fmt library.
- **Implement `clone()`** to return `std::make_unique<YourFormatter>(*this)` for safe logger duplication.
- **Use `spdlog::custom_flag_formatter`** from [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) to add new `%` placeholders without rewriting the entire formatting engine.
- **Attach formatters** via `logger->set_formatter()` and swap them at runtime as needed.

## Frequently Asked Questions

### What is the difference between `spdlog::formatter` and `spdlog::pattern_formatter`?

`spdlog::formatter` is the abstract base class that defines the interface for all formatters. `spdlog::pattern_formatter` is a concrete implementation that parses format strings like `"%+ [%H:%M:%S]"` and converts them into log output. You inherit from `formatter` for complete control over serialization, or extend `pattern_formatter` via `custom_flag_formatter` to add specific placeholders.

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

spdlog uses the `clone()` method to duplicate formatters when copying logger instances or creating asynchronous sinks. Without a proper implementation, loggers sharing formatter state could cause data races or undefined behavior. Always return a deep copy using `std::make_unique<YourClass>(*this)`.

### Can I modify formatter state after attaching it to a logger?

While possible, modifying formatter state after attachment requires synchronization if multiple threads use the same logger. spdlog formatters are designed to be stateless or immutable after creation. Store mutable configuration data externally and pass it to the formatter constructor rather than modifying the formatter instance post-attachment.

### How do I access the original format string in a custom flag formatter?

The `custom_flag_formatter` base class does not provide the full pattern string context. If you need to parse complex interdependencies between flags, implement a complete custom formatter inheriting from `spdlog::formatter` instead of using the flag extension mechanism.