# How spdlog's Error Handler Mechanism Works and How to Customize It

> Discover how spdlog's error handler works and learn to customize it with callbacks. Set global or per-logger handlers for robust error management in your C++ applications.

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

---

**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::logger` instance can have its own handler via `logger::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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h) | Declares `spdlog::set_error_handler()` global API at [lines 90-92](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/spdlog.h#L90-L92) |
| [`include/spdlog/spdlog-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog-inl.h) | Forwards global calls to the registry at [lines 54-56](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/spdlog-inl.h#L54-L56) |
| [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) | Declares per-logger `set_error_handler()` at [lines 310-311](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/logger.h#L310-L311) |
| [`include/spdlog/logger-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger-inl.h) | Stores handler in logger instance at [lines 112-114](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/logger-inl.h#L112-L114) |
| [`include/spdlog/details/registry.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry.h) | Holds global handler interface at [lines 79-82](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/details/registry.h#L79-L82) |
| [`include/spdlog/details/registry-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry-inl.h) | Propagates handler to new loggers at [lines 165-168](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/details/registry-inl.h#L165-L168) |

### Error Handling Flow

1. **Initialization** – The registry creates a default handler writing to `stderr`
2. **Logging call** – `logger::log()` wraps core work in `try/catch`
3. **Exception caught** – Any exception builds a descriptive `msg` string
4. **Handler invoked** – Per-logger handler takes priority; otherwise global handler runs
5. **User code executes** – Your handler decides: log elsewhere, abort, throw, or ignore

The handler signature is intentionally simple:

```cpp
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.

```cpp
#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.

```cpp
#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`](https://github.com/gabime/spdlog/blob/main/logger-inl.h):

1. Check if logger has custom handler
2. If yes, call it
3. 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:

```cpp
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:

```cpp
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

```cpp
#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

```cpp
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 with **`logger::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 **`stderr`** and 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.