3

I'm making a logger and I wish to have some kind of stream-like happenings going on, ideally doing CLogger << "Testing, " << 1 << ",2,3\n"; instead of CLogger->log("Testing, %i,2,3", 1);

My question is how would I do this? I don't want to directly create a stream to stdout as I want to use my own method which includes writing files and such. I've considered overloading with a certain struct that'd flush the current stream buffer to a method, but I'd have to do CLogger << flush << "Test!\n"; which is kind of odd.

Does anybody know how to do this?

Jookia
  • 6,544
  • 13
  • 50
  • 60

5 Answers5

13

If all that you need is directing certain log messages to files, have you considered std::ofstream?

Otherwise, I like to derive my logging class from std::ostream, so I get all of the stream goodness. The trick is to put all of your application-specific code in the associated streambuf class. Consider:

#include <iostream>
#include <sstream>

class CLogger : public std::ostream {
private:
    class CLogBuf : public std::stringbuf {
    private:
        // or whatever you need for your application
        std::string m_marker;
    public:
        CLogBuf(const std::string& marker) : m_marker(marker) { }
        ~CLogBuf() {  pubsync(); }
        int sync() {
            std::cout << m_marker << ": " << str();
            str("");
            return std::cout?0:-1;
        }

    };

public:
    // Other constructors could specify filename, etc
    // just remember to pass whatever you need to CLogBuf
    CLogger(const std::string& marker) : std::ostream(new CLogBuf(marker)) {}
    ~CLogger() { delete rdbuf(); }
};

int main()
{
    CLogger hi("hello");
    CLogger bye("goodbye");

    hi << "hello, world" << std::endl;
    hi << "Oops, forgot to flush.\n";
    bye << "goodbye, cruel world\n" << std::flush;
    bye << "Cough, cough.\n";
}

Notes:

  • The CLogger constructor can take whatever parameters you need to use -- a filename, an output language, a pointer to the underlying log data, whatever. Just pass the data onto the CLogBuf class.
  • The CLogBuf's sync() is automatically called during in response to std::flush.
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • @Robᵩ - don't you miss a return from function sync()? – Uri London Dec 04 '12 at 10:49
  • 1
    @Uri - Oops. Yes. Thank you for noticing. Fixed. – Robᵩ Dec 04 '12 at 14:56
  • I think your return statement of int sync() is still flawed. According to the definition (http://www.cplusplus.com/reference/fstream/filebuf/sync/) it returns 0 if true and -1 on failure. But you are currently returning either 1 or 0. So i think the correct return statement should look like this: return (std::cout ? 0 : -1); – scigor Jul 13 '15 at 08:30
6

Check out operator <<, which is what STL's streams overload.

class CLogger
{
public:
    CLogger& operator << (const std::string& _rhs)
    {
        // work with it here
        return *this;
    }; // eo operator <<
}; // eo class CLogger

EDIT:

See this page that outlines how std::ostream overloads operator << for different types:

http://www.cplusplus.com/reference/iostream/ostream/operator%3C%3C/

Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
2

Implement a proxy object that gives you operator<< and pass an ownership marker to the returned proxy object. When an object with the ownership marker dies, you flush the stream.

An easy way to do this would be to wrap ostringstream in an auto_ptr in your proxy and flushing to your logger when the auto_ptr is not null in the proxy's d-tor.

That'll give you the formatting possible with ostream, but still result in only one call to your logger, which I thought was the real problem.

Think of something like this:

class CLoggingProxy
{
public:
  template <class T>
  CLoggingProxy operator<<( const T& rhs )
  {
    if ( stream )
      *stream << rhs;
    return *this;
  }

  ~CLoggingProxy()
  {
    if ( stream )
      logger->log(stream->str());
  }

private:
  std::auto_ptr<std::ostringstream> stream; 
  CLogger* logger;

  friend class CLogger;
  CLoggingProxy( CLogger* logger ) // call this e.g. from the logger to "start" input
  : stream(new std::ostringstream), logger(logger) {}
};
ltjax
  • 15,837
  • 3
  • 39
  • 62
  • Sounds great, I've read that twice. Now on to figuring out what you said! – Jookia Dec 06 '10 at 22:48
  • I've added some code to clarify. It works because operator<< is evaluated from left to right, so log << a << b; becomes ((log< – ltjax Dec 07 '10 at 09:52
1

All of the operator<<() functions are defined on the class ostream, which you can inherit from and implement its methods.

Lou Franco
  • 87,846
  • 14
  • 132
  • 192
  • 1
    I don't *think* he is using ostream at all, I think he is asking about stream-*like* behaviour. – Moo-Juice Dec 06 '10 at 13:40
  • Right -- suggesting that he should inherit from ostream, implement the interface and then get everyone's << ops for free instead of implementing them himself. – Lou Franco Dec 06 '10 at 13:43
0

I'm just going to copy-paste my current implementation of this below, it does all you need (and handles things like std::endl and the like). AMBROSIA_DEBUGis macro defined in debug builds, so in theory, every call to this output class should be omitted in release builds (haven't checked though, but seems logical overhead is kept to a minimum. The functionality is based on QDebug functionality, plus a little addition of mine debugLevel, which would allow you to filter debug messages by hand in your code depending on a runtime parameter. Right now it also adds the same amount of spaces before each message.

// C++ includes
#include <iostream>
#include <string>

typedef std::ostream& (*STRFUNC)(std::ostream&);

#ifdef AMBROSIA_DEBUG
    static int debugLevel;
    const static int maxDebugLevel = 9;
#endif

class Debug
{
public:

    #ifdef AMBROSIA_DEBUG
    Debug( const int level = 0 )
    : m_output( level <= debugLevel ),
      m_outputSpaces( true ),
      m_spaces( std::string(level, ' ') )
    #else
    Debug( const int )
    #endif // AMBROSIA_DEBUG
    {}

    template<typename T>
    #ifdef AMBROSIA_DEBUG
    Debug& operator<<( const T &output )
    {
        if( m_output )
        {
            if( m_outputSpaces )
            {
                m_outputSpaces = false;
                std::cerr << m_spaces;
            }
            std::cerr << output;
        }
    #else
    Debug& operator<<( const T & )
    {
    #endif // AMBROSIA_DEBUG
        return *this;
    }
    // for std::endl and other manipulators
    typedef std::ostream& (*STRFUNC)(std::ostream&);
    #ifdef AMBROSIA_DEBUG
    Debug& operator<<( STRFUNC func )
    {
        if( m_output )
            func(std::cerr);
    #else
    Debug& operator<<( STRFUNC )
    {
    #endif // AMBROSIA_DEBUG
        return *this;
    }
private:
#ifdef AMBROSIA_DEBUG
    bool m_output;
    bool m_outputSpaces;
    std::string m_spaces;
#endif // AMBROSIA_DEBUG
};

Example usage:

int main()
{
    debugLevel = 9; // highest allowed in my app...
    Debug(4) << "This message should have an indentation of 4 spaces." << endl;
    Debug(8) << "This is a level 8 debug message.\n";
    return 0;
}
rubenvb
  • 74,642
  • 33
  • 187
  • 332