# How to Use spdlog's Pattern Formatter with Custom Flags

> Learn to use spdlog's pattern formatter with custom flags. Inherit spdlog::custom_flag_formatter, override format and clone, and register your flag for enhanced logging.

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

---

**Implement custom pattern flags in spdlog by inheriting from `spdlog::custom_flag_formatter`, overriding the `format()` and `clone()` methods, and registering the flag with `pattern_formatter::add_flag()`.**

The **gabime/spdlog** library provides a flexible logging framework where log line formatting is handled by the `pattern_formatter` class. While spdlog ships with built-in flags like `%l` for log level and `%v` for the message, you can extend the formatter with custom flags to inject application-specific data, specialized time formats, or dynamic content. This guide demonstrates how to create, register, and use custom pattern flags based on the actual implementation in the spdlog v1.x source code.

## Understanding the Pattern Formatter Architecture

The formatting pipeline centers on three key components defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h).

**`flag_formatter`** is the abstract base class that all formatters inherit. It receives a log message, a `std::tm` time structure, and a destination buffer, then writes its output to that buffer.

**`custom_flag_formatter`** extends `flag_formatter` and adds a pure-virtual `clone()` method. This method is required for deep-copying when a formatter instance is cloned (e.g., when creating new logger instances).

**`pattern_formatter::add_flag<T>()`** registers your custom formatter. This template method stores a `std::unique_ptr<T>` in an internal `custom_handlers_` map, using a single character as the key. When `set_pattern()` parses a pattern string like `%x`, it checks this map for custom handlers before falling back to built-in flags.

When a log record is emitted, `pattern_formatter::format` iterates over the vector of formatter objects and calls each formatter's `format` method. Custom formatters participate in the same pipeline as built-in ones and can leverage spdlog's padding, truncation, and timezone facilities.

## Creating a Custom Flag Formatter

Follow these steps to implement and register a custom flag.

### Step 1: Inherit from custom_flag_formatter

Create a class that inherits from `spdlog::custom_flag_formatter`. You must implement two methods: `format()` to write output, and `clone()` to support copying.

### Step 2: Implement the format Method

The `format` method signature is:

```cpp
void format(const spdlog::details::log_msg& msg, 
            const std::tm& tm, 
            spdlog::memory_buf_t& dest) override;

```

Write your formatted output directly to the `dest` buffer using `dest.append()`.

### Step 3: Implement the clone Method

Return a new instance of your formatter with the same state:

```cpp
std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
    return spdlog::details::make_unique<your_class>(/* args */);
}

```

### Step 4: Register and Use the Flag

Instantiate a `pattern_formatter`, call `add_flag<YourClass>('x', args...)`, then set the pattern using `%x`:

```cpp
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<your_class>('x', constructor_args)
    .set_pattern("[%n] %x %v");

```

## Practical Examples

### Simple Custom Flag (Constant String)

This example creates a flag `%h` that outputs a constant greeting string.

```cpp
// custom_flag.hpp
#pragma once
#include <spdlog/custom_flag_formatter.h>

class hello_flag : public spdlog::custom_flag_formatter {
public:
    explicit hello_flag(std::string txt) : txt_(std::move(txt)) {}

    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<hello_flag>(txt_);
    }

    void format(const spdlog::details::log_msg&, const std::tm&, 
                spdlog::memory_buf_t& dest) override {
        dest.append(txt_.data(), txt_.data() + txt_.size());
    }

private:
    std::string txt_;
};

```

```cpp
// main.cpp
#include <spdlog/spdlog.h>
#include "custom_flag.hpp"

int main() {
    auto fmt = std::make_shared<spdlog::pattern_formatter>();
    fmt->add_flag<hello_flag>('h', "👋 Hello")
        .set_pattern("[%n] [%h] %v");

    auto logger = spdlog::stdout_logger_mt("demo");
    logger->set_formatter(fmt);
    logger->info("world");
}

```

Output:

```

[demo] [👋 Hello] world

```

### Time Formatting Flag (12-Hour Notation)

This flag `%t` formats the current time in 12-hour notation with AM/PM.

```cpp
class time12_flag : public spdlog::custom_flag_formatter {
public:
    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<time12_flag>();
    }

    void format(const spdlog::details::log_msg&, const std::tm& tm,
                spdlog::memory_buf_t& dest) override {
        auto formatted = spdlog::fmt_lib::format("{:d}:{:02d}{}",
            tm.tm_hour % 12 == 0 ? 12 : tm.tm_hour % 12,
            tm.tm_min,
            tm.tm_hour >= 12 ? "PM" : "AM");
        dest.append(formatted.data(), formatted.data() + formatted.size());
    }
};

```

