How to Use enable_backtrace and dump_backtrace in spdlog
spdlog's backtrace facility stores the last N log messages in a circular buffer and replays them through dump_backtrace() to help diagnose the state leading up to an error.
The gabime/spdlog library provides a lightweight, thread-safe mechanism for capturing recent log history without impacting runtime performance. By using enable_backtrace and dump_backtrace in spdlog, you can maintain a sliding window of formatted log messages that can be emitted on demand when critical errors occur, making debugging production issues significantly easier.
How spdlog Backtracing Works
The backtrace system operates as a circular message buffer attached to individual loggers or the global registry. When enabled, every log call pushes a copy of the formatted log_msg into a circular_q<log_msg_buffer> before the message is dispatched to sinks. This capture happens in include/spdlog/details/backtracer.h, where the details::backtracer class manages an std::atomic<bool> enabled_ flag and protects the queue with an std::mutex for thread safety.
The architecture separates global convenience functions from per-logger control:
- Global API (
include/spdlog/spdlog.handspdlog-inl.h): Forwards calls todetails::registry::instance(), propagating settings to all existing loggers. - Per-Logger API (
include/spdlog/logger.handlogger-inl.h): Eachloggerinstance owns adetails::backtracer tracer_member for isolated message capture.
Enabling Backtraces Globally vs Per-Logger
You can activate backtracing across your entire application or limit it to specific loggers that handle critical subsystems.
Global Registry Configuration
Use the global helpers when you want uniform backtrace behavior across all loggers. According to include/spdlog/spdlog-inl.h, these functions forward to the registry which iterates through all registered loggers:
#include <spdlog/spdlog.h>
// Store the last 32 messages in every logger's circular buffer
spdlog::enable_backtrace(32);
// Later, dump the backtrace using the default logger
spdlog::dump_backtrace();
// Disable and clear all backtrace buffers globally
spdlog::disable_backtrace();
Per-Logger Configuration
For finer control, enable backtracing on individual logger instances. This is defined in include/spdlog/logger-inl.h and isolates the buffer to that specific logger's sinks:
auto logger = spdlog::stdout_color_mt("critical_component");
logger->enable_backtrace(10); // Keep only the last 10 messages for this component
// When an error is detected in this subsystem
logger->dump_backtrace();
logger->disable_backtrace();
Implementation Details of the Backtracer Class
The underlying mechanism resides in include/spdlog/details/backtracer.h and its implementation in backtracer-inl.h. The backtracer class template uses a fixed-size circular queue that overwrites old messages when full (FIFO order).
Key implementation aspects:
- Message Storage: The
push_back(const log_msg&)method stores alog_msg_buffer(a copy of the formatted message) to ensure the data persists even if the original log context changes. - Thread Safety: An
std::mutexguards the circular queue during push operations and when iterating viaforeach_pop. - Dumping: The
foreach_popmethod iterates over stored messages in order, invoking a callback that writes them to the logger's sinks. This is called internally bylogger::dump_backtrace().
Practical Code Examples
Basic Global Backtrace Workflow
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
int main() {
auto file_logger = spdlog::basic_logger_mt("file_logger", "app.log");
// Enable global backtrace of last 10 messages
spdlog::enable_backtrace(10);
file_logger->info("Application started");
file_logger->debug("Config loaded: {}", 42);
file_logger->warn("Resource threshold at 80%");
// Simulate error detection
try {
throw std::runtime_error("Critical failure");
} catch (...) {
// Dump the last 10 messages to see what led to the error
spdlog::dump_backtrace(); // Writes to default logger's sinks
}
spdlog::disable_backtrace();
}
Isolated Component Backtrace
auto network_logger = spdlog::stdout_color_mt("network");
network_logger->enable_backtrace(5);
// Simulated network operations
network_logger->info("Connecting to host...");
network_logger->debug("Sent packet: {}", 1024);
network_logger->error("Connection timeout");
// Dump only this logger's history to diagnose the network issue
network_logger->dump_backtrace(); // Prints the 5 most recent lines
network_logger->disable_backtrace();
Summary
- Global vs Local: Use
spdlog::enable_backtrace()inspdlog.hto capture across all loggers, orlogger->enable_backtrace()for isolated component tracking. - Dumping: Call
dump_backtrace()on the default logger viaspdlog::dump_backtrace()or on specific logger instances to replay buffered messages to their sinks. - Implementation: The
details::backtracerclass inbacktracer.huses a thread-safecircular_q<log_msg_buffer>managed bystd::mutexto store formatted message copies. - Cleanup: Invoke
disable_backtrace()to atomically disable tracing and clear the circular buffer without dumping contents.
Frequently Asked Questions
What is the performance impact of enabling backtraces in spdlog?
The overhead is minimal but non-zero. Each log call performs an atomic check on enabled_, locks a mutex, and pushes a copy of the formatted message into the circular queue. For high-frequency logging (millions of messages per second), consider using per-logger backtraces only on critical subsystems rather than the global registry.
How do I clear the backtrace buffer without dumping the messages?
Call disable_backtrace() on the logger or the global registry. In include/spdlog/details/backtracer-inl.h, the disable() method clears the circular_q and sets the atomic flag to false, effectively dropping all stored messages without writing them to sinks.
Is the spdlog backtrace buffer thread-safe?
Yes. The details::backtracer class uses an std::atomic<bool> for the enabled state and an std::mutex to protect the circular_q<log_msg_buffer> during concurrent push and pop operations. Multiple threads can log simultaneously while the backtrace is enabled.
What happens when the backtrace buffer reaches its capacity?
The circular queue overwrites the oldest messages in FIFO order. When push_back() is called on a full buffer, the implementation in backtracer-inl.h replaces the oldest entry, maintaining exactly the last N messages specified in the enable_backtrace(size_t n_messages) call.
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 →