How spdlog's Error Handler Mechanism Works and How to Customize It
spdlog uses a callback-based error handling system where exceptions during logging are caught and forwarded to a user-configurable handler function, which can be set globally via spdlog::set_error_handler() or per-logger via logger::set_error_handler().
The spdlog library provides a robust, extensible error handling mechanism that prevents logging failures from crashing your application while giving you full control over how errors are reported. This article explains how the mechanism works internally in the gabime/spdlog repository and demonstrates how to implement custom handlers for production services, libraries, and debugging scenarios.
How the Error Handler Mechanism Works
spdlog protects every logging call with a try/catch wrapper inside logger::log(). When an internal operation fails—whether due to a sink write failure, formatting exception, or other error—the logger catches the exception and forwards a descriptive message to a handler function.
The mechanism supports two levels of handler configuration:
- Global handler – set once for the entire process via
spdlog::set_error_handler - Per-logger handler – each
spdlog::loggerinstance can have its own handler vialogger::set_error_handler
Both handlers are stored in spdlog::details::registry. The default global handler simply writes error messages to stderr, allowing the application to continue running.
Where the Code Lives
| File | Responsibility |
|---|---|
include/spdlog/spdlog.h |
Declares spdlog::set_error_handler() global API at lines 90-92 |
include/spdlog/spdlog-inl.h |
Forwards global calls to the registry at lines 54-56 |
include/spdlog/logger.h |
Declares per-logger set_error_handler() at lines 310-311 |
include/spdlog/logger-inl.h |
Stores handler in logger instance at lines 112-114 |
include/spdlog/details/registry.h |
Holds global handler interface at lines 79-82 |
include/spdlog/details/registry-inl.h |
Propagates handler to new loggers at lines 165-168 |
Error Handling Flow
- Initialization – The registry creates a default handler writing to
stderr - Logging call –
logger::log()wraps core work intry/catch - Exception caught – Any exception builds a descriptive
msgstring - Handler invoked – Per-logger handler takes priority; otherwise global handler runs
- User code executes – Your handler decides: log elsewhere, abort, throw, or ignore
The handler signature is intentionally simple:
void handler(const std::string &msg);
This accepts any callable: free functions, lambdas, functors, or std::function wrappers.
Setting a Global Error Handler
Use spdlog::set_error_handler() to establish one handler for all loggers in your process. This is the recommended approach for applications with uniform error handling needs.
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
int main() {
// Redirect all spdlog internal errors to a dedicated error logger
spdlog::set_error_handler([](const std::string &msg) {
auto err_log = spdlog::stderr_color_mt("error_logger");
err_log->error("*** SPDLOG INTERNAL ERROR ***: {}", msg);
// Production option: abort on any logging failure
// std::abort();
});
auto logger = spdlog::stdout_color_mt("app");
logger->info("Normal logging proceeds");
// Any internal failure now triggers the lambda above
}
According to the spdlog source code, the global handler is stored in the registry and automatically attached to all newly created loggers.
Setting a Per-Logger Error Handler
Use logger::set_error_handler() when specific loggers need distinct error behavior. This overrides the global handler for that logger only.
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <stdexcept>
// Custom exception type for logging failures
struct logging_error : std::runtime_error {
using std::runtime_error::runtime_error;
};
int main() {
auto file_logger = spdlog::basic_logger_mt("file", "app.log");
// This logger throws on internal errors
file_logger->set_error_handler([](const std::string &msg) {
throw logging_error("Logging failed: " + msg);
});
try {
file_logger->info("This works normally");
// Simulate failure condition (e.g., disk full, permissions error)
// The handler above converts spdlog's internal error to your exception
}
catch (const logging_error &e) {
std::cerr << "Caught custom error: " << e.what() << std::endl;
// Application decides: retry, degrade, or terminate
}
}
Important: When a logger has its own handler, the global handler is not invoked. This isolation lets you mix generic fallback behavior with specialized handling for critical paths.
Handler Priority and Interaction
The spdlog error handler mechanism follows a simple priority rule implemented in logger-inl.h:
- Check if logger has custom handler
- If yes, call it
- If no, call global handler from registry
This design supports flexible configurations:
| Configuration | Behavior |
|---|---|
| Global handler only | All loggers use the same error handling |
| Per-logger handler only | Specific logger uses custom handling; others use default stderr |
| Both set | Logger with custom handler uses it; others use global |
| Neither set | All loggers write errors to stderr via default handler |
Common Custom Handler Patterns
Production Service: Abort on Failure
For services where logging integrity is critical, install a global handler that terminates the process after capturing diagnostic information:
spdlog::set_error_handler([](const std::string &msg) {
// Write to emergency sink that's guaranteed to work (e.g., syslog)
syslog(LOG_EMERG, "spdlog fatal error: %s", msg.c_str());
// Prevent continued operation with corrupted log state
std::abort();
});
Library Development: Surface Errors to Users
Libraries should use per-logger handlers to throw exceptions that calling code can catch and handle appropriately:
class MyLibrary {
std::shared_ptr<spdlog::logger> log_;
public:
MyLibrary() {
log_ = spdlog::stdout_color_mt("mylib");
log_->set_error_handler([](const std::string &msg) {
throw LibraryException("Internal logging error: " + msg);
});
}
};
Debug Builds: Capture Full Diagnostics
#ifdef DEBUG
spdlog::set_error_handler([](const std::string &msg) {
std::cerr << "SPDLOG ERROR: " << msg << std::endl;
spdlog::dump_backtrace(); // Requires SPDLOG_ENABLE_BACKTRACE
// Continue execution to allow debugging
});
#endif
Multi-Process Safe: Dedicated Error File
std::mutex error_mutex;
auto error_file = std::make_shared<spdlog::sinks::basic_file_sink_mt>("errors.log", true);
spdlog::set_error_handler([&](const std::string &msg) {
std::lock_guard<std::mutex> lock(error_mutex);
error_file->log(spdlog::details::log_msg{
spdlog::source_loc{},
"spdlog",
spdlog::level::err,
msg
});
});
Thread Safety Considerations
The error handler mechanism is thread-safe. The registry uses internal synchronization when setting or retrieving the global handler. Per-logger handlers are stored as std::function members and accessed under the logger's mutex during log operations.
However, your handler implementation must be thread-safe if multiple threads may trigger logging errors concurrently. Use locks, atomic operations, or thread-safe sinks as appropriate.
Summary
- spdlog's error handler mechanism is a callback-based system that catches exceptions during logging and forwards them to user-configurable handlers
- Set handlers globally with
spdlog::set_error_handler()or per-logger withlogger::set_error_handler() - The registry stores the global handler and automatically attaches it to new loggers
- Per-logger handlers override global handlers when both are present
- Default behavior writes errors to
stderrand continues execution - Handler signature is
void(const std::string &msg), accepting any callable matching this type
Frequently Asked Questions
What happens if my error handler throws an exception?
spdlog catches exceptions in your handler and falls back to writing to stderr to prevent infinite recursion. Design your handler to not throw, or accept that secondary failures go to stderr.
Can I use the same handler for multiple loggers?
Yes. Handlers are stored as std::function objects, so you can assign the same callable to multiple loggers or share a std::function instance.
How do I restore the default error handler?
Set a handler that writes to stderr, or pass an empty lambda. There is no explicit "reset to default" API in the current spdlog version.
Does the error handler affect normal log message delivery?
No. The error handler only processes internal spdlog failures—sink write errors, formatting exceptions, and similar issues. Successfully formatted and delivered log messages never trigger the error handler.
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 →