views:

633

answers:

4

Hello,

I saw a useful start here:

http://www.cs.technion.ac.il/~imaman/programs/teestream.html

And it works great to make a new stream which goes to both clog and a log file.

However, if I try to redefine clog to be the new stream it does not work because the new stream has the same rdbuf() as clog so the following has no effect:

clog.rdbuf(myTee.rdbuf());

So how can I modify the tee class to have its own rdbuf() which can then be the target of clog?

Thanks.

-William

+2  A: 

You don't want to do what your've trying to do because the 'tee' is not working at the rdbuf level. So setting the rdbuf to something else will not work, the output will only go to one stream.

You need to follow there example:

e.g.

fstream clog_file(...);
xstream clog_x(...);
TeeStream clog(clog_file, clog_x);

then use clog everywhere instead of your original clog.

Shane Powell
Hello,Does anyone have an example of how to redefine the C++ built in clog to instead have a new associated rdbuf() which is processed to be a tee to the original clog.rdbuf() and the rdbuf() of a ofstream object to a log file on disk.The intention is to have the code use the std::clog throughout but to have it go to the both the default clog destination as well as to a log file on disk.Thanks.-William
WilliamKF
See Nathan's answer. It's more work but it will work. I would recommend you get the book 'The C++ Standard Library: A Tutorial and Reference' if you want to go down this road. I still recommand using your own typedef so that you are not tied to clog.http://www.amazon.com/C-Standard-Library-Tutorial-Reference/dp/0201379260/ref=pd_sim_b_1
Shane Powell
+2  A: 

If you really want to keep using std::clog for the tee instead of sending output to a different stream, you need to work one level lower: Instead of deriving from ostream, derive from streambuf. Then you can do this:

fstream logFile(...);
TeeBuf tbuf(logFile.rdbuf(), clog.rdbuf());
clog.rdbuf(&tbuf);

For more information on how to derive your own streambuf class, see here.

Nathan Kitchen
Hi,Thanks for the suggestion. Looking into this, I'm wondering if for my TeeBuf class would I be overriding the virtual xsputn() to put to both streams? Would that be sufficient? In other words, I'm looking for the equivalent to the operator<<() in the original solution that worked one level higher.Thanks.-William
WilliamKF
Okay, I've written a version that overrides xsputn() and sends to the two underlying streambuf(s). However, only the first line works and is sent to both the clog and the log file, once the 'endl' is seen, no more output goes to either clog or the file. So what do I need to have 'endl' processing correct on clog with the new tee buffer it is given?
WilliamKF
Here is the proposed xsputn(): streamsize xsputn(const char_type *charSequence, streamsize numChar) { _clogBuf->sputn(charSequence, numChar); return _fileBuf->sputn(charSequence, numChar); }
WilliamKF
I tried to add definitions for seekoff(), seekpos(), and sync() too which just use the underlying two buffers calling pubseekoff(), pubseekpos(), and pubsync() but I got the same symptom of just the first line up to the 'endl' working.
WilliamKF
you don't need xsputn, that's only for optimization, you need to overload sync() and overflow(), sync() is called everytime the internal buffer should sync to the underlying representation (e.g. the screen), so on every std::flush, std::endl, so make sure it does that. overflow() is called when the internal buffer fills up, so it should sync to free up the buffer, check for eof() etc.
Pieter
+1  A: 

Here is the class I created that seems to do the job, thanks to all who helped out!

-William

class TeeStream : public std::basic_filebuf<char, std::char_traits<char> >
{
private:
  class FileStream : public std::ofstream {
  public:
    FileStream()
      : logFileName("/my/log/file/location.log") {
      open(logFileName.c_str(), ios::out | ios::trunc);

      if (fail()) {
        cerr << "Error: failed to open log file: " << logFileName << endl;
        exit(1);
      }
    }
    ~FileStream() {
      close();
    }

    const char *getLogFileName() const {
      return logFileName.c_str();
    }

  private:
    const string logFileName;

  };

public:
  typedef std::char_traits<char> traits;
  typedef std::basic_filebuf<char, traits> baseClass;

  TeeStream()
    :  baseClass(),
       _logOutputStream(),
       _clogBuf(clog.rdbuf()),
       _fileBuf(_logOutputStream.rdbuf()) {
    clog.rdbuf(this);
    _logOutputStream << "Log file starts here:" << endl;
  }
  ~TeeStream() {
    clog.rdbuf(_clogBuf);
  }

  int_type overflow(char_type additionalChar =traits::eof()) {
    const int_type eof = traits::eof();
    const char_type additionalCharacter = traits::to_char_type(additionalChar);
    const int_type result1 = _clogBuf->sputc(additionalCharacter);
    const int_type result2 = _fileBuf->sputc(additionalCharacter);

    if (traits::eq_int_type(eof, result1)) {
      return eof;
    } else {
      return result2;
    }
  }

  int sync() {
    const int result1 = _clogBuf->pubsync();
    const int result2 = _fileBuf->pubsync();

    if (result1 == -1) {
      return -1;
    } else {
      return result2;
    }
  }

private:
  FileStream _logOutputStream;
  streambuf * const _clogBuf;
  streambuf * const _fileBuf;

};
WilliamKF
While the general idea seems ok, you have some potential problems. `putc` expects a char_type, while you're passing a `int_type`, there is a `traits::to_char_type()` function for these conversions (and do appropriate checks to see if the conversion succeeded)
Pieter
Secondly, it's probably better to use the traits compare functions i.s.o. relying on `operator==` for comparing the internal int_type:`traits::eq_int_type(additionalChar, traits::eof())` (although this is fairly academic, in reality it won't make a difference most of the times)
Pieter
Made changes suggested by Pieter.
WilliamKF
A: 
TheScottMachine