Usage:

```cpp
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<time12_flag>('t')
    .set_pattern("[%n] %t > %v");

```

Result (assuming local time is 14:05):

```

[demo] 2:05PM > Hello world

```

### Padding and Truncation Support

The base class provides a `padinfo_` member of type `padding_info`. When the pattern contains width specifiers like `%5x` or `%=10x`, spdlog populates this member automatically.

```cpp
class padded_flag : public spdlog::custom_flag_formatter {
public:
    explicit padded_flag(std::string txt) : txt_(std::move(txt)) {}

    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<padded_flag>(txt_);
    }

    void format(const spdlog::details::log_msg&, const std::tm&, 
                spdlog::memory_buf_t& dest) override {
        if (padinfo_.enabled()) {
            std::string padded(txt_.size() + padinfo_.width_, ' ');
            if (padinfo_.side_ == spdlog::details::padding_info::pad_side::right) {
                padded.replace(0, txt_.size(), txt_);
            } else {
                padded.replace(padded.size() - txt_.size(), txt_.size(), txt_);
            }
            dest.append(padded.data(), padded.data() + padded.size());
        } else {
            dest.append(txt_.data(), txt_.data() + txt_.size());
        }
    }

private:
    std::string txt_;
};

```

```cpp
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<padded_flag>('p', "data")
    .set_pattern("[%n] [%5p] %v");  // Produces: [demo] [ data] message

```

### Error Handling in Custom Flags

Custom flags can throw exceptions to signal errors. The `pattern_formatter` propagates these as `spdlog::spdlog_ex` exceptions to the caller.

```cpp
class exploding_flag : public spdlog::custom_flag_formatter {
public:
    explicit exploding_flag(std::string trigger) : trigger_(std::move(trigger)) {}

    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<exploding_flag>(trigger_);
    }

    void format(const spdlog::details::log_msg&, const std::tm&, 
                spdlog::memory_buf_t&) override {
        if (trigger_ == "boom") {
            throw spdlog::spdlog_ex("exploding_flag triggered");
        }
    }

private:
    std::string trigger_;
};

```

The test suite in [`tests/test_pattern_formatter.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_pattern_formatter.cpp) validates this behavior under the "custom flags-exception" section.

## Summary

- **Inherit from `custom_flag_formatter`** to create new pattern flags, implementing both `format()` for output generation and `clone()` for deep-copy support.
- **Register flags** using `pattern_formatter::add_flag<T>('x', args...)` where `'x'` becomes the pattern character (e.g., `%x`).
- **Access padding** through the protected `padinfo_` member to support width specifiers like `%5x` or `%-10x`.
- **Reference the source** in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) for the class definitions and [`tests/test_pattern_formatter.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_pattern_formatter.cpp) for comprehensive usage examples.
- **Handle errors** by throwing `spdlog::spdlog_ex` exceptions within the `format()` method, which the library propagates to the caller.

## Frequently Asked Questions

### What is the difference between flag_formatter and custom_flag_formatter?

**`flag_formatter`** is the internal abstract base class used by all formatters, while **`custom_flag_formatter`** is the public extension point for user-defined flags. The key distinction is that `custom_flag_formatter` requires a `clone()` method to support deep-copying when formatters are duplicated across logger instances. Always inherit from `custom_flag_formatter` when adding new flags to ensure proper copy semantics.

### How do I handle padding in custom pattern flags?

Access the **`padinfo_`** protected member inherited from `custom_flag_formatter`. This `padding_info` structure contains the width, side (left/right/center), and truncate settings parsed from patterns like `%5x` or `%-10.8x`. Check `padinfo_.enabled()` to determine if padding was requested, then apply spaces or truncation accordingly before appending to the destination buffer.

### Can custom flags access the log message content?

Yes. The **`format()`** method receives a `const spdlog::details::log_msg&` parameter containing the raw message text, log level, source location, and timestamp. You can access `msg.payload` for the message content, `msg.level` for the severity, or `msg.source` for filename and line number information, enabling context-sensitive formatting logic.

### Where are custom flags stored in the formatter?

Custom flags are stored in the **`custom_handlers_`** map inside `pattern_formatter`, defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h). This map associates single characters (the flag letters) with factory functions that create instances of your custom formatter. When `set_pattern()` parses a pattern string, it checks this map first before consulting the built-in flag registry, allowing your custom implementations to override or extend default behavior.