2

I am trying to write a thread safe logger class so that i can do the exact same as with cout but with thread safety.

here is the logger class (still working on the type of lock required)

class logger { public: logger(LOGGER::output_type type); logger(const logger& orig); virtual ~logger(); template <typename T> logger & operator << (const T & data){ boost::mutex::scoped_lock io_mutex_lock(io_mutex); (*out)<<data; return *this; } private: static boost::mutex io_mutex; std::ostream * out; }; 

The poblem is I cannot do the following

  1. log<<"asdfg";
    I have to instead do
    log<<string("asdfg");

  2. int i = 10;
    log<<string ("i = ") << i << endl;

following is the compilation error.

gcc.compile.c++ src/simpleThread/bin/gcc-4.4.5/debug/simpleThread.o src/simpleThread/simpleThread.cc: In function ‘int main()’: src/simpleThread/simpleThread.cc:28: error: no match for ‘operator<<’ in ‘((logger*)logOut.logger::operator<< [with T = char [18]](((const char (&)[18])"fibonacci thread ")))->logger::operator<< [with T = int](((const int&)((const int*)(& i)))) << std::endl’ 

So I guess i am missing some important concept of C++. Please let me know what it is? Is my requirement even achievable

thanks, Kiran

5
  • 1
    what do you mean by "can't"? do you get a compiler error? which? Commented Apr 3, 2011 at 7:35
  • the compilation error is included in the question now. Commented Apr 3, 2011 at 8:29
  • the errors occur only when adding std::endl which is defined as an inline funtion. Commented Apr 3, 2011 at 8:40
  • BTW, have you considered Apache log4cxx? Commented Apr 3, 2011 at 8:57
  • since you are alrady using boost, I'd suggest you have a look at boost's logger Commented Apr 3, 2011 at 9:05

5 Answers 5

5

Note that your logger class is still not thread safe:

int i = 10; log <<string ("i = ") << i << endl; 

There is nothing stopping this thread from getting preempted by another another thread printing to logger and producing something like:

i = i = 12 

Instead of:

i = 1 i = 2 

If you have a compiler with variadic templates, here's one way of fixing this:

#include <ostream> #include <mutex> inline void sub_print(std::ostream&) {} template <class A0, class ...Args> void sub_print(std::ostream& os, const A0& a0, const Args& ...args) { os << a0; sub_print(os, args...); } std::mutex& io_mut() { static std::mutex m; return m; } template <class ...Args> void log(std::ostream& os, const Args& ...args) { std::lock_guard<std::mutex> _(io_mut()); sub_print(os, args...); os.flush(); } #include <iostream> int main() { int i = 10; log(std::cout, "i = ", i, '\n'); } 

I.e. the mutex is locked until all arguments for given log message are processed. std::endl is handled separately by always flushing the stream after every message.

Sign up to request clarification or add additional context in comments.

Comments

4

Your problem is that logger isn't a stream, so the usual stream operators will not work, just the single one you have defined.

You can get endl to work by defining it yourself:

inline logger& endl(logger& log) { // do something to *out return log; } 

Now log << "Hello!" << endl; will work.

To be able to chain several << operations together like the streams do, you will have to define all the operators for the logger (just like the streams do).

1 Comment

I found a better solution. check my reply.
4

I think you are introducing synchronization at too low a level. To understand why, assume thread 1 executes:

log << "This is " << "my " << "log message" << endl; 

while thread 2 executes:

log << "Hello, " << "World!" << endl; 

In such a case, the log file (or console output) may contain interleaved messages, for example:

This is Hello, my World! log message 

To avoid this problem, your application will have to construct an entire message as a single string, and only then pass that string to a logger object. For example:

ostringstream msg; msg << "This is " << "my " << "log message" << endl; log << msg.str(); 

If you take this approach, then your logger class does not need to overload operator<< for endl and multiple types.

Comments

1

I tested your program with some simplifications as follows, and it compiles and runs fine, which means that the problem is probably elsewhere:

#include <iostream> class logger { public: // logger(LOGGER::output_type type); logger(std::ostream& os): out(&os) {} ~logger() {} template <typename T> logger & operator << (const T & data){ // boost::mutex::scoped_lock io_mutex_lock(io_mutex); (*out)<<data; return *this; } private: // static boost::mutex io_mutex; std::ostream * out; }; int main() { logger log(std::cout); log << std::string("hello "); log << "world\n"; } 

1 Comment

yes. this works i have edited my question. how about adding an endl?
1

After digging into iostreams and with hints from Bo Persson, I think i found a better solutions since I dont need to write a function each for each ios manipulator. So here it is

 logger& operator<<(std::ostream& (*pf)(std::ostream&)) { (*out)<<pf; return *this; } 

For an explanation search for iostreams and applicators.

Here is the complete boost::thread safe implementation (requires some refactoring and optimization probably) using some hints from Ciaran-Mchale

/* * File: logger.h * Author: Kiran Mohan * */ #ifndef LOGGER_H #define LOGGER_H #include <boost/thread.hpp> #include <iostream> namespace LOG { enum output_type { STDOUT, STDERR }; /** * a thread safe logger to print to stdout or stderr */ class logger { public: logger(LOG::output_type type); logger(const logger& orig); virtual ~logger(); template <typename T> logger & operator <<(T data) { /* Takes any data type and stores in a stringstream */ (*oss) << data; return *this; } logger & operator<<(std::ostream& (*pf)(std::ostream&)) { // for stream manipulators (*oss) << pf; return *this; } logger & operator<<(logger & (*pf)(logger &)) { //applicator - mainly calling the print function; return pf(*this); } friend logger & flush(logger & l); logger & print() { boost::mutex::scoped_lock io_mutex_lock(io_mutex); (*out) << oss->str() << std::flush; delete oss; oss = new std::ostringstream; return *this; } private: static boost::mutex io_mutex; std::ostream * out; std::ostringstream * oss; }; logger & flush(logger & l); }; #endif /* LOGGER_H */ /* * File: logger.cc * Author: aryan * */ #include <boost/thread/pthread/mutex.hpp> #include <iostream> #include "logger.h" using namespace LOG; boost::mutex logger::io_mutex; logger::logger(LOG::output_type type) { if (type == LOG::STDOUT) { out = &std::cout; } else { out = &std::cerr; } oss = new std::ostringstream; } logger::logger(const logger& orig) { out = orig.out; } logger::~logger() { delete oss; } logger & LOG::flush(logger & l) { l.print(); boost::this_thread::yield(); return l; } 

use it like this

LOG::logger logOut (LOG::STDOUT); logOut<<"Hello World\n"<<LOG::flush; 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.