How to Set a Custom Log Format in spdlog: Pattern and Formatter Guide
Use spdlog::set_pattern() to apply a global format string or call logger->set_pattern() to configure individual loggers, leveraging conversion specifiers like %Y-%m-%d %H:%M:%S.%e %l : %v to control timestamp, level, and message layout.
The gabime/spdlog library provides a flexible, pattern-based formatting system that allows precise control over log output without modifying sink implementations. By utilizing the pattern_formatter class and its rich set of conversion specifiers, you can customize how timestamps, thread identifiers, and log levels appear across your application's output streams. This guide examines the specific APIs defined in include/spdlog/spdlog.h and include/spdlog/logger.h to implement custom log formats in spdlog at both global and per-logger scopes.
Understanding the Formatter Architecture
spdlog processes log records through formatter objects that interpret pattern strings into formatted output. The library ships with pattern_formatter, a concrete implementation declared in include/spdlog/pattern_formatter.h (lines 89-90) that parses placeholders such as %Y (year), %l (log level), and %v (message).
When you invoke set_pattern(), spdlog instantiates a fresh pattern_formatter for each sink, ensuring isolated state and thread safety. The parsing logic resides in include/spdlog/pattern_formatter-inl.h, specifically within the set_pattern method implementation (lines 1006-1010). This architecture guarantees that formatter instances are immutable after construction, allowing multiple threads to log concurrently without additional locking.
Setting a Global Custom Log Format
Apply formatting to all loggers created after the call using spdlog::set_pattern(), defined in include/spdlog/spdlog.h (lines 58-60). This function accepts a pattern string and an optional spdlog::pattern_time_type enum value to select between local and UTC timestamps.
#include "spdlog/spdlog.h"
// Global pattern: "2023-04-01 12:34:56.789 INFO : message"
spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v");
// Optional: use UTC timestamps instead of local time
spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v",
spdlog::pattern_time_type::utc);
Loggers instantiated after this call inherit the global pattern unless they explicitly override it. This approach is ideal for applications requiring consistent formatting across console, file, and remote sinks.
Configuring Per-Logger Format Patterns
Override the global default for specific logger instances using logger->set_pattern(), declared in include/spdlog/logger.h (lines 89-92). This method affects only the target logger's sinks, leaving other loggers unaffected by the change.
auto console = spdlog::stdout_color_mt("console");
auto file = spdlog::basic_logger_mt("file", "logs.txt");
// Console logger shows thread ID and short logger name
console->set_pattern("[%t] [%!%] %v");
// File logger retains the global pattern defined earlier
file->info("This uses the global format");
console->info("This uses the console-specific format");
This per-logger isolation is implemented by cloning the formatter object for each sink in the logger, ensuring that pattern changes do not leak across logger boundaries.
Essential Pattern Specifiers Reference
The pattern_formatter class recognizes conversion specifiers that inject dynamic data into log lines:
%Y,%m,%d– Year, month, and day components%H,%M,%S– Hour (24-hour), minute, and second%e– Milliseconds (3-digit fractional seconds)%l– Log level abbreviation (e.g., I, W, E, C)%v– The actual log message content%t– Operating system thread ID%P– Process ID%!%– Short logger name (truncated form)
Combine these specifiers to create compact or verbose output formats tailored to your monitoring and debugging requirements.
Advanced: Implementing Custom Formatter Objects
For formatting requirements beyond the pattern syntax, implement the abstract spdlog::formatter interface and register it using spdlog::set_formatter() or logger::set_formatter(). This approach allows complete control over the formatting pipeline, though it requires manual handling of timestamp and level conversions.
#include "spdlog/pattern_formatter.h"
// Create a custom formatter manually
auto fmt = std::make_unique<spdlog::pattern_formatter>(
"[%P] [%8!%] %v",
spdlog::pattern_time_type::local
);
// Register globally (clones the formatter for each sink)
spdlog::set_formatter(std::move(fmt));
// Or attach to a single logger
auto my_logger = spdlog::stdout_color_mt("custom");
my_logger->set_formatter(
std::make_unique<spdlog::pattern_formatter>("[%t] %v"));
According to the source code in include/spdlog/sinks/base_sink.h, the set_formatter method clones the formatter object for each sink, maintaining the immutability guarantees required for thread-safe concurrent logging.
Summary
- Global formatting: Call
spdlog::set_pattern()frominclude/spdlog/spdlog.h(lines 58-60) to establish a default pattern for all future loggers. - Per-logger formatting: Use
logger->set_pattern()frominclude/spdlog/logger.h(lines 89-92) to isolate formatting changes to specific logger instances. - Pattern syntax: Construct format strings using specifiers like
%Y-%m-%d %H:%M:%S.%e %l : %vto control date, time, level, and message placement. - Thread safety: Formatters are immutable and cloned per-sink, enabling lock-free concurrent logging across multiple threads.
- Custom implementations: Extend
spdlog::formatterand register viaset_formatter()for specialized formatting logic that exceeds pattern string capabilities.
Frequently Asked Questions
How do I change the timestamp format in spdlog?
Use the date and time conversion specifiers in your pattern string: %Y for four-digit year, %m for month, %d for day, %H for 24-hour clock, %M for minutes, and %S for seconds. Add %e to include milliseconds. Pass spdlog::pattern_time_type::utc as the second argument to set_pattern() to record timestamps in UTC rather than local system time.
Can different loggers have different formats simultaneously?
Yes. While spdlog::set_pattern() establishes a global default, calling set_pattern() on a specific logger instance overrides the pattern for that logger only. This allows console loggers to display verbose debugging information while file loggers write compact JSON or syslog-compatible formats in the same application.
What is the difference between set_pattern and set_formatter?
set_pattern() accepts a std::string pattern and internally constructs a pattern_formatter instance, providing a convenient API for text-based formatting. set_formatter() accepts a std::unique_ptr<spdlog::formatter> and allows injection of completely custom formatting logic by implementing the virtual format() method of the formatter abstract base class.
Are custom patterns thread-safe?
Yes. The pattern_formatter class is immutable after construction. When you set a pattern, spdlog clones the formatter for each sink (as implemented in include/spdlog/sinks/base_sink.h), ensuring that multiple threads can invoke logging methods concurrently without synchronization overhead or data races on the formatter state.
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 →