tags:

views:

347

answers:

5

I am looking for a way to branch (tee) the input read from an istream (cin, in my case) out to a log file (clog/ofstream/etc), while still using the input for processing.

I have read about boost::tee_device, and it is very similar to my requirements. Unfortunately, it is implemented as an ostream, and thus solves a similar problem from "the other side of the pipe".

I attempted to write an istream (adaptor) class which forwards the input functions on to a wrapped input stream (cin), and also sends what was read to the log file.

This works fine for basic types which call operator>>(...) directly, however, I have run into issues with some more advanced usage of the input stream, for example, for operator>>(std::string), and the std::string getline function.

Is there any easier way to do this (possibly via rdbuf() manipulation)?

Thanks!

Edit: I could change my code all over the place to something like: cin >> value; clog << value; -- but that would be a significant and ugly change. I would also prefer to have an easy way to turn logging off. Thus, I would like a way to model this as an istream "filter" and then simply replace all references to cin with this istream "logger".

Ideal Solution:

class log_istream : public std::istream
{
public:
    log_istream( std::istream & in , std::ostream & out );

    /* ... istream forwarding functions ... */

private:
    std::istream & in_;
    std::ostream & out_;     
};

int main() {
    log_istream logger( std::cin , std::ofstream("logfile.out") );

    logger >> value; // this implies infile >> value and logfile << value
    getline(logger,my_string); // this also implies logfile.writeline(value)
    // etc
}

etc.

A: 

would this work?

slf
Not that I know of. I mentioned it in the description. As far as I can tell, it will only work as an ostream - but perhaps I am missing something.
michalmocny
+2  A: 

Using Boost.IOStreams, you could define an input filter that logs what it reads into clog. Something like:

(warning: untested code ahead)

class LoggingInputFilter : public multichar_input_filter {
public:
    template<typename Source>
    std::streamsize read(Source& Src, char* S, std::streamsize N)
    {
        streamsize result = read(Src, S, N);
        if (result == -1){
            return result;
        }

        if (std::clog.write(S, result)){
            return result;
        }

        return -1;
    }
};

Chain it with std::cin:

LoggingInputFilter cin_logger;
filtering_stream logged_cin(cin_logger);
logged_cin.push(std::cin);

and use logged_cin instead of std::cin

Edit: Or operate at the streabuf level, so that your code still uses std::cin:

LoggingInputFilter cin_logger;
filtering_streambuf logged_cin(cin_logger);
logged_cin.push(std::cin.rdbuf());
std::cin.rdbuf(logged_cin);
Éric Malenfant
This wasn't the solution I used - but it was what got me on the right track. Thanks!
michalmocny
+1  A: 

I have found a simple solution:

Boost::iostreams provides inversion between source/sink filters.

While tee_filter is modeled as a sink, you can invert() it into a source, and it will still "tee" what it filters to the sink specified:

    boost::iostreams::file log_file("sample.txt", std::ios::trunc); // or std::ios::app

    // Single parameter tee() function returns a tee_filter , and invert() inverts that filter
    boost::iostreams::filtering_istream in(
            boost::iostreams::invert(
                    boost::iostreams::tee(log_file)));

This way, I have logging on all filtered input.

Performance isn't an issue, but if anyone notices any red-flags, I would be very interested. Thanks.

michalmocny
Nice to know! I thought about invert(), but wasn't confident that it could work, nor had the time to try it.
Éric Malenfant
I just tested this code again and it will segfault unless you "finish" the "pipe" by adding in.push( std::cin ); or any other "Source".
michalmocny
A: 

Final Answer:

#ifndef TEE_ISTREAM_H_
#define TEE_ISTREAM_H_

/*****************************************************************************/

#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/invert.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/tr1/memory.hpp>
#include <iostream>

/*****************************************************************************/

namespace bio = boost::iostreams;

/*****************************************************************************/

class tee_source : public bio::source {
public:
    tee_source( std::istream & in, const std::string & filename )
     : in_(in), log_file_(filename, std::ios::app), tee_(bio::tee(log_file_), 1)
    { }

    std::streamsize read(char* s, std::streamsize n)
    {
     return tee_.read(in_,s,n);
    }

private:
    std::istream &                               in_;
    bio::file                                    log_file_;
    bio::inverse< bio::tee_filter< bio::file > > tee_;
};

/*****************************************************************************/

typedef bio::filtering_istream                tee_istream_t;
typedef std::tr1::shared_ptr< tee_istream_t > tee_istream_ptr_t;

/*****************************************************************************/

inline tee_istream_ptr_t make_tee_istream( std::istream & in, const std::string & filename )
{
    return tee_istream_ptr_t( new tee_istream_t( tee_source( in , filename ), 0 ) );
}

/*****************************************************************************/

#endif
michalmocny