I'm starting a process through boost::process
. The process uses std::cout
and std::cerr
to output some information. I need to retrieve those information. At some point, I want to be able to store those outputs preserving order and severity (output from cout
or cerr
).
But I could not achieve that considering the way boost::process
redirects the outputs. I could only redirect std::cout
to a specific ipstream and std::cerr
to another. Then, when reading them, I can't preserve the order.
Here is a MCVE isolating teh problem:
#include <iostream>
#include <boost/process.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
void doReadOutput( boost::process::ipstream* is, boost::process::ipstream* err, std::ostream* out )
{
std::string line;
if ( std::getline( *is, line ) )
*out << "cout: " << line << std::endl;
if ( std::getline( *err, line ) )
*out << "cerr: " << line << std::endl;
}
void readOutput( boost::process::ipstream* is, boost::process::ipstream* err, std::ostream* out, std::atomic_bool* continueFlag )
{
std::string line;
while ( *continueFlag )
{
doReadOutput( is, err, out );
}
// get last outputs that may remain in buffers
doReadOutput( is, err, out );
}
int main( int argc, char* argv[] )
{
if ( argc == 1 )
{
// run this same program with "foo" as parameter, to enter "else" statement below from a different process
try
{
boost::process::ipstream is_stream, err_stream;
std::stringstream merged_output;
std::atomic_bool continueFlag = true;
boost::process::child child( argv[0],
std::vector<std::string>{ "foo" },
boost::process::std_out > is_stream,
boost::process::std_err > err_stream );
boost::thread thrd( boost::bind( readOutput, &is_stream, &err_stream, &merged_output, &continueFlag ) );
child.wait();
continueFlag = false;
thrd.join();
std::cout << "Program output was:" << std::endl;
std::cout << merged_output.str();
}
catch ( const boost::process::process_error& err )
{
std::cerr << "Error: " << err.code() << std::endl;
}
catch (...) // @NOCOVERAGE
{
std::cerr << "Unknown error" << std::endl;
}
}
else
{
// program invoked through boost::process by "if" statement above
std::cerr << "Error1" << std::endl;
std::cout << "Hello World1" << std::endl;
std::cerr << "Error2" << std::endl;
std::cerr << "Error3" << std::endl;
std::cerr << "Error4" << std::endl;
std::cerr << "Error5" << std::endl;
std::cout << "Hello World2" << std::endl;
std::cerr << "Error6" << std::endl;
std::cout << "Hello World3" << std::endl;
}
return 0;
}
When I execute this program (Compiled with Visual Studio 2019 under Windows 10), it outputs:
Program output was:
cout: Hello World1
cerr: Error1
cout: Hello World2
cerr: Error2
cout: Hello World3
cerr: Error3
cerr: Error4
cerr: Error5
cerr: Error6
While I want:
Program output was:
cerr: Error1
cout: Hello World1
cerr: Error2
cerr: Error3
cerr: Error4
cerr: Error5
cout: Hello World2
cerr: Error6
cout: Hello World3
Is there any way to achieve that?
Edit, as suggested by Some programmer dude, created one thread per output stream:
#include <iostream>
#include <boost/process.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/thread/mutex.hpp>
void doReadOutput( boost::process::ipstream* str, std::ostream* out, const std::string& prefix, boost::mutex* mutex )
{
std::string line;
if ( std::getline( *str, line ) )
{
boost::mutex::scoped_lock lock( *mutex );
*out << prefix << ": " << line << std::endl;
}
}
void readOutput( boost::process::ipstream* str, std::ostream* out, std::string prefix, boost::mutex* mutex, std::atomic_bool* continueFlag )
{
while ( *continueFlag )
{
doReadOutput( str, out, prefix, mutex );
boost::thread::yield();
}
// get last outputs that may remain in buffers
doReadOutput( str, out, prefix, mutex );
}
int main( int argc, char* argv[] )
{
if ( argc == 1 )
{
// run this same program with "foo" as parameter, to enter "else" statement below from a different process
try
{
boost::process::ipstream is_stream, err_stream;
std::stringstream merged_output;
std::atomic_bool continueFlag = true;
boost::process::child child( argv[0],
std::vector<std::string>{ "foo" },
boost::process::std_out > is_stream,
boost::process::std_err > err_stream );
boost::mutex mutex;
boost::thread thrdis( boost::bind( readOutput, &is_stream, &merged_output, "cout", &mutex, &continueFlag ) );
boost::thread thrderr( boost::bind( readOutput, &err_stream, &merged_output, "cerr", &mutex, &continueFlag ) );
child.wait();
continueFlag = false;
thrdis.join();
thrderr.join();
std::cout << "Program output was:" << std::endl;
std::cout << merged_output.str();
}
catch ( const boost::process::process_error& err )
{
std::cerr << "Error: " << err.code() << std::endl;
}
catch (...) // @NOCOVERAGE
{
std::cerr << "Unknown error" << std::endl;
}
}
else
{
// program invoked through boost::process by "if" statement above
std::cerr << "Error1" << std::endl;
std::cout << "Hello World1" << std::endl;
std::cerr << "Error2" << std::endl;
std::cerr << "Error3" << std::endl;
std::cerr << "Error4" << std::endl;
std::cerr << "Error5" << std::endl;
std::cout << "Hello World2" << std::endl;
std::cerr << "Error6" << std::endl;
std::cout << "Hello World3" << std::endl;
}
return 0;
}
Then the output is:
Program output was:
cerr: Error1
cout: Hello World1
cerr: Error2
cout: Hello World2
cerr: Error3
cout: Hello World3
cerr: Error4
cerr: Error5
cerr: Error6
Still unexpected...