How to Enable and Manage Backtrace Logging in spdlog: A Complete Guide
spdlog enables backtrace logging through a thread-safe circular buffer that stores the last N debug and trace messages, allowing you to dump recent history when errors occur.
The spdlog library (GitHub: gabime/spdlog) provides a lightweight backtrace feature that helps diagnose issues by capturing debug context leading up to warnings or errors. This article explains how to enable and manage backtrace logging using the actual implementation details from the spdlog source code.
How spdlog Backtrace Logging Works
The backtrace system consists of three coordinated components that work together to capture, store, and emit recent log history.
Core Architecture
| Component | Source File | Responsibility |
|---|---|---|
details::backtracer |
include/spdlog/details/backtracer.h / backtracer-inl.h |
Maintains a thread-safe circular queue of log_msg_buffer objects; handles storage via push_back and retrieval via foreach_pop |
logger |
include/spdlog/logger.h |
Owns a details::backtracer tracer_ member; forwards messages during log_it_ and exposes public control methods |
registry |
include/spdlog/details/registry.h |
Manages global backtrace settings across all registered loggers |
When backtrace is enabled, the logger stores formatted messages in a fixed-size circular buffer. Once the buffer reaches capacity, new entries overwrite the oldest ones, ensuring you always retain the most recent activity.
Enabling Backtrace Logging in spdlog
You can enable backtrace logging either for individual loggers or globally across your application.
Per-Logger Configuration
To enable backtrace on a specific logger, call enable_backtrace() with the desired buffer size:
auto logger = spdlog::basic_logger_mt("my_logger", "app.log");
logger->enable_backtrace(10); // Store last 10 debug/trace messages
This invokes details::backtracer::enable(size_t) which initializes the circular queue:
std::lock_guard<std::mutex> lock{mutex_};
enabled_.store(true, std::memory_order_relaxed);
messages_ = circular_q<log_msg_buffer>{size};
Global Configuration
To enable backtrace on all existing and future loggers, use the global namespace functions defined in include/spdlog/spdlog.h:
spdlog::enable_backtrace(8); // Applies to all registered loggers
These global functions forward to spdlog::details::registry, which iterates through all registered loggers and invokes enable_backtrace() on each instance.
Recording Messages in the Backtrace Buffer
During each log call, logger::log_it_() checks tracer_.enabled(). If true, the message is passed to tracer_.push_back(msg):
std::lock_guard<std::mutex> lock{mutex_};
messages_.push_back(log_msg_buffer{msg});
The implementation only stores messages that pass through the logger's formatting pipeline. Because the buffer uses a circular queue structure, once capacity is reached, the oldest log_msg_buffer is automatically discarded to make room for new entries. This design ensures constant memory usage regardless of runtime duration.
Dumping the Backtrace
When an error condition occurs, trigger the backtrace dump using dump_backtrace():
logger->dump_backtrace(); // Emits buffered messages to sinks
The logger::dump_backtrace_() method (defined in logger.h) handles the emission sequence:
void logger::dump_backtrace_() {
if (!tracer_.empty()) {
// Header
log_msg header{source_loc{}, name_, level::info,
"****************** Backtrace Start ******************"};
sink_it_(header);
// Emit stored messages in FIFO order
tracer_.foreach_pop([this](const details::log_msg& msg) { sink_it_(msg); });
// Footer
log_msg footer{source_loc{}, name_, level::info,
"****************** Backtrace End ********************"};
sink_it_(footer);
}
}
The foreach_pop method empties the queue while iterating, passing each stored message to sink_it_() for normal sink processing. This ensures backtrace entries respect your configured formatting patterns and sink destinations.
Thread Safety Considerations
All backtrace operations in details::backtracer acquire mutex_ through std::lock_guard, guaranteeing safe concurrent access from multiple threads. The enabled() check in logger::log_it_ uses std::memory_order_relaxed to minimize overhead when backtrace is disabled, ensuring the feature adds negligible cost to hot paths when not in use.
Practical Code Examples
Basic Per-Logger Backtrace
This example captures debug history for a specific file logger:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
int main() {
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log", true);
spdlog::logger logger("my_logger", {file_sink});
logger.set_level(spdlog::level::debug);
logger.enable_backtrace(5); // Keep last 5 debug/trace messages
logger.info("Application start");
for (int i = 0; i < 10; ++i)
logger.debug("debug {}", i); // Only the last 5 are stored
// Something goes wrong – dump the recent debug history
logger.dump_backtrace();
}
Global Backtrace Configuration
Use this pattern to capture backtraces across your entire application:
#include <spdlog/spdlog.h>
int main() {
spdlog::set_level(spdlog::level::debug);
spdlog::enable_backtrace(8); // Every logger now records 8 messages
auto log = spdlog::default_logger();
log->info("startup");
for (int i = 0; i < 20; ++i)
log->debug("step {}", i);
// Later, perhaps from a signal handler:
spdlog::dump_backtrace(); // Prints backtrace for each registered logger
}
Asynchronous Logger Backtrace
Backtrace works seamlessly with spdlog's async loggers:
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/stdout_color_sinks.h>
int main() {
spdlog::init_thread_pool(8192, 1);
auto async_logger = std::make_shared<spdlog::async_logger>(
"async", spdlog::sinks::stdout_color_sink_mt::instance(),
spdlog::thread_pool(), spdlog::async_overflow_policy::block);
async_logger->enable_backtrace(6);
async_logger->set_level(spdlog::level::debug);
async_logger->info("begin async work");
for (int i = 0; i < 12; ++i)
async_logger->debug("async debug {}", i);
async_logger->dump_backtrace(); // Emitted via the async sink
}
Key Implementation Files
Understanding these source files helps when customizing or debugging backtrace behavior:
include/spdlog/details/backtracer.h– Declares thebacktracerclass with the circular buffer and mutexinclude/spdlog/details/backtracer-inl.h– Contains inline implementations ofenable(),disable(),push_back(), andforeach_pop()include/spdlog/logger.h– Defines thetracer_member anddump_backtrace_()methodinclude/spdlog/spdlog.h– Exposes global API functions that forward to the registryinclude/spdlog/details/registry.h– Propagates backtrace settings to all registered loggers
Summary
- spdlog backtrace logging uses a fixed-size circular buffer to store recent debug and trace messages without unbounded memory growth
- Enable per-logger with
logger.enable_backtrace(N)or globally viaspdlog::enable_backtrace(N) - The
details::backtracerclass inbacktracer.hprovides thread-safe storage usingmutex_andcircular_q<log_msg_buffer> - Dump stored history anytime using
dump_backtrace(), which emits messages through standard sinks with header/footer delimiters - The feature adds minimal overhead when disabled due to relaxed memory ordering checks in the hot path
- Works with both synchronous and asynchronous loggers (
async_logger)
Frequently Asked Questions
What log levels does spdlog backtrace capture?
spdlog backtrace specifically captures debug and trace level messages. When logger::log_it_() processes these levels and backtrace is enabled, it calls tracer_.push_back() to store the message. Higher levels (info, warning, error) trigger the log output but are not stored in the backtrace buffer unless they pass through during active backtrace dumping.
Is spdlog backtrace logging thread-safe?
Yes, all backtrace operations are thread-safe. The details::backtracer class protects its internal circular_q<log_msg_buffer> with std::mutex. Every operation—including enable(), push_back(), and foreach_pop()—acquires std::lock_guard<std::mutex> lock{mutex_} before accessing shared state, making it safe to log from multiple threads concurrently while backtrace is active.
How do I disable backtrace logging after enabling it?
Call disable_backtrace() on the logger instance or spdlog::disable_backtrace() globally. This sets the internal enabled_ atomic flag to false via backtracer::disable(), preventing further messages from being stored in the circular buffer. Existing buffered messages remain until cleared by dump_backtrace() or clear().
Can I adjust the backtrace buffer size at runtime?
Yes, you can change the buffer size by calling enable_backtrace() again with a new size. According to the implementation in backtracer-inl.h, calling enable(size_t) reinitializes the messages_ queue with the new capacity, effectively resizing the buffer. However, this clears any existing stored messages since it creates a new circular_q<log_msg_buffer> instance.
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 →