Custom Formatting with fmt Library Integration in spdlog: A Complete Guide
spdlog delegates all user message formatting to the fmt library via format_string_t type-deduced wrappers, while log record formatting (timestamps, levels, logger names) is handled by a customizable pattern formatter that supports custom flags through the custom_flag_formatter base class.
spdlog is a fast, header-only C++ logging library that leverages the fmt library for type-safe text formatting. Mastering custom formatting with fmt library integration in spdlog enables you to format user-defined types directly in log calls and extend log record layouts with domain-specific context such as request IDs or correlation IDs.
Understanding spdlog's Two-Level Formatting Architecture
spdlog separates formatting into two independent concerns. In include/spdlog/spdlog.h, variadic logger calls like spdlog::info("Hello {}", name) capture arguments through templated overloads using format_string_t or wformat_string_t wrappers. These forward directly to fmt, meaning any type with a fmt::formatter<T> specialization works immediately.
Log record metadata (timestamps, log levels, thread IDs) is formatted separately by the pattern formatter. The default implementation resides in include/spdlog/pattern_formatter.h with inline definitions in include/spdlog/pattern_formatter-inl.h. This parser builds a sequence of flag formatters (e.g., %Y for year, %l for level) and assembles the final output string.
| Concern | Implementation | fmt Integration |
|---|---|---|
| User message | spdlog::logger::log → spdlog::details::log_msg |
Direct call to fmt::format via format_string_t |
| Log record | spdlog::pattern_formatter → flag_formatter objects |
Uses fmt utilities internally; custom flags may call fmt directly |
Message-Level Custom Formatting with fmt::formatter
To format your own types in log calls, provide a template specialization of fmt::formatter<T>. This requires implementing a parse method for format specifications and a format method that writes to the output iterator.
// Point.hpp
struct Point { int x, y; };
template <>
struct fmt::formatter<Point> : fmt::formatter<std::string> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const Point& p, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "({},{})", p.x, p.y);
}
};
#include <spdlog/spdlog.h>
#include "Point.hpp"
int main() {
Point pt{3, 7};
spdlog::info("Current position: {}", pt); // Output: Current position: (3,7)
}
The info call forwards the format string and arguments to fmt, which dispatches to your fmt::formatter<Point> specialization to render the value.
Record-Level Custom Formatting with Pattern Flags
To emit custom context in every log line (such as a hex-encoded thread ID), extend spdlog::custom_flag_formatter (defined at lines 56-63 of include/spdlog/pattern_formatter.h). You must override clone() for copy semantics and format() to write into the destination buffer.
#include <spdlog/spdlog.h>
#include <spdlog/pattern_formatter.h>
class thread_id_flag : public spdlog::custom_flag_formatter {
public:
std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
return std::make_unique<thread_id_flag>(*this);
}
void format(const spdlog::details::log_msg&,
const std::tm&,
spdlog::memory_buf_t& dest) override {
fmt::format_to(dest, "{:x}", std::this_thread::get_id());
}
};
Register your flag using pattern_formatter::add_flag<T>() (declared at lines 84-88 of pattern_formatter.h) and apply the pattern:
int main() {
auto console = spdlog::stdout_color_mt("console");
auto formatter = std::make_unique<spdlog::pattern_formatter>();
formatter->add_flag<thread_id_flag>('T');
formatter->set_pattern("[%T] %v"); // %T invokes thread_id_flag
console->set_formatter(std::move(formatter));
console->info("Hello from custom flag");
}
Inside the flag's format method, you may use any fmt facilities such as fmt::format_to for complex representations.
Combining Message and Record Formatting
You can simultaneously use custom type formatters and custom pattern flags:
#include <spdlog/spdlog.h>
#include <spdlog/pattern_formatter.h>
#include "Point.hpp" // Contains fmt::formatter<Point>
int main() {
auto logger = spdlog::stdout_color_mt("app");
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] %l %v");
Point p{42, 13};
logger->info("Received point {}", p); // Uses fmt::formatter<Point>
}
Key Implementation Files
Understanding these source locations helps when debugging or extending formatting behavior:
include/spdlog/spdlog.h– Public logger API definingformat_string_toverloads that forward to fmt.include/spdlog/pattern_formatter.h– Declaration ofpattern_formatterand thecustom_flag_formatterbase class (see lines 56-63 for the interface and lines 84-88 foradd_flag).include/spdlog/pattern_formatter-inl.h– Inline implementations of pattern parsing and default flag formatters.src/bundled_fmtlib_format.cpp– Provides the bundled fmt library when spdlog ships its own copy.include/spdlog/fmt/fmt.h– Re-exports fmt symbols from the bundled or external installation.
Summary
- fmt delegation: spdlog forwards all user message formatting to fmt via
format_string_twrappers inspdlog.h, requiring no additional spdlog code for custom types. - Type customization: Implement
fmt::formatter<T>to format user-defined structures directly in log calls. - Pattern extension: Derive from
custom_flag_formatterand register withpattern_formatter::add_flag<T>()to inject custom metadata into log lines. - Architecture independence: Message formatting and record formatting operate on separate pipelines, allowing you to customize either or both without interference.
Frequently Asked Questions
How do I format a custom struct with spdlog?
Specialize fmt::formatter<T> for your type within the same namespace or the fmt namespace. Implement the parse and format methods according to the fmt API. Once defined, you can pass instances directly to any spdlog function such as spdlog::info("Value: {}", myStruct).
Can I add custom pattern flags to spdlog's output?
Yes. Create a class inheriting from spdlog::custom_flag_formatter, override the clone() and format() methods, then register the flag using pattern_formatter::add_flag<T>('X') where 'X' is your pattern character. This is defined in include/spdlog/pattern_formatter.h at lines 84-88.
Does spdlog require an external fmt installation?
No. spdlog bundles the fmt library in src/bundled_fmtlib_format.cpp and re-exports symbols through include/spdlog/fmt/fmt.h. You can use the bundled version or link against an external fmt installation by adjusting your build configuration.
What is the difference between message formatting and record formatting in spdlog?
Message formatting handles the user-supplied format string and arguments (e.g., "Hello {}", name), delegated entirely to fmt. Record formatting handles the surrounding metadata such as timestamps, log levels, and logger names via the pattern formatter and its collection of flag formatters.
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 →