# How spdlog's Pattern Formatter Parses Custom Format Strings Internally

> Discover how spdlog's pattern formatter parses custom format strings in a single pass using compile_pattern and dedicated handlers for efficient logging.

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

---

**spdlog parses pattern strings in a single pass through `compile_pattern_`, which delegates to `handle_padspec_` for padding extraction and `handle_flag_` for formatter instantiation, producing a vector of formatter objects stored in `formatters_`.**

The spdlog library uses a sophisticated pattern formatter to transform format strings like `"%Y-%m-%d %H:%M:%S.%e [%l] %v"` into structured log output. Understanding how spdlog's pattern formatter parses custom format strings internally reveals why it achieves high performance while supporting extensive customization, including user-defined flags, padding specifications, and truncation behavior.

## The Three-Stage Parsing Pipeline

When you call `spdlog::set_pattern()` or construct a `pattern_formatter` directly, the library immediately compiles the pattern string into an executable chain of formatter objects. This compilation happens in three coordinated stages within [`/include/spdlog/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main//include/spdlog/pattern_formatter-inl.h).

### Stage 1: Pattern Compilation with `compile_pattern_`

The entry point is `compile_pattern_(const std::string& pattern)`, which performs a character-by-character scan of the input string. As it iterates, the function distinguishes between literal text and format specifiers.

When the scanner encounters ordinary characters, it accumulates them into an `aggregate_formatter` that copies text verbatim to the output buffer. Upon reaching a `%` character, the function finalizes any pending aggregate formatter and transitions to padding analysis. This single-pass approach ensures minimal overhead during subsequent log operations.

### Stage 2: Padding Specification Parsing with `handle_padspec_`

Immediately following a `%`, the parser calls `handle_padspec_` to extract optional padding directives. This function reads the specification according to these rules:

- **Alignment**: `-` indicates right alignment, `=` indicates center alignment, and default (no character) means left alignment.
- **Width**: One or more digits specify the field width, capped internally at 64 characters.
- **Truncation**: An optional `!` character activates truncation mode, which cuts the output if it exceeds the specified width.

The function returns a `padding_info` struct containing these parameters, which determines whether the subsequent flag formatter will be wrapped in a `scoped_padder` (real padding) or `null_scoped_padder` (no padding) during the final formatting phase.

### Stage 3: Flag Dispatch and Formatter Instantiation with `handle_flag_`

With padding information resolved, `compile_pattern_` dispatches to `handle_flag_<Padder>()`. This template function performs a two-tier lookup:

1. **Custom Flag Check**: It first searches the `custom_handlers_` map for user-registered flags added via `add_flag()`.
2. **Built-in Flag Resolution**: If no custom handler exists, it switches over known flag characters (`Y` for year, `m` for month, `v` for message text, etc.) and instantiates the corresponding specialized formatter (e.g., `details::Y_formatter<Padder>`).

For date and time components, the function sets the `need_localtime_` flag to ensure the formatter cache gets refreshed appropriately. Unknown flag characters are emitted verbatim unless truncation is active, in which case they are interpreted as source function names.

## Runtime Execution of Compiled Formatters

After compilation, the `formatters_` vector contains a sequence of ready-to-execute formatter objects. When `pattern_formatter::format` receives a log message, it iterates through this vector and invokes each formatter's `format` method, passing the log record, a `std::tm` structure, and the output buffer.

The `scoped_padder` wrapper handles the actual padding logic just before each formatter writes its output, prepending or appending spaces (or truncating content) according to the `padding_info` captured during parsing. This separation of parsing and execution allows spdlog to optimize the hot path—formatting millions of log messages—without re-parsing the pattern string.

## Practical Implementation Examples

The following examples demonstrate how to leverage the pattern formatter's capabilities:

```cpp
// Example 1: Default full formatter (equivalent to "%+")
spdlog::set_pattern("%+");

```

```cpp
// Example 2: Custom timestamp with padding and truncation
// %8!v pads the message to 8 characters and truncates if longer
spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e [%^%l%$] %8!v");

```

```cpp
// Example 3: Registering a user-defined custom flag
class my_flag : public spdlog::custom_flag_formatter {
public:
    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return std::make_unique<my_flag>();
    }
    
    void format(const spdlog::details::log_msg&, 
                const std::tm&, 
                spdlog::memory_buf_t& dest) override {
        spdlog::details::fmt_helper::append_string_view("[myflag]", dest);
    }
};

auto fmt = std::make_shared<spdlog::pattern_formatter>("%v %^%l%$");
fmt->add_flag('X', my_flag{});  // Register %X as custom flag
spdlog::set_formatter(fmt);

```

## Summary

- **Single-pass compilation**: The `compile_pattern_` function in [`pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/pattern_formatter-inl.h) transforms text patterns into executable formatter objects during construction.
- **Modular parsing**: `handle_padspec_` extracts padding metadata while `handle_flag_` resolves flag characters to concrete formatter instances.
- **Extensible design**: Custom flags integrate seamlessly through `add_flag()` and are resolved alongside built-in formatters like `Y_formatter` or `v_formatter`.
- **Efficient execution**: Pre-compiled formatters stored in `formatters_` eliminate parsing overhead during the actual logging hot path.

## Frequently Asked Questions

### What happens if I use an unknown flag character in my pattern string?

According to the implementation in [`/include/spdlog/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main//include/spdlog/pattern_formatter-inl.h), unknown flags are emitted verbatim to the output. For example, `%x` will appear as literal text unless you have registered a custom flag handler via `add_flag()`. The exception occurs when truncation is active—if the `!` specifier precedes an unknown flag, spdlog interprets it as a source function name rather than a literal string.

### How does padding affect performance in spdlog pattern formatting?

Padding incurs minimal overhead because the width calculation happens once during the compilation phase in `handle_padspec_`. At runtime, the `scoped_padder` wrapper calculates the required padding size by querying the formatter's output length, then uses efficient buffer operations from [`fmt_helper.h`](https://github.com/gabime/spdlog/blob/main/fmt_helper.h) to inject spaces. If no padding is specified, the formatter uses `null_scoped_padder`, a zero-cost abstraction that compiles away entirely.

### Can I modify the pattern formatter after it has been constructed?

While you cannot change the pattern string dynamically after construction, you can register additional custom flags at any time using `add_flag()`. However, doing so requires thread-safe access to the `custom_handlers_` map. For complete pattern changes, spdlog recommends creating a new `pattern_formatter` instance and swapping it via `set_formatter()`, as the `formatters_` vector is optimized for immutable execution.

### What is the maximum width I can specify for padding operations?

The parser caps the width value at 64 characters during the `handle_padspec_` execution. If you specify a width greater than 64, the parser truncates the value to this maximum to prevent excessive memory allocation and potential buffer overflow issues during the formatting phase.