2

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.

mmocny
  • 8,775
  • 7
  • 40
  • 50
  • I tried doing what you wrote in your post above at one time, and it worked until special characters were reached (like endl), and then the class stopped working as expected. I look forward to seeing the answers here. – J. Polfer Jun 15 '09 at 21:23
  • Would these help: [http://stackoverflow.com/questions/999120/c-hello-world-boost-tee-example-program](http://stackoverflow.com/questions/999120/c-hello-world-boost-tee-example-program) [http://stackoverflow.com/questions/670465/using-boostiostreamsteedevice](http://stackoverflow.com/questions/670465/using-boostiostreamsteedevice) – stefanB Jun 16 '09 at 02:06

4 Answers4

3

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
  • 13,938
  • 1
  • 40
  • 42
1

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.

mmocny
  • 8,775
  • 7
  • 40
  • 50
  • Nice to know! I thought about invert(), but wasn't confident that it could work, nor had the time to try it. – Éric Malenfant Jun 16 '09 at 17:33
  • 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". – mmocny Jun 16 '09 at 18:36
1

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
mmocny
  • 8,775
  • 7
  • 40
  • 50
0

Nice easy/short way (needs latest boost I'm guessing and -std=c++11) - thanks to @mmocny, @Éric Malenfant and Boost lib devs.

This demo program logs to "tmp.log" whatever conversation you have on std::cin and std::cout:

#include <iostream>

#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/file.hpp>

namespace bio = boost::iostreams;

int main(int, char**)
{
    // Logging
    bio::filtering_ostream log;
    log.push(bio::file_sink("tmp.log", std::ios_base::app));

    // Tee filter instance (will be copied into each filter stream)
    const bio::tee_filter<std::ostream> teeFilter(log);

    // std::out tee
    bio::filtering_ostream out;
    out.push(teeFilter);
    out.push(std::cout);

    // std::in tee
    bio::filtering_istream in;
    in.push(teeFilter, 0); // If you don't pass 0 for buffer size, on std::cin it'll try to read 4096 chars and basically be useless
    in.push(std::cin, 0);

    out << "What is your name ?" << std::endl << std::flush;
    std::string name;
    getline(in, name);
    out << "Hello " << name << std::endl << std::flush;
}
matiu
  • 7,469
  • 4
  • 44
  • 48