patterncppMinor
C++11 Logging Architecture
Viewed 0 times
loggingarchitecturestackoverflow
Problem
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?
I'm also unsure about the friend class. This is something I never really liked with factories. Any ideas how to improve here?
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> Sinks;
friend class LoggerFactory;
};
/***********************************************/
class LoggerFactory {
public:
std::unique_ptr create() {
std::unique_ptr result = std::make_unique();
result->Sinks.assign(StandardAssignedSinks.begin(), StandardAssignedSinks.end());
return result;
}
void addStandardSink(LoggingSink& ls) {
Sinks.push_back(std::reference_wrapper(ls));
}
private:
std::list> 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 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?
Solution
As noted in the comments:
I would make the factory retain ownership:
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.
I would make the factory retain ownership:
class LoggerFactory {
typedef std::map> Container;
typedef Container::iterator iterator;
typedef Container::const_iterator const_iterator;
typedef Container::value_type value_type;
typedef std::list> SinkHolder;
public:
Logger& get(std::string const& loggerName = "default")
{
iterator find = cont.find(loggerName);
if (find == cont.end())
{
std::unique_ptr result = std::make_unique();
// 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
LogStreamBuffer operator
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.
}Code Snippets
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;
};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.
}Context
StackExchange Code Review Q#46499, answer score: 3
Revisions (0)
No revisions yet.