# How to Add Custom Log Formatters in spdlog: A Complete Guide

> Learn to add custom log formatters in spdlog by inheriting from spdlog::formatter. This guide shows how to implement format and clone methods and attach them to your logger efficiently.

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

---

**To add a custom log formatter in spdlog, inherit from `spdlog::formatter` and implement the `format()` and `clone()` methods, then attach it to a logger using `set_formatter()`.**

The spdlog library (available at gabime/spdlog) decouples log storage from presentation through the abstract `spdlog::formatter` interface defined in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h). When you need to add custom log formatters in spdlog to generate JSON, XML, or other specialized outputs, you create a derived class that transforms `details::log_msg` structures into your required format.

## Understanding the spdlog Formatter Architecture

The formatting pipeline centers on the **spdlog::formatter** abstract base class located 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 processes each `log_msg` before output. To create a custom implementation, you must override two pure virtual functions:

- **`void format(const details::log_msg &msg, memory_buf_t &dest)`** – Converts the incoming log message into a string representation and appends it to the destination buffer.
- **`std::unique_ptr<formatter> clone() const`** – Returns a deep copy of the formatter instance, which spdlog uses when duplicating loggers.

The default pattern-based implementation resides in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) and serves as a reference for building custom formatters.

## Creating a Completely Custom Formatter

For output formats not supported by the pattern engine (such as JSON or binary protocols), implement the base class directly.

### Implementing the format() Method

The `format()` method receives a **`details::log_msg`** containing pre-processed fields (timestamp, log level, thread ID, source location) and a **`memory_buf_t`** buffer. You append your formatted output directly to this buffer using fmt library utilities.

### Implementing the clone() Method

The **`clone()`** method must return a new instance of your formatter with the same configuration. Use `std::make_unique<YourFormatter>(*this)` to create a deep copy, ensuring that any member variables are properly duplicated.

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

class json_formatter : public spdlog::formatter {
public:
    // Build a JSON line: {"ts":"<ISO8601>","lvl":"<level>","msg":"<payload>"}
    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()});
    }

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

int main() {
    auto logger = spdlog::stdout_color_mt("my_logger");
    logger->set_formatter(std::make_unique<json_formatter>());

    logger->info("Application started");
    logger->warn("Low disk space");
}

```

*The formatter receives `msg.time` (a `std::chrono::system_clock::time_point`) and uses fmt’s time formatting (`%FT%TZ`) to produce an ISO‑8601 timestamp.*

## Extending the Pattern Formatter with Custom Flags

When you only need to add a new placeholder (e.g., `%P` for process ID) rather than a complete format overhaul, subclass **`spdlog::custom_flag_formatter`** from [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h). This inner class provides a lighter extension point.

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

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

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

int main() {
    auto logger = spdlog::stdout_color_mt("custom");
    // Register %P as our new placeholder
    auto pat = std::make_shared<spdlog::pattern_formatter>("%+ [%P]");
    pat->add_flag<pid_flag>('P', "process id");
    logger->set_formatter(pat);

    logger->info("Hello from custom flag");
}

```

*After registration, the pattern `[%P]` prints the process ID for each log line.*

## Attaching Your Formatter to a Logger

Once instantiated, attach your custom formatter using **`logger->set_formatter()`**, which accepts a `unique_ptr<formatter>`. You can also switch formatters at runtime to change output styles dynamically.

```cpp
auto logger = spdlog::stdout_color_mt("flex");

// Use the default pattern formatter first
logger->set_pattern("%+");

// Later switch to the JSON formatter
logger->set_formatter(std::make_unique<json_formatter>());

```

## Key Implementation Files

- **[`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h)** – Defines the abstract `spdlog::formatter` base class and the `memory_buf_t` type alias.
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** – Contains `spdlog::pattern_formatter` and the `custom_flag_formatter` extension point for adding pattern flags.

## Summary

- **Inherit from `spdlog::formatter`** for complete control over output serialization when adding custom log formatters in spdlog.
- **Implement `format()`** to write formatted strings to the provided `memory_buf_t` buffer using fmt library functions.
- **Implement `clone()`** to return `std::make_unique<YourFormatter>(*this)`, enabling spdlog to safely duplicate your formatter across logger copies.
- **Extend `spdlog::custom_flag_formatter`** when you only need to add new pattern placeholders rather than replacing the entire formatting engine.
- Reference [`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) for the authoritative interface definitions.

## Frequently Asked Questions

### What is the spdlog::formatter base class?

The `spdlog::formatter` base class is an abstract interface defined in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h) that specifies how log messages are converted to output strings. It requires derived classes to implement the `format()` method for serialization and the `clone()` method for object duplication.

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

Spdlog calls `clone()` whenever it needs to duplicate a logger, such as when creating async logger clones or copying logger configurations. Your implementation must return a deep copy via `std::make_unique<YourFormatter>(*this)` to ensure that separate logger instances do not share mutable formatter state.

### Can I extend the pattern formatter instead of writing a full formatter?

Yes. If you only need to add custom placeholders like `%P` for process ID, inherit from `spdlog::custom_flag_formatter` (defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)) and register your flag using `pattern_formatter::add_flag<>()`. This approach is simpler than implementing the full `spdlog::formatter` interface.

### How do I ensure my custom formatter is thread-safe?

Keep your formatter implementation stateless or immutable. The `format()` method may be called concurrently from multiple threads, so avoid modifying member variables during formatting. If you must maintain state, use thread-local storage or mutexes, though statelessness is preferred for performance according to the spdlog architecture.