Logging using ostream and streambuf
I architected all of my subsystems in the VDrift refactor to receive ostream objects that they use to output logging information/errors. This is convenient because it keeps all of my subsystems independent from a central logging class; all they have to know about is std::ostream. Of course, to get any advanced functionality out of this I realized I’d need to create a class that inherits from the streambuf class and then create my ostreams with the special logger streambuf. I did exactly that today and have posted the results below. The way it’s currently designed, you pass your logger streambuf a prefix and a forwarding ostream. Then, when you use the ostream that’s using your logger streambuf, the streambuf will attach your prefix and send the output to the forwarding ostream. In the future I might create support for multiple forwarding ostreams with filter levels — say I want to send some errors to the std output, everything to a log.txt file, and errors again to a stringstream that i can use to access the errors from within my program (for dialog boxes, etc).
class logstreambuf : public std::streambuf
{
public:
typedef int int_type;
typedef std::streampos pos_type;
typedef std::streamoff off_type;
typedef std::char_traits traits_type;
protected:
std::string buffer;
const std::string prefix;
std::ostream & forwardstream;
public:
logstreambuf(const std::string & newprefix, std::ostream & forwardee) :
prefix(newprefix),forwardstream(forwardee) {}
std::string str() const
{
std::string ret;
if (this->pptr())
{
ret = std::string(this->pbase(), this->pptr());
}
else
ret = buffer;
return ret;
}
protected:
virtual int_type overflow(int_type c = traits_type::eof())
{
const std::string::size_type capacity = buffer.capacity();
const std::string::size_type max_size = buffer.max_size();
/*std::cout << "Overflow: \"" << (char)c << "\" (" << (int)c << ")" << std::endl;
std::cout << "Capacity: " << capacity << std::endl;
std::cout << "pptr: " << this->pptr() - this->pbase() << std::endl;
std::cout << "epptr: " << this->epptr() - this->pbase() << std::endl;
std::cout << "alignment check: " << this->pbase() - buffer.data() << std::endl;*/
// Try to append __c into output sequence in one of two ways.
// Order these tests done in is unspecified by the standard.
const char conv = traits_type::to_char_type(c);
char * bufferend = const_cast(buffer.data());
if (this->pptr() >= this->epptr())
{
// NB: Start ostringstream buffers at 512 chars. This is an
// experimental value (pronounced "arbitrary" in some of the
// hipper english-speaking countries), and can be changed to
// suit particular needs.
//
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 169. Bad efficiency of overflow() mandated
// 432. stringbuf::overflow() makes only one write position
// available
std::string::size_type opt_len = std::max(std::string::size_type(2 * capacity),
std::string::size_type(512));
if (this->epptr() - this->pbase() < capacity)
opt_len = capacity;
const std::string::size_type len = std::min(opt_len, max_size);
std::string tmp;
tmp.reserve(len);
if (this->pbase())
tmp.assign(this->pbase(), this->epptr() - this->pbase());
tmp.push_back(conv);
buffer.swap(tmp);
std::string::size_type o = this->pptr() - this->pbase();
char * newbasep = const_cast(buffer.data());
this->setp(newbasep, newbasep + buffer.capacity());
this->pbump(o);
}
else
*this->pptr() = conv;
this->pbump(1);
return c;
}
virtual int_type sync()
{
std::string myoutput = prefix + str();
for (int i = 0; i < myoutput.length()-1; i++)
{
if (myoutput[i] == '\n')
myoutput.insert(i+1,prefix.length(),' ');
}
forwardstream << myoutput << std::flush;
char * newbasep = const_cast(buffer.data());
this->setp(newbasep, newbasep+buffer.capacity());
pbump(newbasep-this->pptr());
}
};
int main()
{
logstreambuf joebuf("INFO: ",std::cout);
std::ostream os(&joebuf);
os << "Test stream 1" << std::endl;
os << "Test stream 2\n";
os << "Test stream 3" << std::endl;
os << "Test stream 4" << std::endl;
return 0;
}