Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I implement convenient logging without a Singleton?

My current implementation, simplified:

#include <string> #include <memory>  class Log {   public:     ~Log() {       // closing file-descriptors, etc...     }     static void LogMsg( const std::string& msg )     {       static std::unique_ptr<Log> g_singleton;       if ( !g_singleton.get() )         g_singleton.reset( new Log );       g_singleton->logMsg( msg );     }   private:     Log() { }     void logMsg( const std::string& msg ) {       // do work     } }; 

In general, I am satisfied with this implementation because:

  • lazy instantiation means I don't pay unless I use it
  • use of unique_ptr means automatic cleanup so valgrind is happy
  • relatively simple, easy-to-understand implementation

However, the negatives are:

  • singletons aren't conducive to unit-testing
  • dissonance in the back of my mind for introducing a pseudo-global (a bit of a code smell)

So here are my questions directed towards those developers who are successful in exorcising all singletons from their C++ code:

  • What kind of non-Singleton implementation do you use for application-wide logging?
  • Is the interface as simple and accessible as a Log::LogMsg() call above?

I want to avoid passing a Log instance all over my code, if at all possible - note: I am asking because, I, too, want to exorcise all Singletons from my code if there is a good, reasonable alternative.

like image 217
kfmfe04 Avatar asked Dec 01 '11 06:12

kfmfe04


People also ask

Why do we need singleton logger?

Why a singleton? A singleton is a creational design pattern whose purpose is to ensure that only one instance of a class exists. We want our logger to be a singleton as we only want one logger instance to be running and logging information at any one time.

Is logging a singleton?

A logger is, perhaps, the most iconic example of a singleton use case. You need to manage access to a resource (file), you only want one to exist, and you'll need to use it in a lot of places.


1 Answers

First: the use of std::unique_ptr is unnecessary:

void Log::LogMsg(std::string const& s) {   static Log L;   L.log(s); } 

Produces exactly the same lazy initialization and cleanup semantics without introducing all the syntax noise (and redundant test).

Now that is out of the way...

Your class is extremely simple. You might want to build a slightly more complicated version, typical requirements for log messages are:

  • timestamp
  • level
  • file
  • line
  • function
  • process name / thread id (if relevant)

on top of the message itself.

As such, it is perfectly conceivable to have several objects with different parameters:

// LogSink is a backend consuming preformatted messages // there can be several different instances depending on where // to send the data class Logger { public:   Logger(Level l, LogSink& ls);    void operator()(std::string const& message,                   char const* function,                   char const* file,                   int line);  private:   Level _level;   LogSink& _sink; }; 

And you usually wrap the access inside a macro for convenience:

#define LOG(Logger_, Message_)                  \   Logger_(                                      \     static_cast<std::ostringstream&>(           \       std::ostringstream().flush() << Message_  \     ).str(),                                    \     __FUNCTION__,                               \     __FILE__,                                   \     __LINE__                                    \   ); 

Now, we can create a simple verbose logger:

Logger& Debug() {   static Logger logger(Level::Debug, Console);   return logger; }  #ifdef NDEBUG #  define LOG_DEBUG(_) do {} while(0) #else #  define LOG_DEBUG(Message_) LOG(Debug(), Message_) #endif 

And use it conveniently:

int foo(int a, int b) {   int result = a + b;    LOG_DEBUG("a = " << a << ", b = " << b << " --> result = " << result)   return result; } 

The purpose of this rant ? Not all that is a global need be unique. The uniqueness of Singletons is generally useless.

Note: if the bit of magic involving std::ostringstream scares you, this is normal, see this question

like image 111
Matthieu M. Avatar answered Sep 30 '22 17:09

Matthieu M.