How to Use spdlog File Event Handlers for Open/Close Callbacks
spdlog file event handlers allow you to register std::function callbacks that execute custom code immediately before or after a log file opens or closes, providing hooks into the file lifecycle via the file_event_handlers structure.
The gabime/spdlog library includes a lightweight mechanism for executing custom logic during log file lifecycle events. By implementing spdlog file event handlers, applications can inject monitoring, auditing, or resource management code that runs synchronously with file operations. This feature centers on a simple structure defined in include/spdlog/common.h that all file-based sinks support natively.
The file_event_handlers Structure
The core of this functionality is the file_event_handlers struct defined in include/spdlog/common.h (lines 330-341). It contains four optional std::function objects that represent distinct lifecycle hooks:
-
before_open–void(const filename_t &filename)
Invoked immediately before the file descriptor is opened. -
after_open–void(const filename_t &filename, std::FILE *file_stream)
Invoked after the file has been successfully opened, passing the active file stream pointer. -
before_close–void(const filename_t &filename, std::FILE *file_stream)
Invoked right before the file stream is closed. -
after_close–void(const filename_t &filename)
Invoked after the file descriptor has been closed and invalidated.
// include/spdlog/common.h
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;
};
All members initialize to nullptr, making them truly optional. When a function is assigned, the internal file management code invokes it at the appropriate moment.
How File Event Handlers Work Internally
All file-based sinks—including basic_file_sink, rotating_file_sink, and daily_file_sink—delegate low-level I/O operations to spdlog::details::file_helper. According to the spdlog source code, when a sink constructs its file_helper, it forwards the user-supplied file_event_handlers to the helper's constructor:
// From basic_file_sink-inl.h (constructor implementation)
file_helper_{event_handlers}
The file_helper class, declared in include/spdlog/details/file_helper.h and implemented in include/spdlog/details/file_helper-inl.h, stores these handlers and invokes them within its open() and close() methods.
In file_helper::open() (lines 21-41), the implementation checks and calls the handlers around the actual file opening logic:
// include/spdlog/details/file_helper-inl.h
void file_helper::open(const filename_t &fname, bool truncate) {
// ... validation logic ...
if (event_handlers_.before_open) {
event_handlers_.before_open(filename_);
}
// ... file opening via os::fopen_s ...
if (!os::fopen_s(&fd_, fname, mode)) {
if (event_handlers_.after_open) {
event_handlers_.after_open(filename_, fd_);
}
return;
}
// ... error handling ...
}
Similarly, the close() method (lines 84-95) wraps the std::fclose call with the before and after hooks:
// include/spdlog/details/file_helper-inl.h
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_);
}
}
}
Because every file sink uses file_helper for I/O, they automatically inherit support for these callbacks without requiring additional implementation code.
Implementing spdlog File Event Handlers in Practice
To use file event handlers, instantiate a file_event_handlers object, assign lambda functions or callable objects to the relevant members, and pass the structure to your chosen sink's constructor. The test suite in tests/test_custom_callbacks.cpp demonstrates this pattern:
// tests/test_custom_callbacks.cpp
spdlog::file_event_handlers handlers;
handlers.before_open = [](const spdlog::filename_t &f) {
std::cout << "About to open: " << f << '\n';
};
handlers.after_open = [](const spdlog::filename_t &f, std::FILE *) {
std::cout << "Opened successfully: " << f << '\n';
};
handlers.before_close = [](const spdlog::filename_t &f, std::FILE *) {
std::cout << "Closing: " << f << '\n';
};
handlers.after_close = [](const spdlog::filename_t &f) {
std::cout << "Closed: " << f << '\n';
};
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_st>(
"mylog.txt", false, handlers);
spdlog::logger logger("file_logger", {sink});
logger.info("Hello world");
spdlog::drop_all(); // triggers the close callbacks
When executed, this code prints diagnostic messages bracketing each file operation. The handlers can capture external variables, enabling complex interactions with application state, metrics systems, or security audit trails.
Common Use Cases for File Event Callbacks
spdlog file event handlers are particularly valuable in production environments requiring visibility into logging infrastructure:
-
Rotation Monitoring – Track exactly when
rotating_file_sinkordaily_file_sinkcloses one file and opens another, enabling you to trigger log shipping or archival processes immediately upon file close. -
External System Notifications – Notify monitoring services or watchdog processes when log files are created, ensuring downstream consumers can begin tailing new files without polling.
-
Security Auditing – Record precise timestamps and process IDs in a separate audit log whenever sensitive log files are opened, satisfying compliance requirements for access tracking.
-
Resource Management – Flush external buffers, sync filesystem caches, or update database records immediately before a file closes, ensuring data consistency across related subsystems.
Summary
- The
file_event_handlersstructure ininclude/spdlog/common.hdefines four optional callbacks:before_open,after_open,before_close, andafter_close. - All file-based sinks automatically support these handlers through the
file_helperclass implemented ininclude/spdlog/details/file_helper-inl.h. - Callbacks receive the filename and (for open/close operations) the active
std::FILE*stream pointer. - You register handlers by passing a configured
file_event_handlersobject to sink constructors such asbasic_file_sinkorrotating_file_sink. - This mechanism enables audit trails, external monitoring, and resource synchronization without modifying spdlog's internals.
Frequently Asked Questions
How do I register a callback for when a log file opens in spdlog?
Create a spdlog::file_event_handlers instance and assign a callable to either before_open or after_open, then pass the handlers object as the final argument to your file sink constructor. For example, when constructing a basic_file_sink_st, include the handlers as the third parameter: std::make_shared<spdlog::sinks::basic_file_sink_st>("log.txt", false, handlers).
Can I use file event handlers with rotating_file_sink?
Yes. The rotating_file_sink constructor accepts a file_event_handlers parameter and forwards it to the internal file_helper. This allows your callbacks to fire during rotation events—before_close and after_close trigger when the old file closes, while before_open and after_open fire when the new file is created.
What parameters do spdlog file event handlers receive?
The before_open and after_close handlers receive a single const filename_t &filename parameter. The after_open and before_close handlers receive both the filename and a std::FILE *file_stream pointer, allowing you to inspect or manipulate the open file descriptor (such as checking file size with ftell or manually flushing buffers).
Are file event handlers thread-safe in spdlog?
The handlers themselves are std::function objects stored within the file_helper, and spdlog invokes them synchronously during open() and close() operations. While spdlog's sink implementations protect these calls with mutexes where necessary, the thread safety of your callback code depends entirely on your implementation. Ensure any shared state accessed within your lambdas is properly synchronized, as callbacks may execute on the thread that triggers file rotation or logger destruction.
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 →