Welcome to Steve’s Webshed

Games, Screensavers, and Photo Utilities
Tips for C++ and C++Builder



Gotta website? Wanna earn some cash? Sign up to be an affiliate!

Google
Search Now:
In Association with Amazon.com

Snip #2: A Logging Library


One piece that's "missing" from the standard I/O streams is a logging facility. I say "missing" in quotes because every programmer needs a facility at one time or another, but rarely do any two programmers agree on what to log or how to log it. The logstream class I present here can't solve the "what", but it at least takes a stab at solving the "how".

A note on prerequisites: this class requires the overloaded operator << for stream struct tm, provided in the previous code snip.

The code for this snip is too long to post in its entirety, so you can download the code here. Before you grab the code and run, you should know that the logstream class is wrapped in the "slug" namespace. Slug stands for "Steve's Library of Useful Goodies".

For those who are interested, what follows is a brief description of what it is and how it does it.

I've played with a variety of logging schemes in the past, so I had a good idea of what I wanted of this one when I started it. My requirements were:

  1. Each entry in the output must be timestamped with the date and time
  2. Each entry should have an associated "level"
  3. Output should be filtered by level - if a level is off, any output at that level should be disabled
  4. It should work pretty much as though it were a part of the iostream library

At first this seemed like a task beyond mere mortal men. The idea was this: I wanted to write a line like:

mylog << level(llError) << "Something really bad just happened\n";

The problem here was getting the date and time inserted into the front of the output, so that it would look something like:

Fri 11/30/1990 10:54:56  PM [ERROR] Something really bad just happened

Then I came across Dietmar Kühl's webpage, where he describes ways to prefix data to the beginning of a line of output. After reading his site and a bit of experimenting, the problem ended up being very simple.

The key to solving the problem lies in the fact that all the real work in the iostreams library is done by stream buffers. What I had to do was create a specialized stream buffer that, whenever a new line was to be output, first output my line prefix.

Here's the declaration for my log buffer class:

class logbuf : public std::streambuf
{
public:
    explicit logbuf(std::streambuf *sb);
    virtual ~logbuf();

    void     level(unsigned level);
    unsigned level(void) const;

    void suppress(bool value);
    bool suppress(void) const;

protected:
    int overflow(int ch);
    int sync();

private:
    class logbufImpl *impl; // hides implementation to reduce dependencies
};

The two key functions here are overflow and sync. The level functions are setters and getters for the current logging level, and suppress is used to suppress prefix output; I use that to output a new divider every time the log is closed.

Overflow is called every time the stream buffer fills up. Normally, this is after some predefined number of characters. In our case, the log stream is unbuffered, so every character goes through overflow. Now, this doesn't mean I'm not buffering the output, only that logbuf isn't buffering the output. The logbuf passes everything on to the streambuf you pass in the constructor; that classes job is to do the buffering.

Sync's job is to synchronize the buffers with the actual output. In this case we just call the contained stream buffer's pubsync function.

So that's the hard part, the rest of the job is just wrapping the log buffer in something we can use. I use two classes to do this. The first is log_base. It creates a file buffer, then creates the log buffer and passes a pointer to the file buffer to it. It also contains a few other convenience items, like some predefined logging levels and such.

Using this base class isn't just convenient, though, it's a bit of a hack. The actual logstream class descends from ostream. In order to have our log buffer actually get used, the ostream constructor must get a pointer to it. In C++, base classes are always constructed before members. In order to initialize the ostream with a log buffer, it had to be created first, and the only way to do that was to stick it in a base class that gets constructed before the ostream.

Enough of my cheap tricks. The logstream class itself just pulls everything together. It has methods for setting the output level of the next output, and for filtering the levels that will be output. It also provides a method for outputting hex data.

Finally, I provided a templated overloaded operator<< so I can output anything to a logstream that can be output to an ostream. I had trouble with overload resolution for const char*, so a specific operator is provided for that type. These overloaded operators actually do the job of filtering the output.

I almost forgot. I also provide a simple manipulator for setting the output level. I've actually found this to not be very convenient (too much typing, code doesn't line up), so I had the level member of the logstream class return a self reference. So, you now have two options for outputting log data:

mylog << setlevel(log_base::llError) << "Something bad happened\n";
mylog.level(log_base::llWarning) << "Something maybe bad, maybe not happened\n";

So that's it. You still need to create a logstream object and call open on it to open a file; I just create a global one, then open the output file in main. After that, using it works just like using an ostream.

In case you missed it earlier, you can grab the code here.

Latest Releases
Webifier 1.1.0 - 11/14/2007 - Show your pictures to the world with this web photo gallery creator.
PictureSaver 4.2.5 - 03/10/2006 - Turn your pictures into a slideshow screensaver; great with digital cameras!
RandomSaver 2.0.3 - 06/14/2002 - Got a lot of screen savers? Why watch just one? RandomSaver lets you watch 'em all!
GullBlaster 1.1 - 05/12/2002 - Tired of them seagulls? Now you can get even! Blast them before they blast you!
FontViewer 1.1.1 - 06/13/2002 - Preview all the installed fonts on your system.

Firefox 2
Get Thunderbird!

Monitor page
for changes

it's private
powered by
ChangeDetection

Valid HTML 4.01 Transitional


This page has been visited 541 times. View usage statistics.