A smart logger

At work I’ve been asked to design a C++ logger class to be used like this:

Logger logger;
logger << "The universe will collapse in " << remaining_time << " seconds";

This seams like a very easy task, but has some hidden difficulties:

  1. A header should be automatically added when logging (with a timestamp, for example), but only at the first occurence of the insertion operator.
  2. There’s no termination character or manipulator to inform the logger object that a line is complete.
  3. The whole thing should be thread-safe at statement level, that is, you don’t want multiple concurrent logging statements to overlap in the logfile.

At first I thought that it was impossible to achieve all of this without altering the API, but after a bit of experimentation with the lifetime of temporary objects and the ownership transfer semantics of auto_ptr, I’ve come out with a solution based on a technique similar to template expressions.

The solution revolves around a template helper object like the following:

template <typename Helper, typename T>
class LoggerHelper { /* ... */ };

which is returned by value in each call to operator<<. The helper encapsulates a pointer to the previous helper (of type Helper) and to the logger value (of type T), plus a boolean flag active. The helper constructor takes a non const reference to the previous helper, deactivates it and sets itself as the active helper: it will be the one to actually perform the logging action, unless another helper steps in and takes control.
The actual logging is performed in the active helper destructor, which is called at the end of the logging statement. The deactivation of all the encapsulated helpers assures that only one active helper exists at any time. The logging procedure involves locking a global mutex, writing to a log stream and then unlocking it, so that multiple logging statements do not overlap.
This solution works for logging temporary objects as well, because they are guaranteed to be destroyed in the inverse order of their creation: since the helper objects will always be created later, their destructors will be called before the destruction of the temporaries, so they are still valid when they are forwarded to the actual stream.

Here is the code, simplified to avoid dependencies.

Advertisements

7 Responses to “A smart logger”

  1. stregatto Says:

    Very interesting, i’m always happy when someone shares a smart solution to a problem that may occur in many different contexts 🙂

  2. WoundedLion Says:

    Very nice solution. One question, though. Why return the helper object by value? Why not simply have the helper’s operator<<() collect the log message in an internal stringstream and return itself by reference? Upon destruction, it could forward the contents of the stringstream to the actual log. This would remove the need for any of the activation/deactivation logic. Seems much simpler… What am I missing?

  3. pcapriotti Says:

    If operator<< returns a simple pointer, the pointed object will never be destroyed, so it won’t have a chance to actually perform the logging (and will leak memory).
    Whether it prints directly or collects messages and prints afterwards does not seem to make much difference in that respect.

  4. WoundedLion Says:

    Hmmm….I’m not talking about returning a pointer to a heap-allocated object, but rather a reference to the temporary helper object, which will be destroyed at the end of the statement.

    So, for instance:

    Logger log;
    log(ERROR) << “blah” << “blah” << “blah”;

    log(ERROR) creates a temporary LogHelper object and returns it by value. Each call to LogHelper::operator<<() appends the argument to its internal stringstream and returns itself by reference (LogHelper&). At the end of the statement, the temporary LogHelper is destroyed and its destructor forwards the contents of the stringstream to the log.

    Anyway, just an idea to remove some complexity. Your solution is still very clever indeed.

  5. WoundedLion Says:

    Sorry, I changed the API a bit. To keep it the same:

    Logger log;
    log << “blah” << “blah” << “blah”;

    Logger::operator<<() returns a temporary LogHelper by value.
    LogHelper::operator<<() returns itself by reference

  6. pcapriotti Says:

    Yes, that’s a simpler way to do it. Good idea. However, I’m afraid it can get complicated if you try to keep track of the objects passed to the logger instead of serializing them immediately.

    Ah, while we’re at it, I have a question about the logger(…) function of your example: is it guaranteed that the compiler will optimize away the implicit copy constructor call when you return the local Helper object by value? If it is not so, I think you will have to add the ownership-transfer semantics logic back to your helper class.

  7. WoundedLion Says:

    Good point. Return value optimization is not guaranteed, but merely allowed, by the standard and implemented in most compilers worth their salt.

    To make it foolproof, however, you could add an “active” flag to the LogHelper which is set by LogHelper::operator<<(). The destructor would not forward the log message unless this flag was set.

    Then, at least you would still avoid the complexity of implementing the full ownership transfer logic and also the cost all of those intermediate LogHelper constructor calls.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: