C++11 Logging Architecture

To create a Logging system for a larger application, I came up with the following draft. The log sinks aren't detailed yet, but the factory method for logger creation and a rough draft of the logger itself are. Is this a good start and worth continuing? Or are there pitfalls you can see?

class LoggingSink; // abstract base class
class ConsoleSink : public LoggingSink; // some concrete logging sink
class LogfileSink : public LoggingSink; // ... too

/***********************************************/
class Logger {
public:
void log(const std::string& msg) {
for (LoggingSink ls : Sinks)
ls.log(msg);
}

private:
std::list<std::reference_wrapper<LoggingSink>> Sinks;
friend class LoggerFactory;
};

/***********************************************/
class LoggerFactory {
public:
std::unique_ptr<Logger> create() {
std::unique_ptr<Logger> result = std::make_unique<Logger>();
result->Sinks.assign(StandardAssignedSinks.begin(), StandardAssignedSinks.end());
return result;
}

Sinks.push_back(std::reference_wrapper<LoggingSink>(ls));
}

private:
std::list<std::reference_wrapper<LoggingSink>> StandardAssignedSinks;
};

/***********************************************/
int main() {
ConsoleSink cs;
LogfileSink lfs("mylogfile.txt");

LoggerFactory lf;
lf.addStandardSink(lfs); // perhaps the addition of some standard sinks should be done in the constructor of LoggerFactory???

std::unique_ptr<Logger> myLogger = lf.create();

myLogger->log("Hello Log, I'm going to be injected soon!");
MyClass myClassInstance(myLogger); // constructor dependency injection

MyClass myOtherInstance(lf.create()); // direct DI
}


I'm also unsure about the friend class. This is something I never really liked with factories. Any ideas how to improve here?

• Have you looked at Boost.log? – Guy Sirton Apr 6 '14 at 21:33
• Yes, I looked at it. Seems rational to me. The main reason for this draft is to try out something new and try to get a better understanding for factories, the std::reference_wrapper, dependency injection, ... But thank you for the comment! – Martin Apr 6 '14 at 21:38
• Two points: 1) In this case I think it is preferable for the factory to retain ownership of a log (thus it would return a reference to Logger. 2) I would prefer to be able to use them like a stream rather than calling log(std::string const&). Expected usage: LoggerFactory::get().log(Logger::Critical) << "Having fun: FileDS " << fileDescriptor << " has generated the error( " << strerror(errno) << ")"; – Martin York Apr 6 '14 at 22:34
• Correct me if I'm wrong, but std::make_unique makes this code C++14, not C++11. – Morwenn Apr 7 '14 at 8:30
• @LokiAstari 1) You mean something like a static reference? I'd like to have multiple Loggers, each one with a name. Then I would be able to differentiate between Loggers for subsystems. 2) Yeah, an interface like std::ostream would be nice, that's right! – Martin Apr 7 '14 at 10:21

I would make the factory retain ownership:

class LoggerFactory {

typedef std::map<std::string, std::unique_ptr<Logger>>  Container;
typedef Container::iterator                             iterator;
typedef Container::const_iterator                       const_iterator;
typedef Container::value_type                           value_type;

typedef std::list<std::reference_wrapper<LoggingSink>>  SinkHolder;

public:
Logger& get(std::string const& loggerName = "default")
{
iterator find = cont.find(loggerName);
if (find == cont.end())
{

std::unique_ptr<Logger> result = std::make_unique<Logger>();

// Personally I would not do this.
// I would pass the iterators to the constructor
// of the Logger object.
result->Sinks.assign(std::begin(standardAssignedSinks),
std::end(standardAssignedSinks));

auto insertR = cont.insert(value_type(loggerName, result));
if (!insertR.second)
{    throw std::runtime_error("Insertion Failed");
}
find = insertR.first;
}
return *(find->second);
}

standardAssignedSinks.empace_back(ls);
}

private:
SinkHolder standardAssignedSinks;
Container  cont;
};


Now you can have multiple loggers. But you can re-use loggers (as each logger has its own name. Most of the time people should be using the default logger.

Building a streamming logger is not that difficult it just requires an intermediate buffer object to accumulate the message. When it is destroyed it sends the message to the logger.

class Logger {
public:
template<typename T>
LogStreamBuffer  operator<<(T const& data) // Member so left hand side  implied as Logger
{
// This is not perfect
// This may cause a copy (and thus a destruction)
// If there is a copy/destruction cycle an extra message will be
// sent. You can code around this problem.
// This is just to get you started.

return LogStreamBuffer(*this) << data;
}
// OTHER STUFF
};
class LogStreamBuffer
{
Logger&            parent;
std::stringstream  buffer;

public:
LogStreamBuffer(Logger& parent)
: parent()
{}
~LogStreamBuffer()
{
// When the object is destroyed.
// Log the accumulated message with the parent.
parent.log(buffer.str());
}
template<typename T>
LogStreamBuffer&  operator<<(T const& data)
{
// Anything you log is just appended to the message.
buffer << data;
return *this;   // return a reference to yourself.
// So you can chain messages.
}
};

// Usage:
int main()
{
LoggerFactory   factory;
Logger          logger = factory.get("MyLogger");

logger << "This is a test: " << 1 << " Got it";
//   ^^^
//   Creates a LogStreamBuffer
//   As an invisable temporary object.
//   The subsequent operator<< will accumulate
//   data in the buffer. When the statement is
//   finished (at the ;) all temporaies will be
//   destroyed. This calls the LogStreamBuffer
//   destructor and logs your data to the Logger
//   object.

}