How to Use pattern_formatter with Custom Flags in spdlog
To create custom pattern flags in spdlog, inherit from spdlog::custom_flag_formatter, implement the clone() and format() methods, and register your formatter with pattern_formatter::add_flag() before setting the pattern string.
spdlog formats log records through the pattern_formatter class located in include/spdlog/pattern_formatter.h. While built-in flags like %v (message) and %l (level) cover common use cases, the library allows you to inject custom data by implementing your own flag formatters. This guide shows you how to extend the pattern syntax with user-defined tokens using the exact mechanism implemented in the gabime/spdlog repository.
Pattern Formatter Architecture
The formatting pipeline relies on three core components that work together to transform log messages into output strings.
Core Components
spdlog::details::flag_formatter: The abstract base class that defines the interface for all pattern flags. It receives alog_msgstructure, astd::tmtime object, and a destination buffer.spdlog::custom_flag_formatter: Extendsflag_formatterand adds a pure-virtualclone()method required for deep-copying when a formatter is cloned or shared between loggers.pattern_formatter: The orchestrator that maintains aformatters_vector and acustom_handlers_map. When you callset_pattern(), it parses the pattern string and instantiates the appropriate flag formatters.
Registration and Parsing Mechanism
Custom flags are registered via the add_flag<T>(char flag, Args&&… args) method in pattern_formatter.h. This method stores a std::unique_ptr<T> in the internal custom_handlers_ map, where the key is the single character that will be recognized in the pattern string (e.g., 'x' for %x). When set_pattern() parses the string, it resolves both built-in and user-registered flags, building a vector of formatter objects that participate in the same pipeline.
Implementing a Custom Flag Formatter
Creating a working custom flag requires three steps: implementing the class, registering it with the formatter, and using the new token in your pattern string.
Step 1: Inherit from custom_flag_formatter
Your class must inherit from spdlog::custom_flag_formatter and implement two methods:
clone(): Returns astd::unique_ptr<custom_flag_formatter>with the same state. This is required because spdlog clones formatters when initializing loggers.format(const details::log_msg&, const std::tm&, memory_buf_t&): Writes your custom output into the destination buffer.
#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)) {}
// Required deep-copy for formatter cloning
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_;
};
Step 2: Register with pattern_formatter
Instantiate a pattern_formatter and call add_flag<YourClass>('x', ctor_args...) where 'x' is your flag character:
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<hello_flag>('h', "👋 Hello");
Step 3: Apply the Pattern
Use your custom flag in the pattern string passed to set_pattern():
fmt->set_pattern("[%n] [%h] %v");
auto logger = spdlog::stdout_logger_mt("demo");
logger->set_formatter(fmt);
logger->info("world");
// Output: [demo] [👋 Hello] world
Practical Code Examples
Simple Custom Flag (Static Text)
The example above demonstrates a basic flag that appends a constant string. This is useful for injecting static metadata like build versions or environment identifiers into every log line.
Time Formatting Custom Flag
You can access the std::tm parameter to implement custom time formats not covered by built-in flags:
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:
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<time12_flag>('t')
.set_pattern("[%n] %t > %v");
// Result: [demo] 2:05PM > Hello world
Supporting Padding and Truncation
The custom_flag_formatter base class exposes the padinfo_ member (type padding_info). When users specify width in the pattern (e.g., %5x or %8!x), spdlog populates this structure automatically. You can honor padding by checking padinfo_.enabled():
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;
// Apply left/right padding based on padinfo_.side_
// padinfo_.width_ contains the target width
// padinfo_.truncate_ indicates if truncation is enabled
dest.append(padded.data(), padded.data() + padded.size());
} else {
dest.append(txt_.data(), txt_.data() + txt_.size());
}
}
private:
std::string txt_;
};
Reference implementations in tests/test_pattern_formatter.cpp demonstrate handling pad_side::left, pad_side::right, and pad_side::center alignments.
Error Handling in Custom Flags
If your custom flag can throw exceptions (e.g., accessing external resources), spdlog will propagate these as spdlog::spdlog_ex exceptions:
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_;
};
When registered and triggered, the exception bubbles up through pattern_formatter::format() to the caller, allowing you to handle initialization errors or runtime failures in your application logic.
Summary
- Inherit from
custom_flag_formatterininclude/spdlog/pattern_formatter.hto create new pattern tokens. - Implement
clone()to enable deep-copying of your formatter when loggers are initialized or duplicated. - Register with
add_flag<T>()to bind a character (like'x') to your class constructor arguments. - Access padding information through the inherited
padinfo_member to support width specifiers like%5x. - Handle exceptions appropriately, as spdlog propagates
spdlog::spdlog_exthrown within custom flags.
Frequently Asked Questions
Do I need to implement the clone() method for my custom flag?
Yes. The clone() method is mandatory because spdlog::custom_flag_formatter requires it for deep-copying when a formatter is cloned or shared between multiple loggers. Return a std::unique_ptr<custom_flag_formatter> constructed with your class's copy constructor or state.
Can custom flags access the log message content?
Yes. The format() method receives a const spdlog::details::log_msg& parameter that contains the log level, thread ID, source location, and the actual message payload. You can inspect these fields to implement conditional formatting or include message metadata in your output.
How do I handle padding and alignment in custom pattern flags?
Check padinfo_.enabled() to determine if the user specified a width in the pattern string. The padinfo_ struct (type spdlog::details::padding_info) contains width_, side_ (left/right/center), and truncate_ flags. Apply these to your output string before appending to the destination buffer, or use the base class methods if delegating to standard formatting.
Where can I find reference implementations and tests?
The spdlog repository contains comprehensive examples in tests/test_pattern_formatter.cpp, including sections on custom flags, custom flags-padding, and custom flags-exception. These demonstrate edge cases like exception safety and complex padding scenarios that you can adapt for your own implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →