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.
Check to make sure the file was successfully opened by checking to see if the variable == NULL. If it does, an error has occured. Use the fprintf or fscanf functions to write/read from the file. Usually these function calls are placed in a loop.
Solution 1 First check if the file exists (File. Exists) if so try to open for write within try and catch block, if exception is generated then it is used by another process.
To see the open files for a process, select a process from the list, select the View->Lower Panel View->Handles menu option. All of the handles of type "File" are the open files. Also, a great way to find which application has a file open is by using the Find->Handle or DLL menu option.
As a side note: If you wanted to check if the file is already open (by any other process or thread), you can try getting a file write lease (fcntl(fileno(stream),F_SETLEASE,F_WRLCK)). It will fail if the file is already open by anyone else. This only works for normal files owned by the user.
Here is an example that finds all files currently opened by the cron process. The PID for cron is 323762, which is 0x4F0B2 in hex. We can use the slot number to display the file system state info and the file descriptor table. In this example we see that cron has 13 opened files, numbered from 0 to 12.
A process might have a defect that causes it to continuously create files without closing them, or it might open files, read and write to those files, but fail to close the files afterwards.
We can use find command to find all file names in the filesystem /usr with an inode of 13407. Notice the 1 just before the first bin. This indicates that there is only 1 hard link, meaning the file name hostmibd.cat is the only file name associated with this inode. The AIX pstat command can be used to list all files opened by a process.
So here is a simple Linux specific code that checks whether a specified target file is open by the current process (using --std=c++17 for dir listing but any way can be used of course).
#include <string>
#include <iostream>
#include <filesystem>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
bool is_open_by_me(const std::string &target)
{
char readlinkpath[PATH_MAX];
std::string path = "/proc/" + std::to_string(getpid()) + "/fd";
for (const auto & entry : std::filesystem::directory_iterator(path)) {
readlink(entry.path().c_str(), readlinkpath, sizeof(readlinkpath));
if (target == readlinkpath)
return true;
}
return false;
}
Simply list the current pid's open handles via proc, then use readlink function to resolve it to the actual file name.
That is the best way to do it from the userspace I know. This information is not known by the process itself, it is known by the kernel about the process, hence the process has to use various tricks, in this case parsing procfs, to access it.
If you want to check whether a different process hold an open handle to a file, you will have to parse all the procfs for all processes. That may not be always possible since other processes may be run by different users.
All that said - in your specific case, when you are the one owner, opening and closing the files - maintaining a table of open handles is a much cleaner solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With