MercuryDPM  Beta
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Logger.h
Go to the documentation of this file.
1 #ifndef LOGGER_H
2 #define LOGGER_H
3 
4 #include <string>
5 #include <sstream>
6 #include <functional>
7 #include <type_traits>
8 #include "GeneralDefine.h"
9 
10 #ifndef MERCURY_LOGLEVEL
11 #define MERCURY_LOGLEVEL Log::DEFAULT
12 #endif
13 
14 /* IMPLEMENTATION DETAIL
15  * - by dducks
16  *
17  * A brief explanation how this class works. Beware, there is black magic
18  * going on here.
19  *
20  * So, the previous version of the Logger used the syntax
21  * Logger<Log::LEVEL> logger;
22  * logger.log(Log::LEVEL, "Message", args...);
23  * However, people didn't like the slightly large amount of log's in a single
24  * statement. Therefore, the operator() has been chosen. The next problem I
25  * personally had with the logger is the fact that it relied on a compile-time
26  * resolvable if-statement which could often (always) be optimised out. The
27  * problem however is that you would need optimisation enabled to have no
28  * speed penalty for loglevels below visibility.
29  *
30  * The problem however, is that we want to generate different code based on
31  * the first parameter; the loglevel. Because we actually want to generate
32  * code and people don't like the preprocessor, we have to use the template
33  * system to do this.
34  *
35  * One of the caveats of the template system is that it is only able to
36  * resolve templates based on parameters, not on values. It also allows you
37  * to ommit template parameters in case where the template parameters can be
38  * deduced.
39  *
40  * Based on these two rules, we now need not a value per loglevel, but a type.
41  * Therefore we use tagging, in which we use the class LL with a template argument
42  * to create the different tags. Our loglevel, the enum class Log, is considered
43  * to be a valid non-type template parameter - it must be either integral, enum
44  * (which is integral) or a pointer - so we can use it to tag a class to create
45  * diffent types.
46  *
47  * Now, our Logger instance has a template argument which is the loglevel
48  * associated with our Logger; anything of lesser priority is ignored; It
49  * also honours the symbol MERCURY_LOGLEVEL, and compares it to the lesser of
50  * the template argument and the preprocessor define.
51  *
52  * The operator() function now should be based on the template parameter of the
53  * logger class (or MERCURY_LOGLEVEL), and the loglevel of the current message.
54  * Because we can't depend on the value but just on the type, we have to do some
55  * magic. Now, we want the function to resolve to something that produces output
56  * if the level is high enough, or nothing at all if the level is of a too low
57  * priority for the current loglevel. For this we utilize std::enable_if<>
58  * to select the right function.
59  *
60  * Because in C++ a templated function which leads to ill-formed code upon
61  * instantiation is rejected and does NOT result in a compile error,
62  * std::enable_if allows us to make either the version WITH output or
63  * WITHOUT output, based on template parameters.
64  *
65  * As you can see below, the class LL is completely empty; However,
66  * there are a few instances; one for every loglevel. Now, remember,
67  * when using the logger, Log::DEFAULT resolves to the enum class value
68  * while DEFAULT resolves to the instance of class LL with template
69  * argument Log::DEFAULT.
70  *
71  * However, LL<Log::DEFAULT> differs in type from LL<Log::ERROR>, as the
72  * template argument is different. Because of deduction, the compiler can
73  * figure out the actual values for the template arguments. In case of
74  * operator(), the first argument is Log loglevel, which is deduced from
75  * the declaration;
76  *
77  * template<Log LogLevel, typename... Args>
78  * void operator(const LL<LogLevel> ll, std::string text, Args... args);
79  *
80  * Now, the template parameter LogLevel gets filled with Log::ERROR in case
81  * we give ERROR as the first argument, or Log::DEBUG with DEBUG as the first
82  * argument. Now, we want to resolve to two different functions, so as the
83  * return type we use std::enable_if which would lead to ill-formed code
84  * in case the predicate of the std::enable_if is false. So, based on the
85  * tag ll we now can select the different implementations.
86  *
87  * It then buffers everything into a stringstream. so as long as operator<<
88  * is defined properly for your object,. This then gets redirected towards the
89  * correct output channel.
90  *
91  * Please note that operator() is an inline, templated function. In case the
92  * function body is empty, this code is very, very likely to not emit any
93  * instructions at all. If you don't give arguments which have only non-const
94  * functions, the function call can be considered invariant which means it
95  * can completely be taken out, in the case it's a seperate function it just lowers
96  * the cost.
97  *
98  * As said, black magic below.
99  * END OF IMPLEMENTATION DETAIL
100  */
101 
110 enum class Log : signed char {
111  FATAL = -20,
112  ERROR = -15,
113  WARN = -10,
114  INFO = -5,
115  DEFAULT = 0,
116  VERBOSE = 5,
117  DEBUG = 10,
118 };
119 
124 constexpr bool operator<=(const Log rhs, const Log lhs) {
125  return ((static_cast<signed char>(rhs)) <= (static_cast<signed char>(lhs)));
126 }
127 
141  public:
142  std::function<void(std::string,std::string)> onFatal;
143  std::function<void(std::string,std::string)> onError;
144  std::function<void(std::string,std::string)> onWarn;
145  std::function<void(std::string,std::string)> onInfo;
146  std::function<void(std::string,std::string)> onVerbose;
147  std::function<void(std::string,std::string)> onDebug;
148 };
149 
158 extern LoggerOutput* loggerOutput;
159 
160 // Forward declaration..
161 template<Log L = Log::DEFAULT> class Logger;
162 
172 template<Log Level>
173 class LL
174 {
175 public:
176 };
177 
193 extern LL<Log::FATAL> FATAL;
205 extern LL<Log::ERROR> ERROR;
217 extern LL<Log::WARN> WARN;
228 extern LL<Log::INFO> INFO;
256 extern LL<Log::DEBUG> DEBUG;
257 
271 template<Log L>
272 class Logger {
273  private:
277  const std::string module;
278 
279  public:
280 
285  Logger(const std::string name) : module(name) { }
289  ~Logger() {}
290 
291  /*
292  *
293  * \brief Log implementation of this function
294  *
295  * Actual implementation of the log function.
296  * At compile time evaluates to this implementation,
297  * or an empty body implementation, depending on the
298  * loglevel parameter of the Logger itself.
299  *
300  * \arg log Loglevel, either FATAL, ERROR, WARN, INFO, VERBOSE, DEBUG
301  * \arg format Message format, where % can be used as a placeholder for arguments.
302  * \arg arg... Any arguments which needs to be replaced.
303  */
304  template<Log LOGLEVEL, typename... Args>
305  typename std::enable_if<!((L < LOGLEVEL) || (MERCURY_LOGLEVEL < LOGLEVEL)), void>::type
306  operator()(const LL<LOGLEVEL> log UNUSED, const std::string& format, Args&&... arg) {
307  std::stringstream msgstream;
308  createMessage(msgstream, format.c_str(), arg...);
309  if (LOGLEVEL <= Log::FATAL) {
310  loggerOutput->onFatal(module, msgstream.str());
311  } else if (LOGLEVEL <= Log::ERROR) {
312  loggerOutput->onError(module, msgstream.str());
313  } else if (LOGLEVEL <= Log::WARN) {
314  loggerOutput->onWarn(module, msgstream.str());
315  } else if (LOGLEVEL <= Log::INFO) {
316  loggerOutput->onInfo(module, msgstream.str());
317  } else if (LOGLEVEL <= Log::VERBOSE) {
318  loggerOutput->onVerbose(module, msgstream.str());
319  } else {
320  loggerOutput->onDebug(module, msgstream.str());
321  }
322  }
323 
324  template<Log LOGLEVEL, typename... Args>
325  typename std::enable_if<L < LOGLEVEL || MERCURY_LOGLEVEL < LOGLEVEL, void>::type
326  operator()(const LL<LOGLEVEL> log UNUSED, const std::string& format UNUSED, Args&&... arg UNUSED) {
327 
328  }
329 
330  template<Log LOGLEVEL, typename... Args>
331  typename std::enable_if<!((L < LOGLEVEL) || (MERCURY_LOGLEVEL < LOGLEVEL)), void>::type
332  operator()(const LL<LOGLEVEL> log UNUSED, const char * format, Args&&... arg) {
333  std::stringstream msgstream;
334  createMessage(msgstream, format, arg...);
335  if (LOGLEVEL <= Log::FATAL) {
336  loggerOutput->onFatal(module, msgstream.str());
337  } else if (LOGLEVEL <= Log::ERROR) {
338  loggerOutput->onError(module, msgstream.str());
339  } else if (LOGLEVEL <= Log::WARN) {
340  loggerOutput->onWarn(module, msgstream.str());
341  } else if (LOGLEVEL <= Log::INFO) {
342  loggerOutput->onInfo(module, msgstream.str());
343  } else if (LOGLEVEL <= Log::VERBOSE) {
344  loggerOutput->onVerbose(module, msgstream.str());
345  } else {
346  loggerOutput->onDebug(module, msgstream.str());
347  }
348  }
349 
350  template<Log LOGLEVEL, typename... Args>
351  typename std::enable_if<L < LOGLEVEL || MERCURY_LOGLEVEL < LOGLEVEL, void>::type
352  operator()(const LL<LOGLEVEL> log UNUSED, const char * format UNUSED, Args&&... arg UNUSED) {
353 
354  }
355 
356 
361  template<typename... Args>
362  void log(const Log loglevel, const std::string& format, Args&&... arg) {
363  if (loglevel <= L || loglevel <= MERCURY_LOGLEVEL) {
364  std::stringstream msgstream;
365  createMessage(msgstream, format.c_str(), arg...);
366  if (loglevel <= Log::FATAL) {
367  loggerOutput->onFatal(module, msgstream.str());
368  } else if (loglevel <= Log::ERROR) {
369  loggerOutput->onError(module, msgstream.str());
370  } else if (loglevel <= Log::WARN) {
371  loggerOutput->onWarn(module, msgstream.str());
372  } else if (loglevel <= Log::INFO) {
373  loggerOutput->onInfo(module, msgstream.str());
374  } else if (loglevel <= Log::VERBOSE) {
375  loggerOutput->onVerbose(module, msgstream.str());
376  } else {
377  loggerOutput->onDebug(module, msgstream.str());
378  }
379  }
380  }
381  private:
386  template<typename Arg1, typename... Args>
387  void createMessage(std::stringstream& msg, const char* fmt,
388  Arg1&& arg, Args&&... args)
389  {
390  bool doSkipNext = false;
391  while (*fmt != '%' && !doSkipNext)
392  {
393  doSkipNext = false;
394  //Make sure we're not running past the end of our formatting string.
395  if (*fmt == '\0')
396  return;
397 
398  if (*fmt == '\\')
399  { //Escape for the %sign
400  doSkipNext = true;
401  }
402  else
403  {
404  msg << *fmt;
405  fmt++;
406  }
407  }
408 
409  fmt++; //Consume the % sign
410  msg << arg;
411  createMessage(msg, fmt, args...); //and recursively call ourselve / the method below.
412  }
413 
417  template<typename Arg1>
418  void createMessage(std::stringstream& msg, const char* fmt, Arg1&& arg)
419  {
420  bool doSkipNext = false;
421  while (*fmt != '%' && !doSkipNext)
422  {
423  doSkipNext = false;
424  if (*fmt == '\0') // End of string
425  return;
426 
427  if (*fmt == '\\')
428  { //Escape for the %sign
429  doSkipNext = true;
430  }
431  else
432  { //invoke the replacement
433  msg << *fmt;
434  fmt++;
435  }
436  }
437  fmt++; //Consume the % sign
438  msg << arg;
439  while (*fmt != '\0')
440  { //And print the end of the message!
441  msg << *fmt;
442  fmt++;
443  }
444  }
445 
446 
450  void createMessage(std::stringstream& msg, const char* message)
451  {
452  msg << message;
453  }
454 };
455 
465 
466 #endif
LoggerOutput * loggerOutput
Declaration of the output functions. If the output needs to be redirected, please swap the loggerOutp...
Definition: Logger.cc:110
Logger.
Definition: Logger.h:161
LL< Log::INFO > INFO
Info log level.
Definition: Logger.cc:28
LL< Log::DEBUG > DEBUG
Debug information.
Definition: Logger.cc:31
LL< Log::ERROR > ERROR
Error log level.
Definition: Logger.cc:26
const std::string module
The module name of this actual logger.
Definition: Logger.h:277
std::function< void(std::string, std::string)> onWarn
Definition: Logger.h:144
void createMessage(std::stringstream &msg, const char *message)
Terminating case / no argument call.
Definition: Logger.h:450
std::function< void(std::string, std::string)> onVerbose
Definition: Logger.h:146
Logger(const std::string name)
constructor
Definition: Logger.h:285
std::enable_if<!((L< LOGLEVEL)||(MERCURY_LOGLEVEL< LOGLEVEL)), void >::type operator()(const LL< LOGLEVEL > log UNUSED, const std::string &format, Args &&...arg)
Definition: Logger.h:306
void createMessage(std::stringstream &msg, const char *fmt, Arg1 &&arg)
Terminating case / argument call.
Definition: Logger.h:418
constexpr bool operator<=(const Log rhs, const Log lhs)
Internally used to filter on loglevel. Do not edit, as this is required for an optimised logger...
Definition: Logger.h:124
void createMessage(std::stringstream &msg, const char *fmt, Arg1 &&arg, Args &&...args)
Actual implementation to recursively replace all the '' signs by actual values.
Definition: Logger.h:387
std::function< void(std::string, std::string)> onError
Definition: Logger.h:143
~Logger()
destructor
Definition: Logger.h:289
Default functions for output generation.
Definition: Logger.h:140
LL< Log::DEFAULT > DEFAULT
Default log level.
Definition: Logger.cc:29
#define UNUSED
Definition: GeneralDefine.h:37
LL< Log::VERBOSE > VERBOSE
Verbose information.
Definition: Logger.cc:30
std::function< void(std::string, std::string)> onDebug
Definition: Logger.h:147
Logger< MERCURY_LOGLEVEL > logger
std::function< void(std::string, std::string)> onInfo
Definition: Logger.h:145
Log
The different loglevels.
Definition: Logger.h:110
#define MERCURY_LOGLEVEL
Definition: Logger.h:11
LL< Log::FATAL > FATAL
Fatal log level.
Definition: Logger.cc:25
std::function< void(std::string, std::string)> onFatal
Definition: Logger.h:142
LL< Log::WARN > WARN
Warning log level.
Definition: Logger.cc:27
Tag for template metaprogramming.
Definition: Logger.h:173