McLovin
McLovin

Reputation: 3417

Error logging in c++

I implemented a very simple (error) logger class. It looks like this:

#pragma once

#include <string>

extern const char* LOG_DEFAULT_TEXT =  "<LOG>\n\n";

class Log
{
public:
    Log() : _logs(0), _log(LOG_DEFAULT_TEXT) {};

    void add(const char *str, bool nl=true);
    void clear();

    const char* get() const { return _log.c_str(); }
    int getNumLogs() const { return _logs; }


private:
    std::string _log;
    int _logs;
};

Now my question is, say I have this Main class, that contains all the other objects that my program may contain, and also this Log class. Obviously I would want these "other objects" in my Main class to be able to use the Log object in Main, so the easy solution would be to pass pointers to every class' constructor so that it could use the Log object.

I'm talking about something like this:

//Main.h
...

#include "Log.h"

class Main() {
public:
  ...

private:
  Log _log;
  ImportantObject1(&_log); //pass a pointer to _log
  ImportantObject2(&_log); //..same
};

This solution seems too clumsy, so I'm asking if there are different approaches for what I want to accomplish, which is error logging.

Upvotes: 3

Views: 9656

Answers (3)

Serge Ballesta
Serge Ballesta

Reputation: 148870

Another common pattern inherited from C would be a global object. Globals are generally frowned upon because they are the symptom of bad design. But it makes sense for a logger to be a global.

That means that the header for your class should contain :

extern Log log;

and the cpp

#include <log.h>
...
Log log;    // unique definition for the global Log

Then in any module using the log :

#include <log.h>
...
log.add(...);

The caveat here that there will be static initialization of Log log. And C++ is not always very nice with order of static initialization : ... Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit.. The only foolproof way is to ensure that :

  • Log construction does not depend on any other statically initialized variable needing a constructor phase in another translation unit - it includes std::string but hopefully not const char *
  • the global variable is used once before any multithreading starts

If in doubt, use the singleton pattern suggested by πάντα ῥεῖ

Upvotes: 1

R Sahu
R Sahu

Reputation: 206567

Another approach to address the requirement...

Use functions in a suitable namespace instead of a singleton.

Log.h:

namespace Log
{
   void add(const char *str, bool nl=true);
   void clear();

   const char* get();
   int getNumLogs();
}

Log.cpp:

namespace detail
{
   // This is almost same as the class from OP's post.
   struct LogImpl
   {
      LogImpl(std::string const& log) : _logs(0), _log(log) {};

      void add(const char *str, bool nl=true);
      void clear();

      const char* get() const { return _log.c_str(); }
      int getNumLogs() const { return _logs; }


      std::string _log;
      int _logs;
   };
}

namespace Log
{
   // This mimics the singleton implementation...
   // Create only one instance of LogImpl.

   static detail::LogImpl impl("<LOG>\n\n");

   void add(const char *str, bool nl)
   {
      impl.add(str, nl);
   }
   void clear()
   {
      impl.clear();
   }

   const char* get()
   {
      return impl.get();
   }
   int getNumLogs()
   {
      return impl.getNumLogs();
   }
}

namespace Log
{

   void add(const char *str, bool nl=true);
   void clear();

   const char* get();
   int getNumLogs();
}

Using functions in a namespace vs using a singleton

To support use of functions in a namespace requires bit more code.

However, it simplifies calling code.

Calling code can use

Log::add(...);

instead of

Log::instance()->add(...);

Upvotes: 2

πάντα ῥεῖ
πάντα ῥεῖ

Reputation: 1

That's one of the rare cases a singleton makes sense:

class Log
{
public:

    void add(const char *str, bool nl=true);
    void clear();

    const char* get() const { return _log.c_str(); }
    int getNumLogs() const { return _logs; }

    static Log& instance() {
         static Log theInstance;
         return theInstance;
    }
private:
    Log() : _logs(0), _log(LOG_DEFAULT_TEXT) {};

    std::string _log;
    int _logs;
};

This way you can use it elsewhere just accessing

 Log::instance().add("Blah Blah",true);

Upvotes: 5

Related Questions