spdlog _st vs _mt Loggers: Thread Safety Explained
spdlog _st (single-threaded) loggers eliminate mutex overhead for maximum throughput in thread-confined scenarios, while _mt (multi-threaded) loggers provide built-in synchronization that is safe for concurrent access from multiple threads.
The gabime/spdlog library provides two distinct logger factory families distinguished by _st and _mt suffixes, allowing developers to explicitly choose between raw performance and thread safety. Understanding spdlog thread safety is critical when designing concurrent logging architectures, as selecting the wrong variant can lead to data races or unnecessary synchronization overhead.
Understanding spdlog Thread Safety Models
The distinction between single-threaded and multi-threaded loggers is reflected directly in the API naming convention and determines whether internal state is protected during concurrent access.
Single-Threaded (_st) Loggers
_st loggers are optimized for scenarios where the caller guarantees that all logging operations originate from a single thread or that external synchronization is provided. In include/spdlog/logger.h, the core logger class defines the interface, while include/spdlog/logger-inl.h contains inline implementations that bypass mutex acquisition for _st variants.
Key characteristics:
- No internal synchronization – removes mutex overhead on every log call
- Not thread-safe – calling from multiple threads simultaneously results in undefined behavior
- Higher throughput – ideal for performance-critical, thread-confined components
Multi-Threaded (_mt) Loggers
_mt loggers use a mutex to protect shared state, making them the default choice for general-purpose logging. According to the source in include/spdlog/logger-inl.h, these implementations acquire locks to ensure that sink lists, formatting buffers, and internal data structures remain consistent during concurrent modifications.
Key characteristics:
- Mutex-protected – prevents message interleaving and data corruption
- Thread-safe – safe to call from any thread without external synchronization
- Default recommendation – the README explicitly warns: "only use if all your loggers are thread-safe (
_mtloggers)"
Performance vs. Safety Trade-offs
The primary reason for maintaining two variants is performance. The lock-free path of a single-threaded logger removes the overhead of mutex contention, yielding measurable speedups when logging from a dedicated worker thread. However, when several threads share a logger, the _mt variant guarantees that log messages are not interleaved and that internal structures remain valid.
Use an _mt logger unless you are absolutely sure that every log call originates from a single thread or you have implemented external synchronization. The performance penalty of _mt is modest compared to the cost of data races in multi-threaded environments.
Creating spdlog _st and _mt Loggers
Factory functions append the suffix to indicate the thread-safety level. The underlying sink type determines the actual synchronization behavior.
Multi-Threaded Logger Example
Use stdout_color_mt or basic_logger_mt for thread-safe console and file logging:
#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");
// Thread-safe file logger
auto file_logger = spdlog::basic_logger_mt("file_logger", "app.log");
file_logger->info("Safe for concurrent threads");
}
Single-Threaded Logger Example
Use basic_logger_st when you control thread usage and need maximum speed:
#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");
}
Async Loggers and Thread Safety
Asynchronous loggers are built on top of the multi-threaded infrastructure but delegate actual message writing to a background thread pool. As defined in include/spdlog/async_logger.h, the async_logger itself is thread-safe at the API level, while the underlying sink (e.g., basic_file_sink_mt) follows the same _mt-vs-_st rule.
Note that the Mapped Diagnostic Context (MDC) currently works only with synchronous loggers because it relies on thread-local storage mechanisms that are incompatible with the async dispatch model.
#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");
}
When to Choose _st vs _mt Loggers
- Choose
_mtfor any logger that might be accessed from multiple threads, including global loggers or shared components. This is the safest default. - Choose
_stonly when you deliberately instantiate a logger in a performance-critical, thread-confined component (e.g., a dedicated worker thread with no shared access) and need to minimize latency.
The README.md emphasizes that mixing _st loggers in multi-threaded contexts violates thread safety guarantees and can cause crashes or corrupted log output.
Summary
_mtloggers use internal mutex protection to ensure thread safety across concurrent calls, making them the appropriate default for most applications._stloggers provide higher performance by eliminating synchronization overhead but are unsafe for use across multiple threads.- Async loggers depend on the underlying sink's thread-safety type (
_mtor_st) and offload I/O to a background thread pool while maintaining thread-safe APIs. - MDC features are restricted to synchronous loggers due to thread-local storage dependencies.
Frequently Asked Questions
What is the difference between spdlog _st and _mt?
_st (single-threaded) loggers lack internal mutex protection and are optimized for exclusive single-threaded use, while _mt (multi-threaded) loggers use locks to safely handle concurrent access from multiple threads. The _mt suffix appears in factory functions like stdout_color_mt, whereas _st appears in functions like basic_logger_st.
Is spdlog thread-safe by default?
Yes, if you use _mt factories. The library provides both thread-safe and non-thread-safe variants, but you must explicitly choose the _mt suffix to obtain a thread-safe logger. The README warns against mixing _st loggers in multi-threaded applications.
Can I use _st loggers in a multi-threaded application?
Only if you provide external synchronization. While an _st logger can exist within a multi-threaded process, calling its logging methods from multiple threads simultaneously without external locking causes data races. Restrict _st loggers to thread-confined scopes or protect access with your own mutex.
Do async loggers require _mt sinks?
Async loggers work with both types, but _mt sinks are recommended for thread safety. The async_logger class handles thread-safe message queuing, but the underlying sink still follows the _mt/_st contract. If multiple async loggers share a sink, that sink must be _mt to prevent corruption during file writes.
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 →