# How to Configure spdlog File Event Handlers for Open/Close Callbacks

> Learn how to configure spdlog file event handlers with before open and after close callbacks. Enhance your logging by registering custom functions for file operations.

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

---

**Use `spdlog::file_event_handlers` to register four optional callbacks (`before_open`, `after_open`, `before_close`, `after_close`) and pass the struct to any file-based sink constructor such as `basic_file_sink` or `rotating_file_sink`.**

The spdlog library (gabime/spdlog) provides a clean, non-intrusive mechanism for executing custom code whenever log files are opened or closed. This feature centers on the `file_event_handlers` structure defined in [`include/spdlog/common.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/common.h), which plugs into all file-based sinks through the internal `file_helper` class.

## Understanding the file_event_handlers Structure

The `file_event_handlers` struct contains four `std::function` members, all defaulting to `nullptr`. As implemented in [`include/spdlog/common.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/common.h) (lines 330-341):

```cpp
struct file_event_handlers {
    file_event_handlers()
        : before_open(nullptr), after_open(nullptr),
          before_close(nullptr), after_close(nullptr) {}

    std::function<void(const filename_t &filename)>               before_open;
    std::function<void(const filename_t &filename,
                       std::FILE *file_stream)>                after_open;
    std::function<void(const filename_t &filename,
                       std::FILE *file_stream)>                before_close;
    std::function<void(const filename_t &filename)>               after_close;
};

```

Each callback fires at a specific point in the file lifecycle:

- **`before_open`** — invoked immediately before `fopen()` is called
- **`after_open`** — invoked after successful file opening, receiving the `FILE*` stream
- **`before_close`** — invoked before `fclose()`, with access to the active stream
- **`after_close`** — invoked after the file descriptor is released

## How Callbacks Are Triggered

All file-based sinks delegate I/O operations to `spdlog::details::file_helper`. When constructing a sink, the handlers are forwarded to this helper. The invocation logic lives in [`include/spdlog/details/file_helper-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/file_helper-inl.h).

The `open()` method (lines 21-41) shows the before/after open sequence:

```cpp
void file_helper::open(const filename_t &fname, bool truncate) {
    // ... path calculations ...
    if (event_handlers_.before_open) {
        event_handlers_.before_open(filename_);
    }
    // ... mode selection ...
    if (!os::fopen_s(&fd_, fname, mode)) {
        if (event_handlers_.after_open) {
            event_handlers_.after_open(filename_, fd_);
        }
        return;
    }
    // ... error handling ...
}

```

The `close()` method (lines 84-95) handles the before/after close sequence:

```cpp
void file_helper::close() {
    if (fd_ != nullptr) {
        if (event_handlers_.before_close) {
            event_handlers_.before_close(filename_, fd_);
        }
        std::fclose(fd_);
        fd_ = nullptr;
        if (event_handlers_.after_close) {
            event_handlers_.after_close(filename_);
        }
    }
}

```

This design means **every file-based sink automatically supports callbacks** without additional implementation work.

## Complete Working Example

Below is a minimal, compilable example adapted from the official test suite in [`tests/test_custom_callbacks.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_custom_callbacks.cpp) (lines 11-27):

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <iostream>

int main() {
    spdlog::file_event_handlers handlers;
    
    handlers.before_open = [](const spdlog::filename_t &f) {
        std::cout << "[callback] About to open: " << f << '\n';
    };
    
    handlers.after_open = [](const spdlog::filename_t &f, std::FILE *) {
        std::cout << "[callback] Opened successfully: " << f << '\n';
    };
    
    handlers.before_close = [](const spdlog::filename_t &f, std::FILE *) {
        std::cout << "[callback] Closing: " << f << '\n';
    };
    
    handlers.after_close = [](const spdlog::filename_t &f) {
        std::cout << "[callback] Closed: " << f << '\n';
    };

    auto sink = std::make_shared<spdlog::sinks::basic_file_sink_st>(
        "mylog.txt",    // filename
        false,          // truncate = false
        handlers        // event handlers
    );
    
    spdlog::logger logger("file_logger", {sink});
    logger.info("Hello world");
    
    spdlog::drop_all();  // triggers before_close and after_close callbacks
    
    return 0;
}

