2
votes

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

3
It would help if you put a little bit of source code, like how is the logger being called. What is the general format of the error messages, and what type are you using. From your question, it could be std::string, stream, or character arrays.Dennis Miller
The logger is designed to handle both std::string & const char* it handles bothjoy

3 Answers

4
votes

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.

0
votes

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.

0
votes

Check https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-5072104.html

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