views:

58

answers:

3

I am very new at this and apologise if my question is not clear.

I have created a thread safe logger in C++. This logger will be used in a large program & will be called from multiple places. I am using a singleton so there is only one instance of the logger. This logger outputs to a file & to the console. It behaves similar to cout; it takes in a string from another file, ( concatenates it if necessary), stores the peices in a buffer until the string is done then outputs using cout. The string is being stored as a const char*. Right now the mutexes are being locked in one function and unlocked in another function ( this is were my problem is) which overloads the endl operator.

My problem is that this function (where the mutexes are unlocked )only works if the user writes endl in the other files where the logger is being called. I need this to be a versatile utility which will NOT rely on what the user writes since a user may not use endl or may use it too often. I now need some means for my logger to identify when the string ( from the other file) is done so that it can empty out the buffer. Currently endl is like a keyword & i need some means to make it work without any key words.

I was initially thinking i could find some means to check for the "\0" terminating character in the string then using that check to know that the string is done and then emptying out the buffer. However, i get out of bounds errors when i do this.

Thank you for your time

A: 

Typically it's a bad idea to have mutex lock in one function and unlock in another. It should be locked and unlocked in the same function.

I created something similar, and typically I made a C++ class called Error.

That way the user creates an Error object, and that error object handles all the termination stuff. Then the error object gets sent to the ErrorLogger's Queue, and the error logger terminates when the ErrorLogger Queue is empty. Then you don't have to worry about mutexes, because the ErrorLogger has time to process out of the queue.

Dennis Miller
Dennis, being new to this, i will look into your proposed solution and see if it can work for me. thank you for your reply. However, i dont know if i can get rid of the mutexes. This is a very complex logger it has many added functionalities that i did not mention above. But they need to be done between the locking and unlocking. I use the mutexes elsewhere to ensure that the the logger is threadsafe and functional with these functionalities. Nevertheless, I still need some means to know when the string is complete. How can i check if a string is done without some keyword?
joy
I need to know more about the string. Is it just a std::string, character array, or a stream?I'm assuming you use mutexes in order to prevent another thread from posting a current error message while your writing an old one to file correct?
Dennis Miller
if you reading from a file, can you keep track of how long each line in that file is? std::string line; getline (inputFile,line); int lengthInCharacters = line.length()
Dennis Miller
A: 

Check http://articles.techrepublic.com.com/5100-10878_11-5072104.html

The idea is to create thread-local "proxies" which will call actual thread-safe logging function.

phadej
+4  A: 

I'm not quite sure I get the situation, but it sounds like you want a proxy:

class LogSingleton
{
public:
    LogSingleton& instance() { /* ... */ }

    void lock(); // lock mutex
    void unlock(); // unlock mutex

    template <typename T>
    friend LogSingleton& operator<<(LogSingleton& pLog, const T& pX)
    {
        // needs to be locked first
        assert(is_locked()); 

        /* output pX however */

        return pLog;
    }
};

class LogProxy
{
public:
    LogProxy()
    {
        // manage lock in proxy
        LogSingleton::instance().lock();            
    }

    ~LogProxy()
    {
        LogSingleton::instance().unlock();            
    }
};

// forward input into the proxy to the log, knowing it's locked
template <typename T>
LogProxy& operator<<(LogProxy& pProxy, const T& pX)
{
    LogSingleton::instance() << pX;

    return pProxy;
}

// now expose proxy
typedef LogProxy log;

And you'd do this:

log() << "its locked now" << "and the temporary will die" << "here ->";

The locking is done in the constructor and destructor, and the destructor is called at the end.


As Tony correctly points out, this holds the lock unnecessarily long. The lock is only needed for the "final" output to the LogSingleton. Imagine this:

log() << "this next function takes 5 minutes"
        << my_utterly_crappy_function() << "ouch";

Nothings getting logged yet the mutex is locked for a long time. Better would be to buffer-up output then output it all at once:

class LogProxy
{
public:
    ~LogProxy()
    {
        // manage lock in proxy
        LogSingleton::instance().lock();

        // no-throw, or wrap mutex use in a scoped-lock
        LogSingleton::instance() << mBuffer.rdbuf();

        LogSingleton::instance().unlock();            
    }

    // buffer output
    template <typename T>
    friend LogProxy& operator<<(LogProxy& pProxy, const T& pX)
    {
        mBuffer << pX;

        return pProxy;
    }

private:
    std::ostringstream mBuffer;
};

Now no locks are acquired until the buffer is ready to be outputted.

GMan
If the application is logging a lot of data, then this solution could seriously compromise performance. While it's less efficient for light logging or when one thread is doing almost all the logging, it's more scalable to concatenate the output in a ostringstream then take the singleton's lock only when ready to write.(On some systems a single write() to a stream will be performed while the stream/file holds it's own mutex lock, which guarantees atomicity - but that's not portable).
Tony
@Tony: Good point. I'll add that.
GMan