```

Expected output:

```

[callback] About to open: mylog.txt
[callback] Opened successfully: mylog.txt
[callback] Closing: mylog.txt
[callback] Closed: mylog.txt

```

## Supported Sink Types

The `file_event_handlers` parameter is accepted by all sinks that internally use `file_helper`:

| Sink | Header File | Use Case |
|------|-------------|----------|
| `basic_file_sink_st` / `basic_file_sink_mt` | [`spdlog/sinks/basic_file_sink.h`](https://github.com/gabime/spdlog/blob/main/spdlog/sinks/basic_file_sink.h) | Single static log file |
| `rotating_file_sink_st` / `rotating_file_sink_mt` | [`spdlog/sinks/rotating_file_sink.h`](https://github.com/gabime/spdlog/blob/main/spdlog/sinks/rotating_file_sink.h) | Size-based rotation |
| `daily_file_sink_st` / `daily_file_sink_mt` | [`spdlog/sinks/daily_file_sink.h`](https://github.com/gabime/spdlog/blob/main/spdlog/sinks/daily_file_sink.h) | Time-based rotation |
| `hybrid_file_sink` | [`spdlog/sinks/hybrid_file_sink.h`](https://github.com/gabime/spdlog/blob/main/spdlog/sinks/hybrid_file_sink.h) | Combined size and time rotation |

Constructor signatures vary slightly, but all expose a final `file_event_handlers` parameter with a default value.

## Practical Use Cases

Configure spdlog file event handlers when you need to:

- **Monitor log rotation** — track exactly when `rotating_file_sink` closes one file and opens another
- **Integrate with external systems** — notify a metrics collector or orchestrator that a new log file is available
- **Enforce security policies** — audit file access by logging open operations with process ID and timestamp
- **Coordinate resource cleanup** — flush external buffers or release related locks before the file closes
- **Implement custom naming schemes** — validate or transform the filename in `before_open` before the open proceeds

## Key Source Files Reference

| File | Purpose |
|------|---------|
| [`include/spdlog/common.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/common.h) | Defines `file_event_handlers` structure |
| [`include/spdlog/details/file_helper.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/file_helper.h) | Declares `file_helper` class |
| [`include/spdlog/details/file_helper-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/file_helper-inl.h) | Implements callback invocation in `open()` and `close()` |
| [`include/spdlog/sinks/basic_file_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/basic_file_sink.h) | Basic file sink with handlers support |
| [`include/spdlog/sinks/rotating_file_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/rotating_file_sink.h) | Rotating sink forwarding handlers |
| [`tests/test_custom_callbacks.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_custom_callbacks.cpp) | Official test demonstrating usage |

## Summary

- **`file_event_handlers`** is a simple struct with four optional `std::function` callbacks
- Pass it as the final argument to any file-based sink constructor
- Callbacks fire automatically through `file_helper::open()` and `file_helper::close()`
- The `FILE*` parameter in `after_open` and `before_close` enables advanced operations like `fstat()` or custom buffering
- No runtime overhead when callbacks are left as `nullptr` (default)

## Frequently Asked Questions

### Can I use file event handlers with asynchronous logging?

Yes. The handlers execute synchronously on the thread that performs the file operation—either the calling thread or the background async worker. No special async configuration is required.

### What happens if a callback throws an exception?

The spdlog source code does not wrap callback invocations in try-catch blocks. An exception will propagate through `file_helper::open()` or `close()` and should be handled by your application. Keep callbacks lightweight and exception-safe.

### Can I modify the filename inside before_open?

The `filename` parameter is passed by `const` reference; you cannot change it for the current operation. However, you can implement custom naming logic before constructing the sink, or use the callback to validate paths and throw if requirements are not met.

### Are callbacks invoked for every rotation in rotating_file_sink?

Yes. Each rotation triggers a close of the old file (invoking `before_close` and `after_close`) followed by an open of the new file (invoking `before_open` and `after_open`). This makes handlers ideal for tracking rotation events precisely.