4

I'm using a 3rd party that sometimes has internal errors, when it happens the 3rd party writes to stderr and I can see it in the console.

(I do check the return values of the 3rd party functions and see that he's failed, but i want the information he writes to stderr)

I have a logger to whom I write using a method I have

SendLog(string log);

I want somehow to catch the things my 3rd party is writing to the stderr (maybe listen on the stderr some how) and then write this things to my log.

How can I do it ? I need it because my users can't see the console, the only see the log.

It is important to note, my program doesn't crash or exists after those errors, it's continues to work.

EDIT: My question is not the same as the allegedly similar question, I want to avoid using linker hacks (as been used in the similar question).

OopsUser
  • 4,642
  • 7
  • 46
  • 71
  • @BenjaminBannier: Close enough - there are answers there which don't depend on the `.rdbuf` behind `cout`. – MSalters Sep 09 '15 at 14:59
  • The answer you are refereing to does not answer my question, it uses ugly linking hacks. I'm looking for a way to do it by code. – OopsUser Sep 09 '15 at 15:09
  • there are multiple answers in the linked question. I'll agree that the link hack is ugly, but there's a time for ugly solutions: when there are no non-ugly solutions. – MSalters Sep 09 '15 at 15:17
  • I believe that even if there is another question that looks similar and has some "hacky" solution, we need to give humanity a chance to find a better solution before closing it and giving up. – OopsUser Sep 09 '15 at 15:21
  • The other question is not closed. New clean answers can still be added there. What we're trying to avoid on SO is that someone like you, who has the same question again needs to check a dozen questions each with one answer. Instead, we group all answers under one question. You can even award a bounty to bring the old question in the spotlights again. – MSalters Sep 09 '15 at 15:25

3 Answers3

3

One solution is to duplicate everything that is written to cerr into for example a file.

This is the helper class:

class CTee {
public:
    // Use ostream &s2 if you want to duplicate to an ostream, pass other
    // stuff you need if you have other logging mechanisms.
    CTee(ostream &s1, ostream &s2) : m_s1(s1), m_s1OrigBuf(s1.rdbuf()), m_teebuf(s1.rdbuf(), s2.rdbuf()) { s1.rdbuf(&m_teebuf); }
    ~CTee() { m_s1.rdbuf(m_s1OrigBuf); }

private:
    CTee &operator =(CTee &rhs);    // not implemented

    class CTeeBuf : public streambuf {
    public:
        // Use streambuf *sb2 if you want to duplicate to an ostream/streambuf.
        // Pass other Information if you want to log to something different.
        CTeeBuf(streambuf* sb1, streambuf* sb2) :  m_sb1(sb1), m_sb2(sb2) {}

    protected:
        virtual int_type overflow(int_type c) {
            if(streambuf::traits_type::eq_int_type(c, streambuf::traits_type::eof()))
                return c;
            else {
                // Put char to cerr/stream to duplicate
                m_sb1->sputc((streambuf::char_type)c);
                // Put char to duplicate stream. If you want to duplicate to something
                // different, then write the char whereever you want to.
                return m_sb2->sputc((streambuf::char_type)c);
            }
        }
        virtual int sync() {
            m_sb1->pubsync();
            return m_sb2->pubsync();
        }

        // Store streambuf *m_sb2 if you want to duplicate to streambuf.
        // Store anything else if you want to duplicate to something different.
        streambuf *m_sb1, *m_sb2;
    };

    ostream &m_s1;
    streambuf * const m_s1OrigBuf;
    CTeeBuf m_teebuf;
};

CTee takes an ostream to duplicate and an ostream to duplicate to. It takes the ostream that shall be duplicated and replaces it's rdbuf, the streambuf that is written to, with a CTeeBuf (see CTee ctor). CTeeBuf takes the chars that are written to it and forwards them to the streambufs of both ostreams (see CTeeBuf::overflow and CTeeBuf::sync). The CTee dtor reverts the changed streambufs to their original values.

And it is used like this:

char logfilename[] = "myfile.log";
ofstream logfile(logfilename, ios_base::out | ios_base::app);
CTee tee(cerr, logfile);

From now on everything written to cerr will be duplicated to logfile (during the lifetime of tee). So this message will be written to cerr, but also to logfile:

cerr << "error occured: ..." << endl;

It is also possible to write to other ostreams than a logfile. If you don't want to duplicate to another ostream but to something else, just replace CTeeBuf::overflow by an implementation that logs whereever you want to.

See also http://www.cs.technion.ac.il/~imaman/programs/teestream.html and http://wordaligned.org/articles/cpp-streambufs.

Werner Henze
  • 16,404
  • 12
  • 44
  • 69
  • Can you write some explanation to the "tee" class and the CTeeBuf class ? – OopsUser Sep 09 '15 at 14:52
  • How can I call a function and pass it the string that currently written to the buf ? – OopsUser Sep 09 '15 at 15:12
  • @OopsUser I adjusted my answer with some comments in the code where you need to do your changes. You start with changing the overflow function to call whichever function you want to notify for every char (sorry, only for chars, not for strings). You'll then see which members you need additionally in CTeeBuf and how to adjust the ctor. – Werner Henze Sep 09 '15 at 15:39
  • Thanks werner, so the sync function actually could be deleted ? do i have a way to know if someone finished with his write to stderr ? will the last char will be null ? – OopsUser Sep 09 '15 at 15:52
  • @OopsUser `sync` must still call `m_sb1->pubsync`, so you still need it. You will not see the (string termination) null bytes as they would also not be in files you write to. If you don't want to write char by char you will need your own buffer logic to detect for example when a line is complete ('\n' detected) and then write the complete line to your own log. – Werner Henze Sep 09 '15 at 15:58
1

One way is to use stringstream for this. If library write using c++streams that will work.

class redirect_stream
{
public:
   redirect_stream(std::ostreamstream& stream, std::ostream& oldstream) :
   stream_(stream), oldstream_(oldstream)
   {
      oldbuf_ = oldstream_.rdbuf();
      oldstream_.rdbuf(stream_.rdbuf());
   }
   ~redirect_stream()
   {
      const std::string str = stream_.str();
      if (!str.empty())
      {
         SendLog(str);
      }
      oldstream_.rdbuf(oldbuf_);
   }
private:
   std::ostringstream& stream_;
   std::ostream& olstream_;
   std::streambuf* oldbuf_;
};

And before usage of 3rd party library just:

std::ostringstream oss;
redirect_stream redirecter(oss, std::cerr);

or you can not print message to log in destructor and just print oss.str() after the end of work with 3rd party library.

Simple usage example

ForEveR
  • 55,233
  • 2
  • 119
  • 133
  • You assume that my program exists after the error ? it is not the case. i use the 3rd party for the entire life time of the program. and while it running i want to get the logs – OopsUser Sep 09 '15 at 14:28
  • @OopsUser why exit? that's just destructor, object redirecter will be destroyed after scope. – ForEveR Sep 09 '15 at 16:57
0

You can use std::stringstream

std::stringstream log;
std::streambuf *buf = std::cerr.rdbuf(log).rdbuf());
std::cerr << "Error Msg" << std::endl;
std::string errMsg( log.str() );

errMsg will be "Error Msg".

Gombat
  • 1,994
  • 16
  • 21