How to Add Custom Log Formatters in spdlog: A Complete Guide
To add a custom log formatter in spdlog, inherit from spdlog::formatter and implement the format() and clone() methods, then attach it to a logger using set_formatter().
The spdlog library (available at gabime/spdlog) decouples log storage from presentation through the abstract spdlog::formatter interface defined in include/spdlog/formatter.h. When you need to add custom log formatters in spdlog to generate JSON, XML, or other specialized outputs, you create a derived class that transforms details::log_msg structures into your required format.
Understanding the spdlog Formatter Architecture
The formatting pipeline centers on the spdlog::formatter abstract base class located in include/spdlog/formatter.h. Every logger maintains a std::unique_ptr<formatter> that processes each log_msg before output. To create a custom implementation, you must override two pure virtual functions:
void format(const details::log_msg &msg, memory_buf_t &dest)– Converts the incoming log message into a string representation and appends it to the destination buffer.std::unique_ptr<formatter> clone() const– Returns a deep copy of the formatter instance, which spdlog uses when duplicating loggers.
The default pattern-based implementation resides in include/spdlog/pattern_formatter.h and serves as a reference for building custom formatters.
Creating a Completely Custom Formatter
For output formats not supported by the pattern engine (such as JSON or binary protocols), implement the base class directly.
Implementing the format() Method
The format() method receives a details::log_msg containing pre-processed fields (timestamp, log level, thread ID, source location) and a memory_buf_t buffer. You append your formatted output directly to this buffer using fmt library utilities.
Implementing the clone() Method
The clone() method must return a new instance of your formatter with the same configuration. Use std::make_unique<YourFormatter>(*this) to create a deep copy, ensuring that any member variables are properly duplicated.
#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 receives msg.time (a std::chrono::system_clock::time_point) and uses fmt’s time formatting (%FT%TZ) to produce an ISO‑8601 timestamp.
Extending the Pattern Formatter with Custom Flags
When you only need to add a new placeholder (e.g., %P for process ID) rather than a complete format overhaul, subclass spdlog::custom_flag_formatter from include/spdlog/pattern_formatter.h. This inner class provides a lighter extension point.
#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 registration, the pattern [%P] prints the process ID for each log line.
Attaching Your Formatter to a Logger
Once instantiated, attach your custom formatter using logger->set_formatter(), which accepts a unique_ptr<formatter>. You can also switch formatters at runtime to change output styles dynamically.
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>());
Key Implementation Files
include/spdlog/formatter.h– Defines the abstractspdlog::formatterbase class and thememory_buf_ttype alias.include/spdlog/pattern_formatter.h– Containsspdlog::pattern_formatterand thecustom_flag_formatterextension point for adding pattern flags.
Summary
- Inherit from
spdlog::formatterfor complete control over output serialization when adding custom log formatters in spdlog. - Implement
format()to write formatted strings to the providedmemory_buf_tbuffer using fmt library functions. - Implement
clone()to returnstd::make_unique<YourFormatter>(*this), enabling spdlog to safely duplicate your formatter across logger copies. - Extend
spdlog::custom_flag_formatterwhen you only need to add new pattern placeholders rather than replacing the entire formatting engine. - Reference
include/spdlog/formatter.handinclude/spdlog/pattern_formatter.hfor the authoritative interface definitions.
Frequently Asked Questions
What is the spdlog::formatter base class?
The spdlog::formatter base class is an abstract interface defined in include/spdlog/formatter.h that specifies how log messages are converted to output strings. It requires derived classes to implement the format() method for serialization and the clone() method for object duplication.
Why does my custom formatter need a clone() method?
Spdlog calls clone() whenever it needs to duplicate a logger, such as when creating async logger clones or copying logger configurations. Your implementation must return a deep copy via std::make_unique<YourFormatter>(*this) to ensure that separate logger instances do not share mutable formatter state.
Can I extend the pattern formatter instead of writing a full formatter?
Yes. If you only need to add custom placeholders like %P for process ID, inherit from spdlog::custom_flag_formatter (defined in include/spdlog/pattern_formatter.h) and register your flag using pattern_formatter::add_flag<>(). This approach is simpler than implementing the full spdlog::formatter interface.
How do I ensure my custom formatter is thread-safe?
Keep your formatter implementation stateless or immutable. The format() method may be called concurrently from multiple threads, so avoid modifying member variables during formatting. If you must maintain state, use thread-local storage or mutexes, though statelessness is preferred for performance according to the spdlog architecture.
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 →