2

I would like to define something like a new cout, which I can use to log my data:

some_type cout2( Message_type message ){ cout << message; logfile.save(message); } 

so I will use it with

cout2 << "some message" << endl; 

Up to now I was not able to find out how the code above has to look like exactly.

Thanks for your help.

4
  • 4
    Maybe you should have an instance based logger with a .log() method, that, depending on implementation can log to file, write to stdout etc, rather than trying to overload free functions from std namespace. It will be far more OO and look less crap. Commented Oct 27, 2015 at 10:18
  • 1
    How about using std::clog and redirecting its output to a file: std::cout.rdbuf(logfile.rdbuf()); ? Commented Oct 27, 2015 at 10:25
  • Look, Nathan, I also have a .log() method, but I would like to know how can accomplish what I described above, that is why I raised this question. If I wanted to know whether it was crap or not I would have phrased it differently. Commented Oct 27, 2015 at 10:27
  • @varantir My phrasing is often too flippant. I think what yo uactually want can be achieved more neatly by a logger, however. Commented Oct 27, 2015 at 10:29

6 Answers 6

2

You can create your own logger like:

class Logger { public: Logger(std::string const& filename) : stream_(filename, std::ofstream::out | std::ofstream::app) { if (!stream_) { // Error... } } Logger& operator<<(std::string const& str) { std::cout << str; stream_ << str; return *this; } private: std::ofstream stream_; }; 
Sign up to request clarification or add additional context in comments.

Comments

2

Generally speaking, classes from C++ standard library are not designed to be derived, and that is true for stream classes.

So IMHO, it is better to specify what method you will need on your cout2 object, and then:

  • design a class containing a ostream& object, initialized in ctor
  • delegate actual output to that internal object
  • do whatever log you need in your methods

You should use templated a operator << to be able to easily process any class that std::ostream can process.

class LogStream { std::ostream& out; Logfile logfile; LogStream(std::ostream& out, /* param for logfile initialization */ ...) : out(out), logfile(...) {} ... // other useful methods for state }; template<typename T> LogStream& operator << (LogStream& out, T val) { out.out << message; // should first test whether T is manipulator, and choose whether and how it should be logged logfile.save(message); } 

Comments

2

You don't want to modify std::cout.

Instead, you want to create a specialised std::streambuf that writes to two buffers rather than one. For example;

#include <streambuf> template <typename char_type, typename traits = std::char_traits<char_type> > class basic_teebuf: public std::basic_streambuf<char_type, traits> { public: typedef typename traits::int_type int_type; basic_teebuf(std::basic_streambuf<char_type, traits> * sb1, std::basic_streambuf<char_type, traits> * sb2) : sb1(sb1) , sb2(sb2) { } protected: // override virtuals inherited from std::basic_streambuf virtual int sync() { int const r1 = sb1->pubsync(); int const r2 = sb2->pubsync(); return r1 == 0 && r2 == 0 ? 0 : -1; } virtual int_type overflow(int_type c) { int_type const eof = traits::eof(); if (traits::eq_int_type(c, eof)) { return traits::not_eof(c); } else { char_type const ch = traits::to_char_type(c); int_type const r1 = sb1->sputc(ch); int_type const r2 = sb2->sputc(ch); return traits::eq_int_type(r1, eof) || traits::eq_int_type(r2, eof) ? eof : c; } } private: std::basic_streambuf<char_type, traits> * sb1; std::basic_streambuf<char_type, traits> * sb2; }; typedef basic_teebuf<char> teebuf; 

Then you need to create a specialised ostream which uses such a buffer

#include <ostream> class teestream : public std::ostream { public: // Construct an ostream which tees output to the supplied // ostreams. teestream(std::ostream & o1, std::ostream & o2); private: teebuf tbuf; }; teestream::teestream(std::ostream & o1, std::ostream & o2) : std::ostream(&tbuf) , tbuf(o1.rdbuf(), o2.rdbuf()) { } 

All the above does is create a specialised std::ostream that uses our specialised buffer, which in turn makes use of two buffers.

Now, our teestream needs to be initialised using two streams. For example

#include <fstream> #include <iostream> // include the preceding definition of teestream here int main() { std::ofstream logfile("hello-world.log"); teestream tee(std::cout, logfile); // tee is now a stream that writes the same output to std::cout and logfile tee << "Hello, world!\n"; return 0; } 

The advantage of this is that all stream insertions (operator <<) will work with our teestream - even for classes with overloaded versions.

When main() returns, the streams will also be closed cleanly.

I've written the specalised streambuf as a template (std::streambuf is a specialisation of a templated class named std::basic_streambuf). For generality, it would probably be better to do the same with the stream (using the fact that std::ostream is also a specialisation of a templated std::basic_ostream). I leave that sort of generalisation as an exercise.

Comments

0

The logging systems I've seen, built on this idea look something like this:

#define MY_PRINT(x, ...) \ { \ fprintf(logFile, x, ##__VA_ARGS__); \ fflush(acuLogFile); \ } 

And you would use it like:

MY_PRINT("this is just a test\n"); 

Even though this is the C way of doing things, it is very versatile and work in C++ as well.

You just have to use your newly define print function everywhere in the code.

Comments

0

Maybe you should have an instance based logger with a .log() method, that, depending on implementation can log to file, write to stdout etc, rather than trying to overload free functions from std namespace?

Your intent will be clearer and it will be more object orientated.

In fact here is an example of a pretty cookie-cutter event logger I wrote recently, if that helps you to understand the sort of thing I'm talking about. My design allows for dependnacy injection, as well as keeping boring decisions about how something should be formatted as output and where it should go (stdout or file etc) in the logger.

Comments

0

Of course you can define your own cout. First you need a class which can handle the << operator and contains an fstream and an ostream (like cout).

class logstream { private: fstream *filestream; ostream *cout; public: logstream(fstream* filestream, ostream* cout) { this->cout = cout; this->filestream = filestream; } string operator<< (char* s) { *cout << s; *filestream << s; return s; } }; 

To use this, simply initialize it with cout and a fstream.

fstream lout = fstream("Filename", ios::app); logstream cout = logstream(&lout, &std::cout); 

And now you have what you wanted.

cout << "message"; 

EDIT: Don't forget to include

#include <iostream> #include <fstream> 

and

using namespace std; 

1 Comment

I would propose not to use "using namespace std", since you have to fiddle around between std::cout and the local cout. I dont think the std:: directive is too lengthy here...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.