MercuryDPM  Trunk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Logger.h
Go to the documentation of this file.
1 //Copyright (c) 2013-2020, The MercuryDPM Developers Team. All rights reserved.
2 //For the list of developers, see <http://www.MercuryDPM.org/Team>.
3 //
4 //Redistribution and use in source and binary forms, with or without
5 //modification, are permitted provided that the following conditions are met:
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above copyright
9 // notice, this list of conditions and the following disclaimer in the
10 // documentation and/or other materials provided with the distribution.
11 // * Neither the name MercuryDPM nor the
12 // names of its contributors may be used to endorse or promote products
13 // derived from this software without specific prior written permission.
14 //
15 //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 //DISCLAIMED. IN NO EVENT SHALL THE MERCURYDPM DEVELOPERS TEAM BE LIABLE FOR ANY
19 //DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 //(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 //ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 #ifndef LOGGER_H
27 #define LOGGER_H
28 
29 #include <string>
30 #include <sstream>
31 #include <functional>
32 #include <type_traits>
33 #include "GeneralDefine.h"
34 
35 #ifdef MERCURY_USE_MPI
36 #include "MpiContainer.h"
37 #endif
38 
39 
40 #ifndef MERCURY_LOGLEVEL
41 #define MERCURY_LOGLEVEL Log::DEFAULT
42 #endif
43 
44 #ifndef CG_LOGLEVEL
45 #define CG_LOGLEVEL Log::DEFAULT
46 #endif
47 
48 #ifdef assert
49 #undef assert
50 //#error You included assert before the logger. Please use logger.assert() instead.
51 #endif
52 
53 #ifdef MERCURY_FORCE_ASSERTS
54 #define MERCURY_ASSERTS true
55 #else
56 #ifdef MERCURY_NO_ASSERTS
57 #define MERCURY_ASSERTS false
58 #else
59 #ifdef NDEBUG
60 #define MERCURY_ASSERTS false
61 #else
62 #define MERCURY_ASSERTS true
63 #endif
64 #endif
65 #endif
66 
68 /* IMPLEMENTATION DETAIL
69  * - by dducks and
70  * - and fixed by MX
71  *
72  * A brief explanation how this class works. Beware, there is black magic
73  * going on here.
74  *
75  * So, the previous version of the Logger used the syntax
76  * Logger<Log::LEVEL> logger;
77  * logger.log(Log::LEVEL, "Message", args...);
78  * However, people didn't like the slightly large amount of log's in a single
79  * statement. Therefore, the operator() has been chosen. The next problem I
80  * personally had with the logger is the fact that it relied on a compile-time
81  * resolvable if-statement which could often (always) be optimised out. The
82  * problem however is that you would need optimisation enabled to have no
83  * speed penalty for loglevels below visibility.
84  *
85  * The problem however, is that we want to generate different code based on
86  * the first parameter; the loglevel. Because we actually want to generate
87  * code and people don't like the preprocessor, we have to use the template
88  * system to do this.
89  *
90  * One of the caveats of the template system is that it is only able to
91  * resolve templates based on parameters, not on values. It also allows you
92  * to ommit template parameters in case where the template parameters can be
93  * deduced.
94  *
95  * Based on these two rules, we now need not a value per loglevel, but a type.
96  * Therefore we use tagging, in which we use the class LL with a template argument
97  * to create the different tags. Our loglevel, the enum class Log, is considered
98  * to be a valid non-type template parameter - it must be either integral, enum
99  * (which is integral) or a pointer - so we can use it to tag a class to create
100  * diffent types.
101  *
102  * Now, our Logger instance has a template argument which is the loglevel
103  * associated with our Logger; anything of lesser priority is ignored; It
104  * also honours the symbol MERCURY_LOGLEVEL, and compares it to the lesser of
105  * the template argument and the preprocessor define.
106  *
107  * The operator() function now should be based on the template parameter of the
108  * logger class (or MERCURY_LOGLEVEL), and the loglevel of the current message.
109  * Because we can't depend on the value but just on the type, we have to do some
110  * magic. Now, we want the function to resolve to something that produces output
111  * if the level is high enough, or nothing at all if the level is of a too low
112  * priority for the current loglevel. For this we utilize std::enable_if<>
113  * to select the right function.
114  *
115  * Because in C++ a templated function which leads to ill-formed code upon
116  * instantiation is rejected and does NOT result in a compile error,
117  * std::enable_if allows us to make either the version WITH output or
118  * WITHOUT output, based on template parameters.
119  *
120  * As you can see below, the class LL is completely empty; However,
121  * there are a few instances; one for every loglevel. Now, remember,
122  * when using the logger, Log::DEFAULT resolves to the enum class value
123  * while DEFAULT resolves to the instance of class LL with template
124  * argument Log::DEFAULT.
125  *
126  * However, LL<Log::DEFAULT> differs in type from LL<Log::ERROR>, as the
127  * template argument is different. Because of deduction, the compiler can
128  * figure out the actual values for the template arguments. In case of
129  * operator(), the first argument is Log loglevel, which is deduced from
130  * the declaration;
131  *
132  * template<Log LogLevel, typename... Args>
133  * void operator(const LL<LogLevel> ll, std::string text, Args... args);
134  *
135  * Now, the template parameter LogLevel gets filled with Log::ERROR in case
136  * we give ERROR as the first argument, or Log::DEBUG with DEBUG as the first
137  * argument. Now, we want to resolve to two different functions, so as the
138  * return type we use std::enable_if which would lead to ill-formed code
139  * in case the predicate of the std::enable_if is false. So, based on the
140  * tag ll we now can select the different implementations.
141  *
142  * It then buffers everything into a stringstream. so as long as operator<<
143  * is defined properly for your object,. This then gets redirected towards the
144  * correct output channel.
145  *
146  * Please note that operator() is an inline, templated function. In case the
147  * function body is empty, this code is very, very likely to not emit any
148  * instructions at all. If you don't give arguments which have only non-const
149  * functions, the function call can be considered invariant which means it
150  * can completely be taken out, in the case it's a seperate function it just lowers
151  * the cost.
152  *
153  * As said, black magic below.
154  * END OF IMPLEMENTATION DETAIL
155  */
156 
165 enum class Log
166  : signed char
167 {
168  FATAL = -20, ERROR = -15, WARN = -10, INFO = -5, DEFAULT = 0, VERBOSE = 5, DEBUG = 10
169 };
170 
175 constexpr bool operator<=(const Log rhs, const Log lhs)
176 {
177  return ((static_cast<signed char>(rhs)) <= (static_cast<signed char>(lhs)));
178 }
179 
193 {
194 public:
195  std::function<void(std::string, std::string)> onFatal;
196  std::function<void(std::string, std::string)> onError;
197  std::function<void(std::string, std::string)> onWarn;
198  std::function<void(std::string, std::string)> onInfo;
199  std::function<void(std::string, std::string)> onVerbose;
200  std::function<void(std::string, std::string)> onDebug;
201 };
202 
211 extern LoggerOutput* loggerOutput;
212 
213 // Forward declaration..
214 template<Log L = Log::DEFAULT, bool ASSERTS = MERCURY_ASSERTS>
215 class Logger;
216 
226 template<Log Level>
227 class LL
228 {
229 public:
230 };
231 
247 extern LL<Log::FATAL> FATAL;
259 extern LL<Log::ERROR> ERROR;
271 extern LL<Log::WARN> WARN;
282 extern LL<Log::INFO> INFO;
310 extern LL<Log::DEBUG> DEBUG;
311 
325 template<Log L, bool ASSERTS>
326 class Logger
327 {
328 private:
332  const std::string module;
333 
334 public:
335 
340  explicit Logger(const std::string name)
341  : module(name)
342  {
343  }
344 
348  ~Logger()
349  = default;
350 
351  /*
352  *
353  * \brief Log implementation of this function
354  *
355  * Actual implementation of the log function.
356  * At compile time evaluates to this implementation,
357  * or an empty body implementation, depending on the
358  * loglevel parameter of the Logger itself.
359  *
360  * \arg log Loglevel, either FATAL, ERROR, WARN, INFO, VERBOSE, DEBUG
361  * \arg format Message format, where % can be used as a placeholder for arguments.
362  * \arg arg... Any arguments which needs to be replaced.
363  */
364  template<Log LOGLEVEL, typename ... Args>
365  typename std::enable_if<!((L < LOGLEVEL) && (MERCURY_LOGLEVEL < LOGLEVEL)), void>::type
366  operator()(const LL<LOGLEVEL> log, const char* format UNUSED, Args&& ... arg UNUSED)
367  {
368  std::stringstream msgstream;
369  createMessage(msgstream, format, arg...);
370  if (LOGLEVEL <= Log::FATAL)
371  {
372  loggerOutput->onFatal(module, msgstream.str());
373  }
374  else if (LOGLEVEL <= Log::ERROR)
375  {
376  loggerOutput->onError(module, msgstream.str());
377  }
378  else if (LOGLEVEL <= Log::WARN)
379  {
380  loggerOutput->onWarn(module, msgstream.str());
381  }
382  else if (LOGLEVEL <= Log::INFO)
383  {
384  loggerOutput->onInfo(module, msgstream.str());
385  }
386  else if (LOGLEVEL <= Log::VERBOSE)
387  {
388  loggerOutput->onVerbose(module, msgstream.str());
389  }
390  else
391  {
392  loggerOutput->onDebug(module, msgstream.str());
393  }
394  }
395 
396  template<Log LOGLEVEL, typename... Args>
397  typename std::enable_if<L <
398  LOGLEVEL&& MERCURY_LOGLEVEL<LOGLEVEL, void>::type
399 
400  operator()(const LL<LOGLEVEL> log, const char* format UNUSED, Args&& ... arg UNUSED)
401  {
402 
403  }
404 
405  //std::string is sometimes convenient, but always slow, so where possible, don't convert the const char* to a string before converting it back
406  template<Log LOGLEVEL, typename... Args>
407  void operator()(const LL<LOGLEVEL> log, const std::string& format UNUSED, Args&& ... arg UNUSED)
408  {
409  (*this)(log, format.c_str(), arg...);
410  }
411 
412 
413 
414  /*
415  *
416  * \brief Asserts on this logger
417  *
418  * Evaluates an assertion, prints an error message and aborts
419  * in case of a failure. This message can be redirected,
420  * and will be send to loggerOuptput->onFatal.
421  *
422  * \arg assertion An assertion, which must be true
423  * \arg format Message format, where % can be used as a placeholder for arguments.
424  * \arg arg... Any arguments which needs to be replaced.
425  */
426 
427 
428  //the conversion from "" to a std::sting is so slow, it takes 50% of the total run time for a release build...
429  template<typename... Args>
430  typename std::enable_if<(ASSERTS) && (sizeof...(Args) >= 0), void>::type
431  assert(bool assertion, const char* format, Args&& ... arg)
432  {
433  assert_always(assertion, format, arg...);
434  }
435 
436  template<typename... Args>
437  typename std::enable_if<!((ASSERTS) && sizeof...(Args) >= 0), void>::type
438  assert(bool assertion, const char* format, Args&& ... arg)
439  {
440  }
441 
442  template<typename... Args>
443  void assert(bool assertion, const std::string format, Args&& ... arg)
444  {
445  assert(assertion, format.c_str(), arg...);
446  }
447 
448  template<typename... Args>
449  void assert_always(bool assertion, const char* format, Args&& ... arg)
450  {
451  if (!assertion)
452  {
453  std::stringstream msgstream;
454  createMessage(msgstream, format, arg...);
455  loggerOutput->onFatal(module, msgstream.str());
456  }
457 
458  }
459 
460  template<typename... Args>
461  void assert_always(bool assertion, const std::string format, Args&& ... arg)
462  {
463  assert_always(assertion, format.c_str(), arg...);
464  }
465 
470  template<typename... Args>
471  void log(const Log loglevel, const std::string& format, Args&& ... arg)
472  {
473  if (loglevel <= L || loglevel <= MERCURY_LOGLEVEL)
474  {
475  std::stringstream msgstream;
476  createMessage(msgstream, format.c_str(), arg...);
477  if (loglevel <= Log::FATAL)
478  {
479  loggerOutput->onFatal(module, msgstream.str());
480  }
481  else if (loglevel <= Log::ERROR)
482  {
483  loggerOutput->onError(module, msgstream.str());
484  }
485  else if (loglevel <= Log::WARN)
486  {
487  loggerOutput->onWarn(module, msgstream.str());
488  }
489  else if (loglevel <= Log::INFO)
490  {
491  loggerOutput->onInfo(module, msgstream.str());
492  }
493  else if (loglevel <= Log::VERBOSE)
494  {
495  loggerOutput->onVerbose(module, msgstream.str());
496  }
497  else
498  {
499  loggerOutput->onDebug(module, msgstream.str());
500  }
501  }
502  }
503 
504 private:
509  template<typename Arg1, typename... Args>
510  void createMessage(std::stringstream& msg, const char* fmt,
511  Arg1&& arg, Args&& ... args)
512  {
513  bool doSkipNext = false;
514  while (*fmt != '%' || doSkipNext)
515  {
516  //Make sure we're not running past the end of our formatting string.
517  if (*fmt == '\0')
518  return;
519 
520  if (*fmt == '\\' && !doSkipNext)
521  { //Escape for the %sign
522  doSkipNext = true;
523  fmt++;
524  }
525  else
526  {
527  msg << *fmt;
528  fmt++;
529  doSkipNext = false;
530  }
531  }
532 
533  fmt++; //Consume the % sign
535  msg << arg;
536  createMessage(msg, fmt, args...);//and recursively call ourselve / the method below.
537  }
538 
542  template<typename Arg1>
543  void createMessage(std::stringstream& msg, const char* fmt, Arg1&& arg)
544  {
545  bool doSkipNext = false;
546  while (*fmt != '%' || doSkipNext)
547  {
548  if (*fmt == '\0') // End of string
549  return;
550 
551  if (*fmt == '\\' && !doSkipNext)
552  { //Escape for the %sign and the \sign
553  doSkipNext = true;
554  fmt++;
555  }
556  else
557  { //invoke the replacement
558  msg << *fmt;
559  fmt++;
560  doSkipNext = false;
561  }
562  }
563  fmt++; //Consume the % sign
564  msg << arg << fmt;
565  }
566 
570  void createMessage(std::stringstream& msg, const char* message)
571  {
572  msg << message;
573  }
574 };
575 
585 
587 
588 //just emptying the functions is not sufficiently aggressive in disabling the actual (costly) comparison
589 #if !MERCURY_ASSERTS
590 #define assert(e,...) assert(true,"")
591 #endif
592 
593 #endif
LoggerOutput * loggerOutput
Declaration of the output functions. If the output needs to be redirected, please swap the loggerOutp...
Definition: Logger.cc:168
See.
Definition: Logger.h:215
LL< Log::INFO > INFO
Info log level.
Definition: Logger.cc:55
LL< Log::DEBUG > DEBUG
Debug information.
Definition: Logger.cc:58
LL< Log::ERROR > ERROR
Error log level.
Definition: Logger.cc:53
void createMessage(std::stringstream &msg, const char *message)
Terminating case / no argument call.
Definition: Logger.h:570
std::function< void(std::string, std::string)> onError
Definition: Logger.h:196
void createMessage(std::stringstream &msg, const char *fmt, Arg1 &&arg)
Terminating case / argument call.
Definition: Logger.h:543
std::function< void(std::string, std::string)> onDebug
Definition: Logger.h:200
Logger(const std::string name)
constructor
Definition: Logger.h:340
~Logger()=default
destructor
std::enable_if<!((L< LOGLEVEL)&&(MERCURY_LOGLEVEL< LOGLEVEL)), void >::type operator()(const LL< LOGLEVEL > log, const char *format UNUSED, Args &&...arg UNUSED)
Definition: Logger.h:366
std::function< void(std::string, std::string)> onFatal
Definition: Logger.h:195
Mdouble log(Mdouble Power)
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:175
const std::string module
The module name of this actual logger.
Definition: Logger.h:332
std::function< void(std::string, std::string)> onVerbose
Definition: Logger.h:199
Default functions for output generation.
Definition: Logger.h:192
LL< Log::DEFAULT > DEFAULT
Default log level.
Definition: Logger.cc:56
Logger< CG_LOGLEVEL > cgLogger
#define UNUSED
Definition: GeneralDefine.h:39
std::function< void(std::string, std::string)> onWarn
Definition: Logger.h:197
LL< Log::VERBOSE > VERBOSE
Verbose information.
Definition: Logger.cc:57
std::function< void(std::string, std::string)> onInfo
Definition: Logger.h:198
#define assert(e,...)
Definition: Logger.h:590
Logger< MERCURY_LOGLEVEL > logger
Log
The different loglevels.
Definition: Logger.h:165
#define MERCURY_LOGLEVEL
Definition: Logger.h:41
LL< Log::FATAL > FATAL
Fatal log level.
Definition: Logger.cc:52
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:510
LL< Log::WARN > WARN
Warning log level.
Definition: Logger.cc:54
Tag for template metaprogramming.
Definition: Logger.h:227