1

I need to redirect an ofstream to a file and timestamp every line that's printed. (It's part of a logging system).

I have a working class that manages to do just that but it refuses to flush the file when std::endl is emmited. I'd apreciate any help on that. (If there is a simpler way to do this do tell).

#include <iostream>
#include <streambuf>
#include <fstream>
#include <sys/time.h>
#include <cstring>
#include <memory>

class TimeStampBuf: public std::streambuf {
public:
    explicit TimeStampBuf(std::streambuf* dest) :
            _dest(dest),
            _isAtStartOfLine(true),
            _owner( NULL) {
    }
    explicit TimeStampBuf(std::ostream& dest) :
            _dest(dest.rdbuf()),
            _isAtStartOfLine(true),
            _owner(&dest) {
        _owner->rdbuf(this);
    }
    virtual ~TimeStampBuf() {
        if (_owner != NULL) {
            _owner->rdbuf(_dest);
        }
    }
protected:
    virtual int overflow(int ch) {
        if (_isAtStartOfLine) {
            char timebuff[30];
            timeval curTime;
            gettimeofday(&curTime, NULL);
            strftime(timebuff, sizeof(timebuff), "%Y-%m-%d %H:%M:%S:",
                    localtime(&curTime.tv_sec));
            sprintf(timebuff + strlen(timebuff), "%03u\t",
                    (unsigned int) curTime.tv_usec / 1000);
            _dest->sputn(timebuff, strlen(timebuff));
        }
        _isAtStartOfLine = ch == '\n';
        return _dest->sputc(ch);
    }
private:
    std::streambuf *_dest;
    bool _isAtStartOfLine;
    std::ostream *_owner;
};
class OutputRedirectAndStamp {
public:
    OutputRedirectAndStamp(std::string file, std::ostream &s = std::cout, std::ios::openmode mode = std::ios::out){
        _s=&s;
        _file=file;
        if(_file.size()){
            _mode=mode;
            _buf.open(file.c_str(),mode);
            _coutBuf = s.rdbuf((std::streambuf*)&_buf);
        }
        _tsb.reset(new TimeStampBuf(s));
    }
    void reopen(void){
        _tsb.reset();
        if(_file.size()){
            _s->rdbuf(_coutBuf); //reset to previous output
            _buf.close();
            _buf.open(_file.c_str(),_mode);
            _coutBuf = _s->rdbuf((std::streambuf*)&_buf);
        }
        _tsb.reset(new TimeStampBuf(*_s));
    }
    ~OutputRedirectAndStamp() {
        _tsb.reset();
        if(_file.size()){
            _s->rdbuf(_coutBuf); //reset to previous output
        }
    }
private:
    std::string _file;
    std::ios::openmode _mode;
    std::ostream *_s;
    std::filebuf _buf;
    std::streambuf *_coutBuf;
    std::unique_ptr<TimeStampBuf> _tsb;
};
int main()    //example main
{
    std::unique_ptr<OutputRedirectAndStamp> a;
    a.reset(new OutputRedirectAndStamp("test.txt",std::cout,std::ios::app | std::ios::out));

    std::cout<<"this is written to file"<<2<<std::endl;
    a->reopen();
    std::cout<<"this is written to file also"<<std::endl;
    a.reset();
    std::cout<<"this is written to stdout"<<std::endl;
    return 0;
}
user1783395
  • 95
  • 1
  • 5
  • You may get more help if you are able to reduce your code to just what is needed to reproduce the problem. Try cutting out as much as you can and that will make it easier for us to help you find a solution. – Cory Klein Oct 10 '13 at 20:40
  • 1
    If Boost.Iostreams is available to you, it will seriously reduce your workload. – GManNickG Oct 10 '13 at 20:49
  • I've used boost before. I'll take a look at the Iostreams library but unfortunately it isn't an option for this particular project. Thanks for the sugestion though. – user1783395 Oct 11 '13 at 11:48

1 Answers1

2

When you flush an std::ostream, the stream buffer's pubsync() is called which in turn calls the virtual function sync(). If you don't override sync() it will just claim that it succeeded by returning 0. From your sync() override you should just call the held stream buffer's pubsync():

int TimeStampBuf::sync() {
    return _dest->pubsync();
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380