Reputation: 7128
I have a multithreaded application that uses standard cout for logging, for example,
cout << "some text" << endl;
The problem is that the program is facing an occasional crash because of non-thread-safe access to shared cout by multiple threads.
Say, my program is named prg.exe and we run as prog.exe > t.log
All logs generated by individual thread gets mingled up, even that's ok, the bad part is the crash whenever there is contention by the competing threads in accessing cout, one trying to flush, other trying yo put something there, resulting in the crash.
Since there are are lot many uses of cout in existing code, used by different threads, this is difficult to change all cout to something else. So, I am trying to adopt the following:
std::streambuf * redirect_output(char * filenm, std::ofstream& filestr)
{
std::streambuf *newsb, *oldsb;
filestr.open(filenm);
oldsb = std::cout.rdbuf(); // back up cout's streambuf
newsb = filestr.rdbuf(); // get file's streambuf
std::cout.rdbuf(newsb); // assign streambuf to cout
return oldsb;
}
void restore_output(std::streambuf * oldsb, std::ofstream& filestr)
{
std::cout.rdbuf(oldsb); // restore cout's original streambuf
filestr.close();
}
void showFileContent(char *filenm)
{
std::ifstream infile;
infile.open(filenm);
//read data from file
cout << "------------- " << filenm << " -------------" << endl;
cout << infile.rdbuf();
infile.close();
}
Each thread, on start, tries to call redirect_output to redirect cout to a file, separate file per thread. So, for example, if we have 3 threads, we have t1.log, t2.log, t3.log and at the end we call restore_output by each thread and eventually in main, we merge the individual log files through
showFileContent("t1.log");
showFileContent("t2.log");
showFileContent("t3.log");
My question is, is it wise and safe to redirect to individual log files per thread and merge at the end of the main function? Can we merge the individual log files at logical sync points by individual threads?
Other option could be to have a thread-safe singleton class encapsulating built-in i/o and use that singleton object instead of cout e.g. SPCL_IOSTREAM::getOStream()
Upvotes: 2
Views: 1404
Reputation: 18503
This is one of those cases where search/replace (or sed) is your best friend. Just take the time to replace the std::cout
usage with calls to your favorite (thread safe) logging library and save yourself a lot of headaches.
Also merging the file at the end of main() sounds complicated (you would need to merge based on timestamp of each entry) to maintain order - and not reliable - what if you crash before the end of main()?
Upvotes: 3
Reputation: 2395
Despite mentioning that you have many users all of who use std::cout for logging, I suggest you take a look here to consider if you really want to follow this strategy. My suggestion is not to redirect cout but to provide a synchronized logger class, that can also provide options for output, and use that.
// Logger() is a singleton that returns a ref. to a different output stream
// based on its parameter.
// Default (no parameter) send everything to cout after synchronizing threads
Logger::addLog("log.txt"); // add an instance of a stream in a static
// map<string, ofstream>
// The dtor can delete the streams after flusing, or for better control, you can
// add methods like
Logger::delLog("log.txt"); // flush and erase the map entry, this log is gone
Logger::log(0, "Some message");
Logger::log("log.txt", "another message");
// Logger::log() can also be a variadic method that takes a log key, a format,
// and parameters like printf
Logger::log(0, "Value=%d", int_value);
You may even consider to improve it by returning a functor that locks a mutex for the log stream in its ctor() and keep a ref to it in a data member, then have an operator() that provides access to the stream, and unlock it in its dtor(). This method will let you use << operators supported by the stream, but, you'll need to make sure the functor is destroyed each time, to avoid deadlocks.
EDIT after comment:
Yes you can redirect it. Look at this answer here in StackOverflow. Also, if you also want to catch any printf() calls in the legacy code, then you need to redirect at the stdio level using dup2(). Relevant links here and here.
Hope this helps.
Upvotes: 1