2

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)

Community
  • 1
  • 1
Jan
  • 1,905
  • 17
  • 41
  • In the first question you link to, notice how the top answer is just a simple function that does two outputs? One to the file and one to `std::cout`? Can't you solve it in a similar way? By creating a function which you call, and in turn does the writing to both the file and to `stdout`? – Some programmer dude Jan 24 '17 at 15:47
  • Why is a custom stream so bad? What's wrong with the existing solutions? You just don't like them because it's not already part of `ostream`? – AndyG Jan 24 '17 at 15:49
  • Also, can you elaborate on your need to write to `stdout` or `stderr`? Are you calling some C functions that you can't modify? What is the *actual* and *original* problem that you try to solve? Please [read about the XY problem](http://xyproblem.info/) and think about how it relates to your question. – Some programmer dude Jan 24 '17 at 15:52
  • No, there are no standard classes which would do what you are trying to do. – SergeyA Jan 24 '17 at 15:56
  • [how-can-i-compose-output-streams-so-output-goes-multiple-places-at-once](http://stackoverflow.com/questions/1760726/how-can-i-compose-output-streams-so-output-goes-multiple-places-at-once) and [how-to-redirect-cin-and-cout-to-files](http://stackoverflow.com/questions/10150468/how-to-redirect-cin-and-cout-to-files) might help. – Jarod42 Jan 24 '17 at 16:09
  • This might be of help: http://stackoverflow.com/a/1761960/4181011 – Simon Kraemer Jan 25 '17 at 08:44

2 Answers2

3

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.

JvO
  • 3,036
  • 2
  • 17
  • 32
  • best answer so far! Do you possibly know how to make this work with Windows named pipes? (as to the edit in my question) – Jan Jan 25 '17 at 06:21
  • Sorry, my Windows knowledge is quite limited – JvO Jan 25 '17 at 09:57
2

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

Community
  • 1
  • 1
Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
  • Thanks for the code. I however cannot alter the called code so I need to duplicate **stdout/stderr** - exactly those – Jan Jan 24 '17 at 18:11
  • @SimonKraemer, I tried your code in vs2015, it only writes to the file, but not console. – Robin Jul 06 '17 at 09:03
  • @Robin Do you mean the actual console or the debugging window in Visual Studio? These are 2 different things. Please run the program from the command line and not from within VS to check. – Simon Kraemer Jul 06 '17 at 09:13
  • @SimonKraemer, I was using it AllocConsole, my bad, it is working now. – Robin Jul 07 '17 at 09:11