# Creating a Custom Formatter for spdlog: Architecture and Implementation Guide

> Learn to create a custom spdlog formatter by inheriting from spdlog::formatter and implementing format() and clone() methods. Enhance your C++ logging experience today.

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

---

**To create a custom formatter for spdlog, inherit from `spdlog::formatter` and implement the pure virtual `format()` method to write into a `memory_buf_t` buffer, plus a `clone()` method that returns a deep copy via `std::make_unique`.**

Creating a custom formatter for spdlog allows you to control exactly how log records are serialized, whether you need structured JSON output or specialized plain text formats. The formatting system is built around the abstract `spdlog::formatter` base class defined in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h), which every logger uses to process incoming `details::log_msg` objects. By implementing this interface in the gabime/spdlog repository, you can integrate bespoke output formats while maintaining the library's high-performance asynchronous logging capabilities.

## Understanding the Formatter Architecture

### The Base Formatter Interface

The `spdlog::formatter` base class in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h) defines the contract for all formatting logic. Every formatter must implement two pure virtual functions:

- **`void format(const details::log_msg &msg, memory_buf_t &dest)`**: Converts the incoming log message into a string representation. You append the final output to the `dest` buffer, which is a type alias for a fmt memory buffer.
- **`std::unique_ptr<formatter> clone() const`**: Returns a fresh copy of the formatter. Spdlog uses this when duplicating loggers to ensure thread safety and proper ownership semantics.

The `format()` method receives a `details::log_msg` structure that contains pre-processed fields including the timestamp, log level, thread ID, source file, and the message payload.

### The Pattern Formatter Extension Point

For users who need to extend the default pattern-based output rather than replace it entirely, [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) provides the `custom_flag_formatter` inner class. This extension point allows you to define new placeholder characters (such as `%P` for process ID) that integrate seamlessly with the existing pattern syntax. You register these flags using the `add_flag<YourFlag>('x', "description")` method on the pattern formatter instance.

## Creating a Custom Formatter from Scratch

When you need complete control over the output format—such as emitting structured JSON instead of plain text—subclass `spdlog::formatter` directly. The formatter must be stateless or immutable to ensure thread safety, as the same instance may be called from multiple threads concurrently.

### JSON Formatter Example

```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 uses `msg.time` (a `std::chrono::system_clock::time_point`) and **fmt** library time specifiers to produce ISO-8601 timestamps. The `clone()` method creates a deep copy using `std::make_unique`, which is essential for spdlog's internal logger duplication logic.

## Extending the Pattern Formatter with Custom Flags

If you only need to add specialized fields to the standard pattern output, inherit from `spdlog::custom_flag_formatter` and register your flag with the pattern formatter. This approach avoids reimplementing timestamp and level formatting while adding custom data like process IDs or request correlation IDs.

### Process ID Custom Flag

```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 calling `add_flag<pid_flag>('P', ...)`, the pattern string recognizes `%P` and invokes your `format_flag()` method for each log record according to the implementation in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h).

## Managing Formatters at Runtime

Spdlog allows dynamic formatter replacement via the `set_formatter()` method on any logger instance. You can also use `set_pattern()` as a convenience wrapper that creates a default pattern formatter internally.

```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>());

```

This flexibility lets you change output formats based on configuration changes or runtime modes without recreating loggers. The swap is atomic from the caller's perspective, though you should ensure no other thread is logging during the transition or synchronize externally if necessary.

## 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 that replace the default pattern behavior.
- **Implement `format()`** to append the string representation to a `memory_buf_t` buffer and `clone()` to return a deep copy via `std::make_unique`, ensuring proper ownership for logger duplication.
- **Extend `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 to the pattern formatter using `add_flag<>()`.
- **Maintain thread safety** by keeping formatters stateless; the `clone()` method must return independent copies to prevent data races when loggers are duplicated.
- **Register custom formatters** at runtime using `logger->set_formatter()` or embed them in pattern strings when using custom flags.

## Frequently Asked Questions

### What two methods must a custom formatter implement?

You must implement `void format(const details::log_msg &msg, memory_buf_t &dest)` to construct the output string and `std::unique_ptr<formatter> clone() const` to return a deep copy of your formatter. The `clone()` method is critical for spdlog's internal logger duplication logic, as implemented in [`include/spdlog/formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/formatter.h).

### How do I add a custom flag to the pattern formatter?

Subclass `spdlog::custom_flag_formatter`, implement `format_flag()` and `clone()`, then register your flag using `pattern_formatter::add_flag<YourClass>('X', "description")` where 'X' is your custom placeholder character. This registration mechanism is defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) and allows seamless integration with existing pattern strings.

### Are custom formatters thread-safe?

Custom formatters are thread-safe as long as they do not modify shared mutable data during the `format()` call. The formatter receives a const reference to the log message and writes to a destination buffer that is unique per invocation, so implement your formatter as a stateless or immutable object according to the thread safety model in gabime/spdlog.

### Can I change the formatter after creating the logger?

Yes. You can call `set_formatter(std::make_unique<YourFormatter>())` on an existing logger instance at any time. This replaces the formatter atomically, allowing you to switch between formats like JSON and plain text based on runtime configuration changes without destroying and recreating the logger object.