Jan
Jan

Reputation: 1975

C++ - Duplicating stdout/stderr to file while keeping console outputs

Very similar question was already asked here: Writing to both terminal and file c++

But without a good answer. All answers suggest to use custom stream or duplicating std::cout. However I need the behavior for stdout/stderr.

Wanted behavior: For every write to stdout/stderr I want this to appear on console and also be redirected to a file.

I was thinking about redirecting the stdout to pipe and from there writing to file and console - expanding on this answer https://stackoverflow.com/a/956269/2308106

Is there any better approach to this?

EDIT1: Why stdout/stderr and not custom streams?

I'm calling (3rd party) code that I cannot modify and that is hosted within my process. So I cannot use custom streams (the called code is already writting to stderr/stdout).

EDIT2:

Based on the suggestion from JvO I tried my implementation (windows):

HANDLE hPipe = ::CreateNamedPipe(TEXT("\\\\.\\pipe\\StdErrPipe"),
    PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
    PIPE_TYPE_BYTE,
    //single instance
    1,
    //default buffer sizes
    0,
    0,
    0,
    NULL);

if (hPipe == INVALID_HANDLE_VALUE)
{
    //Error out
}

bool fConnected = ConnectNamedPipe(hPipe, NULL) ?
    TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

if (!fConnected)
{
    //Error out
}

int fd = _open_osfhandle(reinterpret_cast<intptr_t>(hPipe), _O_APPEND | /*_O_WTEXT*/_O_TEXT);

if (dup2(fd, 2) == -1)
{
    //Error out
}

There is still some issue though - as from the other end of the pipe I receive only a rubish (I first try to send something dirrectly - that works great; but once stderr is redirected and I write to it; the pipe receives same nonprinatble character over and over)

Upvotes: 2

Views: 3122

Answers (2)

JvO
JvO

Reputation: 3106

You can 'hijack' stdout and stderr by replacing the pointers; stdout and stderr are nothing more than FILE *. I suggest you open a pipe pair first, then used fdopen() to create a new FILE * which is assiocated with the sending end of the pipe, then point stdout to your new FILE. Use the receiving end of the pipe to extract what was written to the 'old' stdout.

Pseudo code:

int fd[2];
FILE *old_stdout, *new_stdout;

pipe(fd);
new_stdout = fdopen(fd[1], "w");
old_stdout = stdout;
stdout = new_stdout;

Now, everything you read from fd[0] can be written to a file, old_stdout, etc.

Upvotes: 3

Simon Kraemer
Simon Kraemer

Reputation: 5670

You can redirect cout. One (incomplete) example might look like this:

#include <fstream>
#include <iostream>


template<class CharT, class Traits = std::char_traits<CharT> >
struct teestream : std::basic_streambuf<CharT, Traits> {

private:
    std::basic_streambuf<CharT, Traits>* m_rdbuf1;
    std::basic_streambuf<CharT, Traits>* m_rdbuf2;

public:
    teestream(std::basic_streambuf<CharT, Traits>* rdbuf1, std::basic_streambuf<CharT, Traits>* rdbuf2)
        :m_rdbuf1(rdbuf1)
        ,m_rdbuf2(rdbuf2)
    {}

    ~teestream() {
        m_rdbuf1->pubsync();
        m_rdbuf2->pubsync();
    }

protected:
    int_type overflow(int_type ch = Traits::eof()) override
    {
        int_type result = m_rdbuf1->sputc(ch);
        if (result != Traits::eof())
        {
            result = m_rdbuf2->sputc(ch);
        }
        return result;
    }

    virtual int sync() override
    {
        int result = m_rdbuf1->pubsync();
        if (result == 0)
        {
            result = m_rdbuf2->pubsync();
        }
        return result;
    }

};

typedef teestream<char, std::char_traits<char>> basic_teestream;

int main()
{
    std::ofstream fout("out.txt");
    std::streambuf *foutbuf = fout.rdbuf();         //Get streambuf for output stream
    std::streambuf *coutbuf = std::cout.rdbuf();    //Get streambuf for cout

    std::streambuf *teebuf = new basic_teestream(coutbuf, foutbuf); //create new teebuf

    std::cout.rdbuf(teebuf);//Redirect cout

    std::cout << "hello" << std::endl;

    std::cout.rdbuf(coutbuf); //Restore cout
    delete teebuf; //Destroy teebuf
}

As you can see here the streambuf used by cout is replaced by one that controls the streambuf itself as well as the streambuf of a ofstream.

The code has most likely many flaws and is incomplete but you should get the idea.

Sources:
https://stackoverflow.com/a/10151286/4181011
How can I compose output streams, so output goes multiple places at once?
http://en.cppreference.com/w/cpp/io/basic_streambuf/pubsync
Implementing std::basic_streambuf subclass for manipulating input

Upvotes: 2

Related Questions