7
\$\begingroup\$

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;
 }
 void addStandardSink(LoggingSink& ls) {
 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(cs); // adding as reference for polymorphism
 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?

200_success
146k22 gold badges190 silver badges479 bronze badges
asked Apr 6, 2014 at 21:00
\$\endgroup\$
9
  • \$\begingroup\$ Have you looked at Boost.log? \$\endgroup\$ Commented Apr 6, 2014 at 21:33
  • \$\begingroup\$ 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! \$\endgroup\$ Commented Apr 6, 2014 at 21:38
  • 2
    \$\begingroup\$ 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) << ")"; \$\endgroup\$ Commented Apr 6, 2014 at 22:34
  • \$\begingroup\$ Correct me if I'm wrong, but std::make_unique makes this code C++14, not C++11. \$\endgroup\$ Commented Apr 7, 2014 at 8:30
  • \$\begingroup\$ @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! \$\endgroup\$ Commented Apr 7, 2014 at 10:21

1 Answer 1

3
\$\begingroup\$

As noted in the comments:

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);
 }
 void addStandardSink(LoggingSink& ls) {
 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. 
}
answered Apr 7, 2014 at 16:01
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.