1

To put it simply, I'm trying to create one stream that links to a file std::ofstream outFile{pathToFile, std::ios_base::app};. This file is a log file that, ideally, would receive copies of both stderr and stdout. When an error would occur, for example, the error would be printed in the console and in the file.

I've tried using freopen(pathToFile, "a+", stderr); as well as std::cerr.rdbuf(outFile.rdbuf()); however, both of these completely redirect the output, similar to a pipe in bash. I also want to see the error in the console.

Being somewhat new to C++ streams, I'm not quite sure how I would achieve this. The way I would see it being done in another language would be "subscribing" to stderr, so my original stream would be notified everytime stderr changes and update itself. Another way would be to override stderr, log to my file and call the original one, but since stderr is usually not called by the programmer itself, I'm not sure how I could do that.

It should be noted that I'm using windows, so I don't have access to unistd.h. I would also like to avoid using STL or third party libraries.

Chris
  • 26,361
  • 5
  • 21
  • 42
Etienne Poulin
  • 178
  • 1
  • 4
  • 15

1 Answers1

2

The IOStreams internally use a std::streambuf to actually write (or read) characters. If you just want to redirect all characters written to another stream, you can just redirect the respective stream buffer. You should restore the original stream buffer eventually, though, as the streams are flushed on destruction and the life time of the stream buffers matter. For example

 #include <iostream>
 #include <fstream>

 struct redirect {
     std::ostream&   d_stream;
     std::streambuf* d_orig;
     redirect(std::ostream& out, std::ostream& to)
         : d_stream(out)
         , d_orig(out.rdbuf(to.rdbuf())) {
     }
     ~redirect() { this->d_stream.rdbuf(this->d_orig); }
};

int main() {
    std::ofstream log("my-log.txt");
    redirect rcout(std::cout, log);
    redirect rcerr(std::cerr, log);
    std::cout << "hello, ";
    std::cerr << "world!\n";
}

If you also want to the redirected stream to appear on the original stream, you can use a "teestream" which uses a stream buffer copying each character to two different stream buffers (I have posted multiple versions of such a stream in the past in multiple place, including on stackoverflow).

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • The example you showed here didn't really help, since this is pretty much what I already had, but the answer you linked was exactly what I needed. I have pretty much solved my issue. However, I'd like to ask about restoring the buffer, as you mentionned. As of right now, I simply set cout's buffer to the teestream's buffer, and it works well. But you mentionned restoring the buffer. I'm not making any backup of the old cout, since I want this behavior to happen from application launch to exit. In that case, is it still fine to never restore the buffer? Assuming I flush regularly. – Etienne Poulin Dec 24 '21 at 20:49
  • 1
    @EtiennePoulin: I answered the question as asked and also linking to what was probably intended to be asked (a usual case of X/Y-question). At the unspecified time when `std::cout` (likewise of `std::cerr`) is destroyed, the buffer will be forcibly flushed (i.e., `pubsync()` gets called). To that end, the stream buffer pointed to _has_ to be valid. The easiest approach to do that is to restore its original stream buffer. If you are willing to leak a `teebuf` and not destroy it properly you can leave that installed. It would be against my personal code hygiene but it should work. – Dietmar Kühl Dec 24 '21 at 21:00
  • Sorry about the ambiguity in the question. So, from what I understand, you mean that when std:cout is destroyed (most likely when the program stops running?) it needs to have been restored to the original stream before that? So, say I have a Logger class, which handles all logging and is instanciated at the beginning of execution, if I restore the cout and cerr streams in the destructor of said Logger class, it would be fine? Because otherwise, I have no clue when I'd restore them, given that I want this teestream to be up as long as the program is running. – Etienne Poulin Dec 24 '21 at 21:15
  • 1
    @EtiennePoulin: yes, restoring the stream buffers when the replacement stream buffer gets destroyed is a reasonable place. The net effect could be that some logs are not captured if they are written after the logger got destroyed. That is similar to some logs not being captured before the logger got constructed. – Dietmar Kühl Dec 24 '21 at 21:18
  • 1
    @EtiennePoulin: you are welcome. The problem is also not just something theoretical: about 25 years ago that lead me to the realization that a derived IOStream creating a custom stream (e.g. a `oteestream` creating a `teebuf`) should make the stream buffer (a class holding a member thereof) the first [probably `private`] `virtual` base class: if the stream buffer is a member of the derived stream, it would be destroyed before the base class constructor is run and the program might crash - such crashes actually did happen (and would probably still happen). – Dietmar Kühl Dec 24 '21 at 21:26