Bregmadev

Bregmasoft Development Weblog

Name:
Location: The Back Woods, Ontario, Canada

Thursday, October 13, 2005

Building a C++ Logger, Part I

I've written a lot of server-side software over the decades, and with that wort of software comes the need to communicate information to the server administrator. Now, I've seen all sorts of fancy libraries that try do do everything for everyone, but they've never done anything for me. I'm a C++ developer. I don't need a Java solution and I don't need a source code massager solution to make up for language inadequacies. What I want is something that's very simple to use from within code, has very little to no impact on code when not in use, and leaves the code clean and readable. I also don't want the source of log messages to determine the destination of long messages. What I want to be able to do is to stream information to std::cerr (or std::clog, or std::cout), and have the information tagged with the time, message severity level, process or thread ID, and software component. Any of these additional data should be optional.

Enhancing a standard stream

One of my requirements is that I want to just send my output to std::cerr. It turns out that a solution that will work with std::cerr will work with any std::ofstream object. What I want to do is to implement my own std::basic_ostreambuf class, wrapping the existing rdbuf from the ostream, and appending log information to any messages sent through. Here's a start of such a class.
#include <streambuf>

template<typename Char, typename Traits = std::char_traits<Char> >
class DebugStreambuf
: public std::basic_streambuf<char traits="">
{
public:
  typedef typename std::basic_streambuf<Char, Traits>::traits_type traits_type;
  typedef typename std::basic_streambuf<Char, Traits>::int_type    int_type;
public:
  DebugStreambuf(std::basic_streambuf<Char, Traits> *pRealBuf);

protected:
  int_type overflow(int_type c = traits_type::eof());

private:
  DebugStreambuf(const DebugStreambuf&);            ///< unimplimented
  DebugStreambuf& operator=(const DebugStreambuf&); ///< unimplimented


  std::basic_streambuf<Char, Traits> *m_pRealBuf;
  bool                                      m_isAtBeginningOfLine;
};
The trick here is to grab the ostream's existing streambuf, intercept any overflow() calls, and attach useful log data at the beginning of each line. Since regular streambufs aren't copyable, it's better to make this one noncopyable too by declaring, but not defining, the copy costructor and assignment operator so the linker will give an error if an erroneous attempt is made to copy the buffer. The only member functions required are the constructor and the overflow() operation. The constructor is simple, since all it has to do is initialized the members.
template<typename C, typename T>
DebugStreambuf<C,T>::
  DebugStreambuf(basic_streambuf<C,T> *pRealBuf)
  : m_pRealBuf(pRealBuf)
  , m_isAtBeginningOfLine(true)
  { }
The overflow operator is a little hairier. For this first pass, I'm just going to prepend a marker (a bang) onto each line. Fancier stuff comes with a later installment of this series.
template<ypename C, typename T>
typename DebugStreambuf<C,T>::int_type DebugStreambuf<C,T>::
  overflow(int_type c)
  {
    int_type retval = traits_type::not_eof(c);
    if (!traits_type::eq_int_type(c, traits_type::eof()))
    {
      if (m_isAtBeginningOfLine;)
      {
        // Prepend a dummy character for now.
        m_pRealBuf->sputc('!');
        m_isAtBeginningOfLine = false;
      }
      // Send the real character out.
      retval =  m_pRealBuf->sputc(c);

      // If the end-of-line was seen, reset the beginning-of-line indicator and
      // the default log level.
      if (traits_type::eq_int_type(c, traits_type::to_int_type('\n')))
      {
        m_isAtBeginningOfLine = true;
      }
    }

    return retval;
  }
Finally, to make things really simple for the developer, I'd like to provide a simple function that will convert a regular std::ostream object into a logger stream.
#include <ostream>

void convertToLogger(std::ostream& ostr)
{
  ostr.rdbuf(new DebugStreambuf<char>(ostr.rdbuf()));
}
Now, to use this new logging facility, just wrap it around std::cerr and away we go.
#include <iostream>

using namespace std;

int main(int, char*[])
{
  convertToLogger(cerr);
  cerr << "This is a test.\nThis is the second test line.\n";
} 
Next time: adding fancier logging info to each line of output. For more information on using IOStreams, the best reference available is Langer & Kreft.

Thursday, August 18, 2005

The beginning....

This is the first post in my online developer's diary. This is just a place I can jot down a few notes while I'm online without losing them on my desk, or whever the heck it is those things go as soon as I turn my back.

Everything on these pages is copyrighted by Bregmasoft and Stephen M. Webb