1

There are countless articles and answers online explaining how to redirect standard output, like this or this. However, I want to duplicate/mirror what is written to stdout/stderr without completely redirecting it. When I do a printf or an std::cout, I want it to simultaneously write to the console and to a file. This is a plugin of sorts; I do not have control over when or how the process calls printf/std::cout/etc. I only need to support Windows. I sort of want something like Boost's tee device but that somehow uses stdout. Is this possible?

Edit: I wonder if the people who closed this really understood what I am asking, or if I am missing something? I don't want to duplicate the std::cout stream buffer, I want to duplicate standard output. None of those answers linked in the "duplicate" question link could intercept/duplicate a call to printf.

Community
  • 1
  • 1
  • 1
    https://en.wikipedia.org/wiki/Tee_(command) – r3mainer Apr 12 '17 at 12:04
  • Are you not allowed to use Boost's tee? – AndyG Apr 12 '17 at 12:04
  • http://man7.org/linux/man-pages/man2/dup.2.html – sailfish009 Apr 12 '17 at 12:06
  • 1
    @AndyG I am. I just don't understand how to use it in conjunction with stdout. Examples online like [this](http://stackoverflow.com/a/5942532/1594014) use an std::ostream like std::cout. I want to capture all standard output. – Aidan McArthur Apr 12 '17 at 12:11
  • 1
    @squeamishossifrage I am on Windows. I cannot use the powershell command. I also cannot create my own tee program because I can't create the process that I wish to log. It is already running and I attach to it. – Aidan McArthur Apr 12 '17 at 12:14
  • @AidanMcArthur: I think I see what you're asking. You want to intercept all calls to std::cout, and treat that stream itself as the tee, rather than use a tee as the stream instead, yes? – AndyG Apr 12 '17 at 12:14
  • This is ridiculous that people are voting to close. OP has researched the problem, and has linked to a Stack Overflow question that discusses dup2. The `tee` command wouldn't work directly in this scenario, either, as the process is already started. @AidanMcArthur One possible approach is to create a pipe, replace stdout with the write end of the pipe, and create a thread that reads from the read end, writing to whichever file(s) you want. – Daniel Trebbien Apr 12 '17 at 12:18
  • @AndyG I'm not sure about that. I want to intercept all calls to std::cout AND printf, WriteConsole, etc; anything that writes to the standard output. The example [here](http://stackoverflow.com/a/5942532/1594014) is exactly what I want, except instead of `Tee tee( std::cout, file );` I would want something like `Tee tee( stdout, file );` This seems like it would be too good to be true. – Aidan McArthur Apr 12 '17 at 12:24
  • "I can*t create the process that I whish to log." So it is already running - but how was it created then? As a system service? – Aconcagua Apr 12 '17 at 12:32
  • @DanielTrebbien Thanks, I will look into this. But why create a new thread? – Aidan McArthur Apr 12 '17 at 12:37
  • @Aconcagua Possibly. Or another process started it. There is not a single scenario in mind, this is for the purpose of debugging processes. – Aidan McArthur Apr 12 '17 at 12:39
  • With windows services, you should have a problem - although not 100% sure (linux: yes), your processes should not be connected to a console any more, thus stdout being closed (or just dropping the output...) and there is nothing to catch any more. "Or another process started it" - well, just redirecting the same problem to that other process - if that one is not linked to a console, the child will not be either again. – Aconcagua Apr 12 '17 at 12:43
  • @Aconcagua I understand and appreciate your points, but I am just making a debugging tool for myself and I would like the ability to attach to a running process and intercept it's standard output. There are many scenarios I could give you where this could be useful (at least for myself). – Aidan McArthur Apr 12 '17 at 12:49
  • @AidanMcArthur The purpose of the thread is to take whatever is written to the pipe and write that to the file(s) you want to contain copies of what the process is writing to "stdout" (which is actually the write end of a pipe). Without a thread, you could periodically transfer the data in the pipe to the destination file(s), but you run the risk of deadlock if the pipe ever becomes full. See this blog post from Raymond Chen: https://blogs.msdn.microsoft.com/oldnewthing/20110707-00/?p=10223 – Daniel Trebbien Apr 12 '17 at 13:03
  • @DanielTrebbien Awesome thanks. – Aidan McArthur Apr 12 '17 at 13:14
  • @DanielTrebbien Sorry to bug you, only if you have time, could you check out what I have so far and tell me if I'm on the right track: https://pastebin.com/gaGSCE1F This is allowing me to block on ReadFile and wait for output to arrive that I could then write to a file, but this redirects output away from the console. Am I not doing what you suggested correctly, or is this the expected result? – Aidan McArthur Apr 13 '17 at 07:18
  • Hi @AidanMcArthur: I coded up a demonstration here: https://gist.github.com/dtrebbien/519b1cfeb12f372642c7deb771e190ea I have only tested it on macOS, but I think that it will work on Windows. (If it doesn't, would you please let me know what fixes are required?) Taking a look at your pastebin, I think that you want to duplicate the current stdout before replacing it, as that way you can write to stdout *and* any other file(s). Also, you probably want to use binary I/O rather than text I/O. – Daniel Trebbien Apr 14 '17 at 12:42
  • @DanielTrebbien Wow, thank you so much. The exact code you wrote works just how it's supposed to. The only thing I need to do is make it make it continue to duplicate output for the entire life of the process. This is interesting because I believe after I exit the scope of my function, the tee device is destroyed, resulting in an exception. Maybe a simple inf. loop at the end of my function? Or maybe an inf. loop within the tee thread, and instead of .join(), .detach()? Also, would I want `boost::iostreams::never_close_handle`? Thanks for your help. Your gist should be the accepted answer. – Aidan McArthur Apr 14 '17 at 17:13
  • By moving the setup code within a class, you can implement this behavior. Please see the updated gist. If you comment out the `delete tee` line, then the tee redirection is enabled for the life of the process. However, note that this can result in not all bytes written to stdout being saved in the file. If you have the option of implementing a "tear down" type of plugin API, this would be a good place to delete the `stdout_tee` object. – Daniel Trebbien Apr 17 '17 at 12:28
  • @DanielTrebbien I'm getting an exception when I hit `tee_os << &pipe_readbuf;` It is thrown by the `write` method in boost's `tee` class. Specifically this line `BOOST_ASSERT(result1 == n && result2 == n);` On breaking, result1 == 4096, result2 == 0 and n = 4096. [Image](http://imgur.com/a/BXrsF). I haven't changed anything else with your code. Let me know if you need more info. Thanks. – Aidan McArthur Apr 17 '17 at 17:53
  • @DanielTrebbien Disregard that I'm an idiot. Was using a directory path as the sink instead of a file path. New issue though. But the same issue I was having with your original code. After removing `delete tee`, it fails to finish writing the for loop of numbers to the console and to the file, and fails to write anything else after. Is this what you were warning me of in your last comment? Even if I called `delete` in DLL_PROCESS_DETACH, why is nothing being outputted in the mean time? Is there some way to force flush everything? `tee_os << std::unitbuf << &pipe_readbuf;` doesn't work. – Aidan McArthur Apr 17 '17 at 18:15
  • I get [this](http://imgur.com/a/yHCjz) every time, or something close to it. Why does it stop working before the loop is finished? Wouldn't the `std::cout << std::endl;` at the end of every for loop iteration cause it to flush? – Aidan McArthur Apr 17 '17 at 18:24
  • `std::cout << std::endl` does flush `std::cout`, so any bytes that were written to `std::cout` will have been written to the pipe. The issue is that as the tee thread is attempting to finish transferring the contents of the pipe to `tee_os`, the program exits. I don't know whether it is specified what happens to threads that are running when a program exits, but it appears to be that the running threads are terminated or maybe the stdout file description that `tee_os` is writing to is closed. Either way, the tee thread does not get to finish. – Daniel Trebbien Apr 17 '17 at 22:08
  • @DanielTrebbien My process doesn't exit after the output calls that you wrote. After your for loop that cout's numbers, the process's main thread is resumed, which does additional printf's and cout's, each with a sleep in between the calls. I should have been more specific. My flow currently is: create a process in suspended mode, inject my dll which works by creating a remote thread which contains the tee solution you made, then the process's main thread is resumed, which does the additional printing. The important thing is that my process doesn't exit and the threads are alive. – Aidan McArthur Apr 17 '17 at 23:32
  • 1
    I'm beginning to think this is just something unrelated to the original question that I am doing wrong, I think what you gave me is probably perfect. I just need to do some serious debugging. So thank you very much for your help Daniel, I really appreciate your continual help. – Aidan McArthur Apr 17 '17 at 23:42

0 Answers0