# How to Set a Custom Log Format in spdlog: Pattern and Formatter Guide

> Learn to set a custom log format in spdlog using set_pattern() for global or logger-specific formatting. Control timestamps, levels, and message layouts with conversion specifiers.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: how-to-guide
- Published: 2026-07-27

---

**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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h) and [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/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`](https://github.com/gabime/spdlog/blob/main/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`](https://github.com/gabime/spdlog/blob/main/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`](https://github.com/gabime/spdlog/blob/main/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.

```cpp
#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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) (lines 89-92). This method affects only the target logger's sinks, leaving other loggers unaffected by the change.

```cpp
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.

```cpp
#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`](https://github.com/gabime/spdlog/blob/main/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()` from [`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h) (lines 58-60) to establish a default pattern for all future loggers.
- **Per-logger formatting**: Use `logger->set_pattern()` from [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/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 : %v` to 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::formatter` and register via `set_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`](https://github.com/gabime/spdlog/blob/main/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.