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 tostd::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 streambuf
s 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.