Reputation: 7024
I'm working on a small C++11 application (an SDL2 game) and i'm having a hard time "porting" some of my object-oriented knowledge from PHP/Java to C++. For example, in order to create an elegant error logging approach, i would create a class with various adapters and centralize logging there. I already did that in C++, but i have no idea on how my classes should be using the Logger
class.
In Java and PHP, i would use dependency injection, and put the Logger
as a class member variable in them. But in C++, what's the proper way? I don't really think that going static would be nice.
Upvotes: 10
Views: 5282
Reputation: 42072
Oh man.
To me logging is similar to date/time handling: the basic case is trivial, but anything more than trivial is extremely complicated: no middle ground.
Let me advise you to look into a general purpose logging library such as Pantheios or Boost.Log.
The reason why I advice for this approach as opposed to making "your own effort", is that I know first hand how the "logging situation" goes:
And it all becomes very, very difficult, and the logging classes start polluting your code.
So, like I said: based on my limited experience, I would encourage you to look into the suggested libraries.
Good luck.
Just for completeness of the post (refer to page for details).
Trivial case:
#include <boost/log/trivial.hpp>
int main(int, char*[]) {
BOOST_LOG_TRIVIAL(trace) << "A trace severity message";
BOOST_LOG_TRIVIAL(debug) << "A debug severity message";
BOOST_LOG_TRIVIAL(info) << "An informational severity message";
BOOST_LOG_TRIVIAL(warning) << "A warning severity message";
BOOST_LOG_TRIVIAL(error) << "An error severity message";
BOOST_LOG_TRIVIAL(fatal) << "A fatal severity message";
return 0;
}
Upvotes: 7
Reputation: 299730
My current approach is to use a kind of dependency injection, using C++ strengths instead of magic. It does not require on anything specific to C++11 (except that __thread
which is an extension could be replaced by thread_local
if you wished to be Standard).
class LoggerEngine {
public:
static LoggerEngine* Current() { return CurrentE; }
virtual bool isActive(Level) { return true; }
virtual void log(char const* function,
char const* file,
int line,
std::string message) = 0;
// cuz' decorators rock
LoggerEngine* previous() const { return _previous; }
protected:
LoggerEngine(): _previous(CurrentE) { CurrentE = this; }
~LoggerEngine() { CurrentE = _previous; }
private:
static __thread LoggerEngine* CurrentE;
LoggerEngine* const _previous;
}; // class LoggerEngine
// in some .cpp file:
__thread LoggerEngine* LoggerEngine::CurrentE = 0;
And then, provide macros (to capture function, file and line):
#define LOG(L_, Message_) \
do { if (LoggerEngine* e = LoggerEngine::Current() and e->isActive(L_)) { \
std::ostringstream _28974986589657165; \
_28974986589657165 << Message_; \
e->log(__func__, __FILE__, __LINE__, _28974986589657165.str()); \
}} while(0);
However it could certainly be made better by using shims instead, because even though it prevents any computation in case the level is not active it still requires formatting of the full message (and the necessary memory allocation) even if it is going to truncate the message anyway (for example because it uses fixed-size buffers) and does not easily allow customization of the formatting.
The combination of stacking engines (and popping them off automatically using RAII) with thread-local behavior is really pretty neat. Most code only ever see an interface, without having to thread it by (cool when you have 4/5 different engines), and any level of the stack can switch the engine to something more appropriate.
There is one caveat, as is, no logging occurs before a first Engine is defined. For this reason I've often thought of defaulting to writing to the console if no engine is setup but... I've mostly changed my style to avoid computation before main
is called since I cannot dependency-inject during this phase (and it's awkward if an exception fires...)
Usage is like this:
void benchmark() {
LOG(INFO, "Hello, World!");
Timer t;
{
MySinkLogger const _; (void)_; // a logger with "isActive" always false
for (size_t i = 0; i != 10000; ++i) {
LOG(INFO, "Flood!");
}
}
LOG(INFO, "Elapsed: " << t.elapsed());
}
int main() {
MyFileLoggerEngine const _("somefile.log"); (void)_; // a file logger
benchmark();
}
And normally this could create a file "somefile.log" containing:
2013-10-03T18:38:04.645512 mylaptop INFO <test.cpp#42> Hello, World!
2013-10-03T18:38:04.865765 mylaptop INFO <test.cpp#47> Elapsed: 0.220213s
Upvotes: 0
Reputation: 5674
I always use something like this:
class Log
{
public:
Log()
: m_filename( "dafault.log" )
{}
// if you wanna give other names eventually...
Log( const std::string & p_filename )
: m_filename( p_filename )
{}
virtual ~Log()
{
// implement your writeToFile() with std::ofstream
writeToFile( m_filename, m_stream, true );
}
template< typename T >
Log & operator<<( const T & p_value )
{
m_stream << p_value;
return *this;
}
private:
std::string m_filename;
std::ostringstream m_stream;
};
So this way I am able to log like this:
Log() << "My message in the log with numbers " << 1 << 2 << 3 << " and so on...";
Log( "other.log" ) << "Log in another file eventually...";
Upvotes: 2
Reputation: 136208
One approach would be to pass a reference to a logger object around function calls. However, logging is a sort of an orthogonal aspect to application logic, so that explicitly passing that logger and having it as a member quickly becomes a nuisance and only adds artificial complexity.
I prefer having one global logger in the application. Modules can create its own loggers as child loggers of the main logger forming a hierarchy (I think this is similar to Python logging module) and control its output sink and verbosity independently if necessary.
Upvotes: 2