ManiAm
ManiAm

Reputation: 1841

C++ #define macro for data logging

I have a vlog class that is responsible for printing all the log messages in the program. Each class can include the vlog.h header and then use the following code to log data:

{
    std::lock_guard<std::mutex> lock(vlog::lock_log);
    vlog::EVENT(host) << "hello" << " world!" << std::flush;
}

I want to change the above code to a simple macro like this:

EVENT_LOG_C(host) << "hello" << " world!" << std::flush;

The macro should call the lock_guard first and then pass the message to the vlog::EVENT(host) object. How can I define the macro?

Edit: I want the whole log msg (which may consists of multiple <<) to be atomic. Each use of << calls the static object and I can not put the lock_guard inside the vlog class itself.

Upvotes: 1

Views: 859

Answers (2)

Quentin
Quentin

Reputation: 63124

Just stuff the std::lock_guard inside your macro, but making it part of the logging expression :

#define EVENT_LOG_C(host) \
    std::lock_guard<std::mutex>{vlog::lock_log}, vlog::EVENT(host)

The comma operator in there ensures that the temporary lock is constructed before anything happens on the right-hand side, and it will survive until the semicolon at the end.


You could also get rid of the macro by having the first operator << return a locking proxy, that then forwards every following << until the end of the expression.

// Simple wrapper around the std::lock_guard to ease operator overloading
struct EventLogLocker {
    EventLogLocker(std::mutex &mtx)
    : lck{mtx} { }

    std::lock_guard<std::mutex> lck;
};

// Perfect-forwarding operator << that hooks on the EventLogLocker
template <class T>
EventLogLocker const &operator << (EventLogLocker const &ell, T &&obj) {
    // Log here
    std::cout << std::forward<T>(obj);
    return ell;
}

// operator << for iostream manipulators (these can't be deduced by the
// template above because they're overloaded for different stream types)
EventLogLocker const &operator << (
    EventLogLocker const &ell,
    std::ostream &(*manip)(std::ostream &)
) {
    std::cout << manip;
    return ell;
}

// Stub function, the important thing is to return an EventLogLocker by value
EventLogLocker EVENT_LOG_C(...) {
    return {lock_log};
}

Upvotes: 4

Peter
Peter

Reputation: 36597

All you need to do is lock the mutex within the constructor of a class that supports streaming operations, and release the mutex in the destructor.

A macro is not needed.

For example;

  namespace vlog
  {
       class EVENT
       {
          public:

           EVENT() : event_lock(vlog::lock_log)
           {

           };

           template<class T> EVENT &operator<<(const T &v)
           {
               std::cerr << v;
               return *this;
           };            

           private:

              std::lock_guard<std::mutex> event_lock;

       };
  }

In the above, for sake of discussion, I've assumed your logging will be to std::cerr, and that the constructor of EVENT accepts no parameters. That can be easily changed.

Then all you need to do is

   vlog::EVENT() << "hello" << "world" << std::flush;

The working of this statement is that vlog::EVENT will be constructed first. The constructor grabs the mutex. Then the mutex will continue to be grabbed during each streaming operations. At the end of the statement (actually the expression within it), the vlog::EVENT object will be destroyed, and during its destruction the lock will be released.

If you want to have the mutex locked over multiple statements, simply bind the temporary to a reference. That attaches the lifetime of the object to the lifetime of the reference. Or construct a named object.

Upvotes: 1

Related Questions