I need to create a logger facility that outputs from different places of code to the same or different files depending on what the user provides. It should recreate a file for logging if it is not opened. But it must append to an already opened file.
This naive way such as
std::ofstream f1(“log”);
f1 << "1 from f1\n";
std::ofstream f2(“log”);
f2 << "1 from f2\n";
f1 << "2 from f1\n";
steals stream and recreates the file. Log contains
1 from f2
With append, it will reuse file, but the second open steals stream from f1.
Log contains
1 from f1
1 from f2
Try to guess which files will be used and open them all in the very begging will work but may create a lot of files that are not actually used.
Open for append and closing on each logging call would be an almost working solution, but it seems to be a slow solution due to a lot of system calls and flushing on each logging action.
I’m going to create a static table of opened files, hoping that std::filesystem::canonical
will work in all of my cases. But as far as I understand such a table should already exist somewhere in the process.
I've read that in Fortran people can check if a file was opened using inquire.
Check whether file has been opened already
But that answer did not give me any insight on how to achieve the same with С/C++.
Update
A scratch of the logger with a "static" table of open logs can look like
//hpp
class Logger {
static std::mutex _mutex;
static std::unordered_map<std::string, std::ofstream> _openFiles;
std::ostream& _appender;
std::ostream& _createAppender(const std::filesystem::path& logPath);
public:
Logger(const std::filesystem::path& logPath):
_appender(_createAppender(logPath)) {
}
template<class... Args>
void log(const Args&... args) const {
std::scoped_lock<std::mutex> lock(_mutex);
(_appender << ... << args);
}
};
//cpp
#include "Logger.hpp"
std::mutex Logger::_mutex;
std::unordered_map<std::string, std::ofstream> Logger::_openFiles;
std::ostream& Logger::_createAppender(const std::filesystem::path& logPath) {
if (logPath.empty()) return std::cout;
const auto truePath{std::filesystem::weakly_canonical(logPath).string()};
std::scoped_lock<std::mutex> lock(_mutex);
const auto entry{_openFiles.find(truePath)};
if (entry != _openFiles.end()) return entry->second;
_openFiles.emplace(truePath, logPath);
std::ostream& stream{_openFiles[truePath]};
stream.exceptions(std::ifstream::failbit|std::ifstream::badbit);
return stream;
}
maybe it will help someone.
Yet, I still wonder if it is possible to get table mapping handles/descriptors from OS mentioned by @yzt, and will accept as an answer if someone explains how to do that inside the program.