Creating a Custom Formatter for spdlog: Architecture and Implementation Guide
To create a custom formatter for spdlog, inherit from spdlog::formatter and implement the pure virtual format() method to write into a memory_buf_t buffer, plus a clone() method that returns a deep copy via std::make_unique.
Creating a custom formatter for spdlog allows you to control exactly how log records are serialized, whether you need structured JSON output or specialized plain text formats. The formatting system is built around the abstract spdlog::formatter base class defined in include/spdlog/formatter.h, which every logger uses to process incoming details::log_msg objects. By implementing this interface in the gabime/spdlog repository, you can integrate bespoke output formats while maintaining the library's high-performance asynchronous logging capabilities.
Understanding the Formatter Architecture
The Base Formatter Interface
The spdlog::formatter base class in include/spdlog/formatter.h defines the contract for all formatting logic. Every formatter must implement two pure virtual functions:
void format(const details::log_msg &msg, memory_buf_t &dest): Converts the incoming log message into a string representation. You append the final output to thedestbuffer, which is a type alias for a fmt memory buffer.std::unique_ptr<formatter> clone() const: Returns a fresh copy of the formatter. Spdlog uses this when duplicating loggers to ensure thread safety and proper ownership semantics.
The format() method receives a details::log_msg structure that contains pre-processed fields including the timestamp, log level, thread ID, source file, and the message payload.
The Pattern Formatter Extension Point
For users who need to extend the default pattern-based output rather than replace it entirely, include/spdlog/pattern_formatter.h provides the custom_flag_formatter inner class. This extension point allows you to define new placeholder characters (such as %P for process ID) that integrate seamlessly with the existing pattern syntax. You register these flags using the add_flag<YourFlag>('x', "description") method on the pattern formatter instance.
Creating a Custom Formatter from Scratch
When you need complete control over the output format—such as emitting structured JSON instead of plain text—subclass spdlog::formatter directly. The formatter must be stateless or immutable to ensure thread safety, as the same instance may be called from multiple threads concurrently.
JSON Formatter Example
#include <spdlog/spdlog.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/fmt/fmt.h>
class json_formatter : public spdlog::formatter {
public:
// Build a JSON line: {"ts":"<ISO8601>","lvl":"<level>","msg":"<payload>"}
void format(const spdlog::details::log_msg &msg,
spdlog::memory_buf_t &dest) override {
fmt::format_to(
dest,
R"({{"ts":"{:%FT%TZ}","lvl":"{}","msg":"{}"}}\n)",
msg.time,
spdlog::level::to_string_view(msg.level),
fmt::string_view{msg.payload.data(), msg.payload.size()});
}
// Required clone for logger copying
std::unique_ptr<spdlog::formatter> clone() const override {
return std::make_unique<json_formatter>(*this);
}
};
int main() {
auto logger = spdlog::stdout_color_mt("my_logger");
logger->set_formatter(std::make_unique<json_formatter>());
logger->info("Application started");
logger->warn("Low disk space");
}
The formatter uses msg.time (a std::chrono::system_clock::time_point) and fmt library time specifiers to produce ISO-8601 timestamps. The clone() method creates a deep copy using std::make_unique, which is essential for spdlog's internal logger duplication logic.
Extending the Pattern Formatter with Custom Flags
If you only need to add specialized fields to the standard pattern output, inherit from spdlog::custom_flag_formatter and register your flag with the pattern formatter. This approach avoids reimplementing timestamp and level formatting while adding custom data like process IDs or request correlation IDs.
Process ID Custom Flag
#include <spdlog/pattern_formatter.h>
class pid_flag : public spdlog::custom_flag_formatter {
public:
// Append the current process id
void format_flag(const spdlog::details::log_msg&, fmt::format_context::iterator out) override {
fmt::format_to(out, "{}", static_cast<int>(::getpid()));
}
// Provide a proper clone
std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
return std::make_unique<pid_flag>(*this);
}
};
int main() {
auto logger = spdlog::stdout_color_mt("custom");
// Register %P as our new placeholder
auto pat = std::make_shared<spdlog::pattern_formatter>("%+ [%P]");
pat->add_flag<pid_flag>('P', "process id");
logger->set_formatter(pat);
logger->info("Hello from custom flag");
}
After calling add_flag<pid_flag>('P', ...), the pattern string recognizes %P and invokes your format_flag() method for each log record according to the implementation in include/spdlog/pattern_formatter.h.
Managing Formatters at Runtime
Spdlog allows dynamic formatter replacement via the set_formatter() method on any logger instance. You can also use set_pattern() as a convenience wrapper that creates a default pattern formatter internally.
auto logger = spdlog::stdout_color_mt("flex");
// Use the default pattern formatter first
logger->set_pattern("%+");
// Later switch to the JSON formatter
logger->set_formatter(std::make_unique<json_formatter>());
This flexibility lets you change output formats based on configuration changes or runtime modes without recreating loggers. The swap is atomic from the caller's perspective, though you should ensure no other thread is logging during the transition or synchronize externally if necessary.
Summary
- Inherit from
spdlog::formatterdefined ininclude/spdlog/formatter.hto create fully custom output formats that replace the default pattern behavior. - Implement
format()to append the string representation to amemory_buf_tbuffer andclone()to return a deep copy viastd::make_unique, ensuring proper ownership for logger duplication. - Extend
spdlog::custom_flag_formatterfrominclude/spdlog/pattern_formatter.hto add new placeholders to the pattern formatter usingadd_flag<>(). - Maintain thread safety by keeping formatters stateless; the
clone()method must return independent copies to prevent data races when loggers are duplicated. - Register custom formatters at runtime using
logger->set_formatter()or embed them in pattern strings when using custom flags.
Frequently Asked Questions
What two methods must a custom formatter implement?
You must implement void format(const details::log_msg &msg, memory_buf_t &dest) to construct the output string and std::unique_ptr<formatter> clone() const to return a deep copy of your formatter. The clone() method is critical for spdlog's internal logger duplication logic, as implemented in include/spdlog/formatter.h.
How do I add a custom flag to the pattern formatter?
Subclass spdlog::custom_flag_formatter, implement format_flag() and clone(), then register your flag using pattern_formatter::add_flag<YourClass>('X', "description") where 'X' is your custom placeholder character. This registration mechanism is defined in include/spdlog/pattern_formatter.h and allows seamless integration with existing pattern strings.
Are custom formatters thread-safe?
Custom formatters are thread-safe as long as they do not modify shared mutable data during the format() call. The formatter receives a const reference to the log message and writes to a destination buffer that is unique per invocation, so implement your formatter as a stateless or immutable object according to the thread safety model in gabime/spdlog.
Can I change the formatter after creating the logger?
Yes. You can call set_formatter(std::make_unique<YourFormatter>()) on an existing logger instance at any time. This replaces the formatter atomically, allowing you to switch between formats like JSON and plain text based on runtime configuration changes without destroying and recreating the logger object.
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 →