# How to Implement Custom Formatter Flags in spdlog's Pattern Matching System

> Learn to implement custom formatter flags in spdlog's pattern matching system. Derive from spdlog::custom_flag_formatter, implement methods, and register your flags for personalized logging patterns.

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

---

**Derive from `spdlog::custom_flag_formatter`, implement `format()` and `clone()`, then register with `pattern_formatter::add_flag<T>()` to define custom pattern flags like `%x`.**

spdlog's **pattern matching system** transforms log format strings into sequences of flag formatters executed per record. While built-in flags like `%v` (message) and `%l` (level) cover common needs, the library exposes a clean extension point for domain-specific formatting. This guide walks through implementing **custom formatter flags** using the three core components found in the gabime/spdlog source code.

## Understanding the Custom Flag Architecture

The extensibility mechanism spans three interconnected locations in the codebase:

- **`custom_flag_formatter`** – Abstract base class defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) (lines 56-63) that adds a `clone()` requirement to the internal `details::flag_formatter`
- **`pattern_formatter::add_flag<T>()`** – Templated registration method in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) (lines 84-88) that stores custom handlers in the `custom_handlers_` map
- **`pattern_formatter::handle_flag_()`** – Compile-time lookup in [`src/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/src/pattern_formatter-inl.h) (lines 21-31) that instantiates custom formatters when pattern strings are parsed

When a pattern containing your custom flag is compiled, spdlog clones the registered formatter via `clone()` and inserts it into the runtime `formatters_` vector. This design ensures thread-safe copying when loggers duplicate formatters.

## Step 1: Create Your Custom Flag Formatter

Inherit from `spdlog::custom_flag_formatter` and implement two pure-virtual methods:

```cpp
// my_flag.hpp
#pragma once
#include <spdlog/custom_flag_formatter.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/common.h>
#include <spdlog/fmt/fmt.h>

class my_flag : public spdlog::custom_flag_formatter {
public:
    explicit my_flag(std::string txt) : text_(std::move(txt)) {}

    // Core formatting logic – called for every log record
    void format(const spdlog::details::log_msg&, const std::tm& tm,
                spdlog::memory_buf_t& dest) override {
        // Example: 12-hour clock hour + custom text
        std::string out = spdlog::fmt_lib::format(
            "{:02d} {}", tm.tm_hour % 12, text_);
        dest.append(out.data(), out.data() + out.size());
    }

    // Required: enables formatter cloning when pattern_formatter is copied
    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<my_flag>(text_);
    }

private:
    std::string text_;
};

```

The `format()` method receives:
- `log_msg` – The complete log record metadata
- `tm` – Broken-down time structure (reused across formatters for performance)
- `dest` – Output buffer to append formatted text

The `clone()` method must return a new instance with identical configuration. The base class provides `padinfo_` member for accessing padding specifications (width, alignment, truncation) if your flag supports modifiers like `%10x` or `%-5x`.

## Step 2: Register Your Flag with the Pattern Formatter

Use `add_flag<T>()` to bind a character to your formatter type. Constructor arguments forward directly to your class:

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

int main() {
    auto fmt = std::make_shared<spdlog::pattern_formatter>();
    
    // Register 'x' flag with constructor argument "CUSTOM"
    fmt->add_flag<my_flag>('x', "CUSTOM");
    
    // Use padding: %5x means width=5, default right-aligned
    fmt->set_pattern("[%n] [%5x] %v");
    
    auto logger = spdlog::stdout_logger_mt("example");
    logger->set_formatter(fmt);
    
    logger->info("hello world");
}

```

**Expected output** (assuming 3 AM execution):

```

[example] [   03 CUSTOM] hello world

```

The `add_flag<T>()` method returns `*this`, enabling chained registration of multiple flags:

```cpp
fmt->add_flag<my_flag>('x', "FIRST")
    .add_flag<another_flag>('y', 42, "SECOND")
    .set_pattern("[%x] [%10y] %v");

```

## Step 3: Handle Advanced Scenarios

### Exception Safety in Custom Flags

