Threadsafe logging
Asked Answered
S

4

7

I want to implement a simple class for logging from multiple threads. The idea there is, that each object that wants to log stuff, receives an ostream-object that it can write messages to using the usual operators. The desired behaviour is, that the messages are added to the log when the stream is flushed. This way, messages will not get interrupted by messages from other threads. I want to avoid using a temporary stringstream to store the message, as that would make most messages at least twoliners. As I see it, the standard way of achieving this would be to implement my own streambuffer, but this seems very cumbersome and error-prone. Is there a simpler way to do this? If not, do you know a good article/howto/guide on custom streambufs?

Thanks in advance,

Space_C0wbo0y

UPDATE:

Since it seems to work I added my own answer.

Squire answered 9/12, 2009 at 10:3 Comment(1)
Seems like a good solution! Perhaps add it as your own answer?Latchet
S
1

So, I took a look at Boost.IOstreams and here's what I've come up with:

class TestSink : public boost::iostreams::sink {
public:
    std::streamsize write( const char * s, std::streamsize n ) {
        std::string message( s, n );
            /* This would add a message to the log instead of cout.
               The log implementation is threadsafe. */
        std::cout << message << std::endl;
        return n;
    }
};

TestSink can be used to create a stream-buffer (see stream_buffer-template). Every thread will receive it's own instance of TestSink, but all TestSinks will write to the same log. TestSink is used as follows:

TestSink sink;
boost::iostreams::stream_buffer< TestSink > testbuf( sink, 50000 );
std::ostream out( &testbuf );

for ( int i = 0; i < 10000; i++ )
    out << "test" << i;

out << std::endl;

The important fact here is, that TestSink.write is only called when the stream is flushed (std::endl or std::flush), or when the internal buffer of the stream_buffer instance is full (the default buffer size cannot hold 40000 chars, so I initalize it to 50000). In this program, TestSink.write is called exactly once (the output is too long to post here). This way I can write logmessage using normal formatted stream-IO without any temporary variables and be sure, that the message is posted to the log in one piece when I flush the stream.

I will leave the question open another day, in case there are different suggestions/problems I have not considered.

Squire answered 9/12, 2009 at 12:37 Comment(0)
Z
5

Take a look at log4cpp; they have a multi-thread support. It may save your time.

Zenobia answered 9/12, 2009 at 10:10 Comment(2)
Thanks for you answer. However, due to restriction of my working environment I cannot introduce a new library. Also, most existing logging libraries are far too heavyweight for my purposes.Pavia
If looking for a more liberal license, see Log4cxx by Apache. logging.apache.org/log4cxx/index.htmlKnocker
S
1

So, I took a look at Boost.IOstreams and here's what I've come up with:

class TestSink : public boost::iostreams::sink {
public:
    std::streamsize write( const char * s, std::streamsize n ) {
        std::string message( s, n );
            /* This would add a message to the log instead of cout.
               The log implementation is threadsafe. */
        std::cout << message << std::endl;
        return n;
    }
};

TestSink can be used to create a stream-buffer (see stream_buffer-template). Every thread will receive it's own instance of TestSink, but all TestSinks will write to the same log. TestSink is used as follows:

TestSink sink;
boost::iostreams::stream_buffer< TestSink > testbuf( sink, 50000 );
std::ostream out( &testbuf );

for ( int i = 0; i < 10000; i++ )
    out << "test" << i;

out << std::endl;

The important fact here is, that TestSink.write is only called when the stream is flushed (std::endl or std::flush), or when the internal buffer of the stream_buffer instance is full (the default buffer size cannot hold 40000 chars, so I initalize it to 50000). In this program, TestSink.write is called exactly once (the output is too long to post here). This way I can write logmessage using normal formatted stream-IO without any temporary variables and be sure, that the message is posted to the log in one piece when I flush the stream.

I will leave the question open another day, in case there are different suggestions/problems I have not considered.

Squire answered 9/12, 2009 at 12:37 Comment(0)
D
1

You think log4cpp is too heavy and you reach for Boost.IOStreams instead? Huh?

You may wish to consider logog. It's thread-safe for POSIX, Win32 and Win64.

Downer answered 25/1, 2012 at 8:58 Comment(1)
If boost is already in his set of libraries, then it's not that heavy weight. Otherwise, totally agree with you.Cornellcornelle
J
0

Re. your own response. If you are using this for error logging and you program crashes before flushing your stream then you logging is a bit useless isn't it?

Jenson answered 9/12, 2009 at 13:20 Comment(2)
I guess that would be a problem with any logging approach.Pavia
Except for logging systems like ETW which have outside help.Renaissance

© 2022 - 2024 — McMap. All rights reserved.