How to Configure spdlog File Event Handlers for Open/Close Callbacks
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, 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 (lines 330-341):
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 beforefopen()is calledafter_open— invoked after successful file opening, receiving theFILE*streambefore_close— invoked beforefclose(), with access to the active streamafter_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.
The open() method (lines 21-41) shows the before/after open sequence:
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:
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 (lines 11-27):
#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 |
Single static log file |
rotating_file_sink_st / rotating_file_sink_mt |
spdlog/sinks/rotating_file_sink.h |
Size-based rotation |
daily_file_sink_st / daily_file_sink_mt |
spdlog/sinks/daily_file_sink.h |
Time-based rotation |
hybrid_file_sink |
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_sinkcloses 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_openbefore the open proceeds
Key Source Files Reference
| File | Purpose |
|---|---|
include/spdlog/common.h |
Defines file_event_handlers structure |
include/spdlog/details/file_helper.h |
Declares file_helper class |
include/spdlog/details/file_helper-inl.h |
Implements callback invocation in open() and close() |
include/spdlog/sinks/basic_file_sink.h |
Basic file sink with handlers support |
include/spdlog/sinks/rotating_file_sink.h |
Rotating sink forwarding handlers |
tests/test_custom_callbacks.cpp |
Official test demonstrating usage |
Summary
file_event_handlersis a simple struct with four optionalstd::functioncallbacks- Pass it as the final argument to any file-based sink constructor
- Callbacks fire automatically through
file_helper::open()andfile_helper::close() - The
FILE*parameter inafter_openandbefore_closeenables advanced operations likefstat()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.
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 →