Custom flags may throw, and spdlog propagates these exceptions. The test suite in [`tests/test_pattern_formatter.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_pattern_formatter.cpp) (lines 67-87) demonstrates this pattern:

```cpp
class custom_test_flag : public spdlog::custom_flag_formatter {
public:
    explicit custom_test_flag(std::string txt) : payload_(std::move(txt)) {}
    
    void format(const spdlog::details::log_msg&, const std::tm&, 
                spdlog::memory_buf_t& dest) override {
        if (payload_ == "throw_me")
            throw spdlog::spdlog_ex("custom_flag_exception_test");
        dest.append(payload_.data(), payload_.data() + payload_.size());
    }
    
    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<custom_test_flag>(payload_);
    }
    
private:
    std::string payload_;
};

```

Registered and tested as:

```cpp
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<custom_test_flag>('t', "throw_me")
   .add_flag<custom_test_flag>('u', "safe_value")
   .set_pattern("[%t] [%u] %v");

```

### Accessing Padding Information

The `custom_flag_formatter` base class exposes `padinfo_` for implementing width-aware formatting:

```cpp
void format(const spdlog::details::log_msg&, const std::tm&,
            spdlog::memory_buf_t& dest) override {
    std::string raw = generate_content();
    
    // Apply standard spdlog padding (width, align left/right, truncate)
    if (padinfo_.enabled_) {
        // Use padinfo_.width_, padinfo_.align_, padinfo_.truncate_
        // Or delegate to helper methods in the base class
    }
    dest.append(raw.data(), raw.data() + raw.size());
}

```

## Key Implementation Files

| File | Purpose | Location |
|------|---------|----------|
| [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) | `custom_flag_formatter` base class and `add_flag<T>()` API | [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) |
| [`src/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/src/pattern_formatter-inl.h) | Pattern compilation and flag resolution logic | [`src/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/src/pattern_formatter-inl.h) |
| [`tests/test_pattern_formatter.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_pattern_formatter.cpp) | Reference implementations and edge cases | [`tests/test_pattern_formatter.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_pattern_formatter.cpp) |

## Summary

- **Derive** from `spdlog::custom_flag_formatter` to define custom pattern flag behavior
- **Implement** both `format()` for output generation and `clone()` for formatter duplication
- **Register** via `pattern_formatter::add_flag<T>(char, Args&&...)` before pattern compilation
- **Access** `padinfo_` for built-in padding support and `tm` for time-based formatting
- **Clone** properly to ensure thread-safe formatter sharing across logger copies

The `custom_handlers_` unordered map in `pattern_formatter` stores registered flags as `std::unique_ptr<custom_flag_formatter>`, allowing multiple distinct custom flags per formatter instance with O(1) lookup during pattern compilation.

## Frequently Asked Questions

### What characters can I use for custom flag names?

Any single ASCII character except those reserved for built-in flags (`v`, `t`, `n`, `l`, `L`, `a`, `A`, `b`, `B`, `c`, `C`, `Y`, `D`, `m`, `d`, `H`, `I`, `M`, `S`, `e`, `f`, `F`, `p`, `r`, `R`, `T`, `z`, `Z`, `E`, `P`, `s`, `X`, `i`, `u`, `o`, `O`, `%`). Review the complete list in [`src/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/src/pattern_formatter-inl.h) to avoid collisions.

### Do custom flags support padding specifiers like `%10x` or `%-5x`?

Yes. The `padinfo_` member inherited from `details::flag_formatter` contains parsed width, alignment, and truncation settings. Implement padding logic manually in `format()`, or use spdlog's internal padding helpers for consistent behavior with built-in flags.

### Why does my custom flag require a `clone()` method?

`pattern_formatter` instances are frequently copied when loggers are cloned or when asynchronous sinks duplicate formatters for thread isolation. The `clone()` method ensures each copy receives an independent instance with identical configuration. Failure to implement this causes compilation errors since `custom_flag_formatter` declares `clone()` as pure virtual.

### Can I register the same custom flag class with different characters?

Yes. Call `add_flag<T>()` multiple times with different characters and constructor arguments. Each registration creates a distinct entry in `custom_handlers_`. The test suite demonstrates this with `custom_test_flag` registered as both `'t'` and `'u'` with different payloads.