# Thread Safety of spdlog _st vs _mt Loggers: When to Use Each Variant

> Understand the thread safety of spdlog _st vs _mt loggers. Learn when to use the single-threaded _st logger for performance or the multi-threaded _mt logger for safe concurrent access.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: deep-dive
- Published: 2026-07-14

---

**spdlog's `_st` (single-threaded) loggers provide no internal synchronization and are unsafe for concurrent access, while `_mt` (multi-threaded) loggers use mutexes to protect shared state, making them thread-safe for concurrent calls.**

The gabime/spdlog library distinguishes between high-performance single-threaded and thread-safe multi-threaded logging variants through `_st` and `_mt` suffixes. Understanding the thread safety implications of these logger types is essential for preventing data races in concurrent C++ applications. This guide examines the implementation details, performance trade-offs, and correct usage patterns based on the spdlog source code.

## Understanding `_st` and `_mt` Logger Types

spdlog provides two families of logger factories that determine synchronization behavior at compile time.

### Single-Threaded (`_st`) Loggers

The `_st` suffix designates **single-threaded** loggers that intentionally omit internal synchronization. According to the source code in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h), these loggers access internal state such as sink lists and formatting buffers without mutex protection.

This design eliminates locking overhead, yielding higher throughput when the caller guarantees exclusive access. However, concurrent calls from multiple threads result in undefined behavior, including message interleaving and corruption of internal data structures.

### Multi-Threaded (`_mt`) Loggers

The `_mt` suffix creates **multi-threaded** loggers that protect shared resources with mutexes. The implementation in [`include/spdlog/logger-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger-inl.h) wraps critical sections in locks, ensuring that concurrent calls to methods like `info()` or `debug()` remain atomic and that log messages do not interleave.

The README explicitly warns: "only use if all your loggers are thread-safe (`_mt` loggers)" ([`README.md`](https://github.com/gabime/spdlog/blob/main/README.md) line 175).

## Performance Implications and Use Cases

The distinction exists primarily to optimize for scenarios where thread safety guarantees are unnecessary.

### When to Use `_st` Loggers

Use **_st** loggers in performance-critical, thread-confined components where you control exclusive access. For example, a dedicated worker thread processing a single queue can benefit from the lock-free path that removes mutex overhead on every log call.

### When to Use `_mt` Loggers

Use **_mt** loggers as the default choice for general-purpose logging. The modest overhead of mutex acquisition ensures data consistency when multiple threads write to the same logger instance.

## Asynchronous Logging and Thread Safety

Async loggers build upon the multi-threaded infrastructure but delegate actual message writing to a background thread pool. While the `async_logger` class defined in [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h) is thread-safe, it still depends on the underlying sink's `_mt` or `_st` type for final output operations.

Note that the Mapped Diagnostic Context (MDC) currently works only with synchronous loggers because it relies on thread-local storage ([`README.md`](https://github.com/gabime/spdlog/blob/main/README.md) lines 478-480).

## Creating Thread-Safe and Single-Threaded Loggers

The following examples demonstrate the API differences between the two variants.

*Creating a multi-threaded logger for concurrent use:*

```cpp
#include <spdlog/spdlog.h>

int main() {
    // Thread-safe logger that writes to the console with colors
    auto console = spdlog::stdout_color_mt("console");
    console->info("Hello from thread-safe logger");
}

```

*Creating a single-threaded logger for a dedicated worker:*

```cpp
#include <spdlog/spdlog.h>

void worker_thread() {
    // No internal locking – must be used only from this thread
    static auto logger = spdlog::basic_logger_st("worker", "worker.log");
    logger->debug("Worker step completed");
}

```

*Initializing an asynchronous logger:*

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/async.h>

int main() {
    // Must be called before any async logger creation
    spdlog::init_thread_pool(8192, 1); // queue size 8k, 1 background thread

    // Async logger built on top of a multi-threaded sink
    auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>(
        "async_file", "async.log");
    async_file->info("This goes through the async thread pool");
}

```

## Summary

- **`_mt`** loggers provide thread-safe concurrent access using mutex protection defined in [`include/spdlog/logger-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger-inl.h), making them suitable for multi-threaded applications.
- **`_st`** loggers eliminate synchronization overhead for single-threaded contexts but cause data races if shared across threads.
- **Async loggers** maintain thread safety at the logger level while depending on underlying sink types for final output.
- **MDC functionality** is restricted to synchronous loggers due to thread-local storage dependencies documented in the README.

## Frequently Asked Questions

### What happens if I use an `_st` logger from multiple threads?

Using a single-threaded logger from multiple threads simultaneously results in undefined behavior, including corrupted log output and race conditions on internal data structures. The `_st` variant intentionally omits mutexes in [`include/spdlog/logger-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger-inl.h) to maximize performance when exclusive access is guaranteed by the caller.

### Are async loggers in spdlog thread-safe?

Yes, `async_logger` instances are thread-safe and can be called concurrently from multiple threads. However, they rely on the underlying sink being multi-threaded (`_mt`) when shared between loggers, and the Mapped Diagnostic Context (MDC) feature is unavailable with async loggers because it relies on thread-local storage that does not transfer across the async boundary.

### When should I choose `_st` over `_mt` loggers?

Choose `_st` loggers only when you can guarantee that all calls originate from a single thread or when you implement external synchronization. The performance gain from avoiding mutex locks is measurable only in high-throughput, low-latency scenarios where logging occurs in a thread-confined context, such as dedicated worker threads with exclusive access to the logger instance.

### How do I check if my spdlog configuration is thread-safe?

Check your logger factory function names: functions ending in `_mt` create thread-safe loggers, while those ending in `_st` create single-threaded variants. Review the sink templates in your initialization code—`basic_file_sink_mt` is safe for sharing, whereas `basic_file_sink_st` requires exclusive access. The README documentation (lines 52-105) provides the authoritative reference for thread-safety guarantees.