views:

100

answers:

4

I'm trying to find a decent way to do logging from C++. My current solution is this:

ostream & GetLog() { if( output == NULL ) throw error; return *output; }

Where output is defined somewhere and can be a file or whatever. This is fine, but it doesn't let me do anything other than throw an error if output is not allocated. Also, my program is multithreaded, and I need to obtain a lock in order to correctly check if output is not NULL and then write to it if it is not. Ideally, any code that uses GetLog() should obtain that lock:

{
    LockLog lock;
    if( HasLog() )
        GetLog() << "My dog ate " << n << " cookies!" << endl;
}

This seems like too much verbiage to me. I'd like to do something like just

GetLog() << "My dog ate " << n << " cookies!" << endl;

and have it work without an error when the log's not allocated (and with lock), or a function like

WriteLog( "My dog ate " << n << " cookies!" << endl );

I know with C printf syntax this can be done with variable argument function. Is there a way to do this with C++ syntax and without a macro that would force me to expose the GetLog, HasLog, and LockLog functions anyway?

+2  A: 

like this?

class Log {
    class buffer {
        buffer(...);
        ~buffer() {
            Lock lock(mutex);
            // write in destrcutor
        }
        string data;
        Mutex &mutex;
    };
    Mutex mutex;
...
};

template<class T>
Log::buffer operator<<(Log& l, T t) {
    return t;
}

template<class T>
Log::buffer& operator<<(Log::buffer& b, T t) {
    return b += t;
}

Log log;

log << "blah" << 6;
aaa
That might work poorly in a MT environment, since you only lock for each '<<' operator invocation...
Nick
@Nick fair enough, give me sec
aaa
Shouldn't lock be a singleton/monostate kind of a class?
Chubsdad
@Chu fair enough, give me sec
aaa
A: 

From experience, I would write a function which takes a string (or list of strings), and locks/logs inside the function. Leave the formatting outside the actual logging function, so you ensure that everything gets logged in the same block for a single logical log message.

You could do something like I did to handle the formatting, which is: create a string-derived class with a printf-style constructor, and use it inline.

So, for example:

class PFString : public string
{
public:
    PFString( const char* pcszFormat, ... )
    { ... }
};

Log( PFString( "something with number %d", 42 ) );

Of course, you're free to format the string other ways too (eg: C++ operator style syntax, resource string formatting, etc.).

Nick
+1  A: 

For the formatting you might try boost::format

http://beta.boost.org/doc/libs/1_44_0/libs/format/index.html

Mark
A: 

Use log4cxx, which is a

decent way to do logging from C++

that you are unlikely to be able to match in a timely way. Even if this seems like overkill now you may find that your diagnostic needs grow as your system does, making the logging component a time sink that was not envisaged at the start.

I find I both save time, and learn good design from better developers, when I use well-designed, robust frameworks like this (cf. Boost, STL).

Steve Townsend