How to Use spdlog's Pattern Formatter with Custom Flags
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.
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:
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:
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:
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.
// 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_;
};
// 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.
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 (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.
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_;
};
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.
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 validates this behavior under the "custom flags-exception" section.
Summary
- Inherit from
custom_flag_formatterto create new pattern flags, implementing bothformat()for output generation andclone()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%5xor%-10x. - Reference the source in
include/spdlog/pattern_formatter.hfor the class definitions andtests/test_pattern_formatter.cppfor comprehensive usage examples. - Handle errors by throwing
spdlog::spdlog_exexceptions within theformat()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. 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.
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 →