clutchlog  0.12
clutchlog.h
Go to the documentation of this file.
1 #pragma once
2 #ifndef CLUTCHLOG_H
3 #define CLUTCHLOG_H
5 
7 #include <ciso646>
8  #ifdef FSEXPERIMENTAL
9  #include <experimental/filesystem>
10  namespace fs = std::experimental::filesystem;
11 #else
12  #include <filesystem>
13  namespace fs = std::filesystem;
14 #endif
15 
16 #include <iostream>
17 #include <sstream>
18 #include <fstream>
19 #include <cassert>
20 #include <cstdlib>
21 #include <string>
22 #include <limits>
23 #include <regex>
24 #include <map>
25 
27 #if __has_include(<execinfo.h>) && __has_include(<stdlib.h>) && __has_include(<libgen.h>)
28  #include <execinfo.h> // execinfo
29  #include <stdlib.h> // getenv
30  #include <libgen.h> // basename
31  #define CLUTCHLOG_HAVE_UNIX_SYSINFO 1
32 #else
33  #define CLUTCHLOG_HAVE_UNIX_SYSINFO 0
34 #endif
35 
37 #if __has_include(<sys/ioctl.h>) && __has_include(<stdio.h>) && __has_include(<unistd.h>)
38  #include <sys/ioctl.h>
39  #include <stdio.h>
40  #include <unistd.h>
41  #define CLUTCHLOG_HAVE_UNIX_SYSIOCTL 1
42 #else
43  #define CLUTCHLOG_HAVE_UNIX_SYSIOCTL 0
44 #endif
45 
46 
47 /**********************************************************************
48  * Enable by default in Debug builds.
49  **********************************************************************/
50 #ifndef WITH_CLUTCHLOG
51  #ifndef NDEBUG
52  #define WITH_CLUTCHLOG
54  #endif
55 #endif
56 
57 /**********************************************************************
58  * Macros definitions
59  **********************************************************************/
60 #ifdef WITH_CLUTCHLOG
61 
65 #ifndef CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG
66  #define CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG clutchlog::level::progress
68 #endif // CLUTCHLOG_DEFAULT_DEPTH_BUILT
69 
76 #define CLUTCHLOC __FILE__, __FUNCTION__, __LINE__
78 
80 #ifndef NDEBUG
81  #define CLUTCHLOG( LEVEL, WHAT ) do { \
82  auto& clutchlog__logger = clutchlog::logger(); \
83  std::ostringstream clutchlog__msg ; clutchlog__msg << WHAT; \
84  clutchlog__logger.log(clutchlog::level::LEVEL, clutchlog__msg.str(), CLUTCHLOC); \
85  } while(0)
86 #else // not Debug build.
87  #define CLUTCHLOG( LEVEL, WHAT ) do { \
88  if(clutchlog::level::LEVEL <= CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG) { \
89  auto& clutchlog__logger = clutchlog::logger(); \
90  std::ostringstream clutchlog__msg ; clutchlog__msg << WHAT; \
91  clutchlog__logger.log(clutchlog::level::LEVEL, clutchlog__msg.str(), CLUTCHLOC); \
92  } \
93  } while(0)
94 #endif // NDEBUG
95 
97 #ifndef NDEBUG
98  #define CLUTCHDUMP( LEVEL, CONTAINER, FILENAME ) do { \
99  auto& clutchlog__logger = clutchlog::logger(); \
100  clutchlog__logger.dump(clutchlog::level::LEVEL, std::begin(CONTAINER), std::end(CONTAINER), \
101  CLUTCHLOC, FILENAME, CLUTCHDUMP_DEFAULT_SEP); \
102  } while(0)
103 #else // not Debug build.
104  #define CLUTCHDUMP( LEVEL, CONTAINER, FILENAME ) do { \
105  if(clutchlog::level::LEVEL <= CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG) { \
106  auto& clutchlog__logger = clutchlog::logger(); \
107  clutchlog__logger.dump(clutchlog::level::LEVEL, std::begin(CONTAINER), std::end(CONTAINER), \
108  CLUTCHLOC, FILENAME, CLUTCHDUMP_DEFAULT_SEP); \
109  } \
110  } while(0)
111 #endif // NDEBUG
112 
114 #ifndef NDEBUG
115  #define CLUTCHFUNC( LEVEL, FUNC, ... ) do { \
116  auto& clutchlog__logger = clutchlog::logger(); \
117  clutchlog::scope_t clutchlog__scope = clutchlog__logger.locate(clutchlog::level::LEVEL, CLUTCHLOC); \
118  if(clutchlog__scope.matches) { \
119  FUNC(__VA_ARGS__); \
120  } \
121  } while(0)
122 #else // not Debug build.
123  #define CLUTCHFUNC( LEVEL, FUNC, ... ) do { \
124  if(clutchlog::level::LEVEL <= CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG) { \
125  auto& clutchlog__logger = clutchlog::logger(); \
126  clutchlog::scope_t clutchlog__scope = clutchlog__logger.locate(clutchlog::level::LEVEL, CLUTCHLOC); \
127  if(clutchlog__scope.matches) { \
128  FUNC(__VA_ARGS__); \
129  } \
130  } \
131  } while(0)
132 #endif // NDEBUG
133 
135 #ifndef NDEBUG
136  #define CLUTCHCODE( LEVEL, ... ) do { \
137  auto& clutchlog__logger = clutchlog::logger(); \
138  clutchlog::scope_t clutchlog__scope = clutchlog__logger.locate(clutchlog::level::LEVEL, CLUTCHLOC); \
139  if(clutchlog__scope.matches) { \
140  __VA_ARGS__ \
141  } \
142  } while(0)
143 #else // not Debug build.
144  #define CLUTCHCODE( LEVEL, CODE ) do { \
145  if(clutchlog::level::LEVEL <= CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG) { \
146  auto& clutchlog__logger = clutchlog::logger(); \
147  clutchlog::scope_t clutchlog__scope = clutchlog__logger.locate(clutchlog::level::LEVEL, CLUTCHLOC); \
148  if(clutchlog__scope.matches) { \
149  CODE \
150  } \
151  } \
152  } while(0)
153 #endif // NDEBUG
154 
157 #else // not WITH_CLUTCHLOG
158  // Disabled macros can still be called in Release builds.
159  #define CLUTCHLOG( LEVEL, WHAT ) do {/*nothing*/} while(0)
160  #define CLUTCHDUMP( LEVEL, CONTAINER, FILENAME ) do {/*nothing*/} while(0)
161  #define CLUTCHFUNC( LEVEL, FUNC, ... ) do {/*nothing*/} while(0)
162  #define CLUTCHCODE( LEVEL, CODE ) do {/*nothing*/} while(0)
163 #endif // WITH_CLUTCHLOG
164 
165 /**********************************************************************
166  * Implementation
167  **********************************************************************/
168 
169 #ifdef WITH_CLUTCHLOG
170 
178 {
179  protected:
180 
185  #ifndef NDEBUG
186  #ifndef CLUTCHLOG_DEFAULT_FORMAT
187  #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 // Enables: name, depth and depth_marks
189  #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1 // Enables: hfill
190  #define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg} {hfill} {func} @ {file}:{line}\n"
191  #else
192  #define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n"
193  #endif
194  #else
195  #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
196  #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg} {hfill} {func} @ {file}:{line}\n"
197  #else
198  #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\t\t\t\t\t{func} @ {file}:{line}\n"
199  #endif
200  #endif
201  #endif
202  #else
203  #ifndef CLUTCHLOG_DEFAULT_FORMAT
204  #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
206  #define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg} {hfill} {func}\n"
207  #else
208  #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\t\t\t\t\t{func}\n"
209  #endif
210  #endif
211  #endif
212  static inline std::string default_format = CLUTCHLOG_DEFAULT_FORMAT;
214 
215  #ifndef NDEBUG
216  #ifndef CLUTCHDUMP_DEFAULT_FORMAT
217  #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
219  #define CLUTCHDUMP_DEFAULT_FORMAT "# [{name}] {level} in {func} (at depth {depth}) @ {file}:{line}"
220  #else
221  #define CLUTCHDUMP_DEFAULT_FORMAT "# {level} in {func} @ {file}:{line}"
222  #endif
223  #endif // CLUTCHDUMP_DEFAULT_FORMAT
224  #else
225  #ifndef CLUTCHDUMP_DEFAULT_FORMAT
226  #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
228  #define CLUTCHDUMP_DEFAULT_FORMAT "# [{name}] {level} in {func} (at depth {depth})"
229  #else
230  #define CLUTCHDUMP_DEFAULT_FORMAT "# {level} in {func}"
231  #endif
232  #endif // CLUTCHDUMP_DEFAULT_FORMAT
233  #endif
234  static inline std::string dump_default_format = CLUTCHDUMP_DEFAULT_FORMAT;
236 
237  #ifndef CLUTCHDUMP_DEFAULT_SEP
238  #define CLUTCHDUMP_DEFAULT_SEP "\n"
240  #endif // CLUTCHDUMP_DEFAULT_SEP
241  static inline std::string dump_default_sep = CLUTCHDUMP_DEFAULT_SEP;
243 
244  #ifndef CLUTCHLOG_DEFAULT_DEPTH_MARK
245  #define CLUTCHLOG_DEFAULT_DEPTH_MARK ">"
247  #endif // CLUTCHLOG_DEFAULT_DEPTH_MARK
248  static inline std::string default_depth_mark = CLUTCHLOG_DEFAULT_DEPTH_MARK;
250 
251  #ifndef CLUTCHLOG_STRIP_CALLS
252  #define CLUTCHLOG_STRIP_CALLS 5
254  #endif // CLUTCHLOG_STRIP_CALLS
255  static inline unsigned int default_strip_calls = CLUTCHLOG_STRIP_CALLS;
257 
258  #ifndef CLUTCHLOG_DEFAULT_HFILL_MARK
259  #define CLUTCHLOG_DEFAULT_HFILL_MARK '.'
261  #endif // CLUTCHLOG_DEFAULT_HFILL_MARK
264 
265 
266  #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
267  #ifndef CLUTCHLOG_DEFAULT_HFILL_MAX
268  #define CLUTCHLOG_DEFAULT_HFILL_MAX 300
269  #endif
270  #ifndef CLUTCHLOG_DEFAULT_HFILL_MIN
271  #define CLUTCHLOG_DEFAULT_HFILL_MIN 150
272  #endif
273  #endif
274  static inline size_t default_hfill_max = CLUTCHLOG_DEFAULT_HFILL_MAX;
277  static inline size_t default_hfill_min = CLUTCHLOG_DEFAULT_HFILL_MIN;
278 
279  // NOTE: there is no CLUTCHLOG_HFILL_STYLE for defaulting,
280  // but you can still set `hfill_style(...)` on the logger singleton.
281  /* @} DefaultConfig */
282  /* @} */
283 
284 
285  public:
286 
296  static clutchlog& logger()
297  {
298  static clutchlog instance;
299  return instance;
300  }
301 
303  enum level {critical=0, error=1, warning=2, progress=3, note=4, info=5, debug=6, xdebug=7};
304 
314  class fmt {
315  public:
317  enum class fg {
318  black = 30,
319  red = 31,
320  green = 32,
321  yellow = 33,
322  blue = 34,
323  magenta = 35,
324  cyan = 36,
325  white = 37,
326  none
327  } fore;
328 
330  enum class bg {
331  black = 40,
332  red = 41,
333  green = 42,
334  yellow = 43,
335  blue = 44,
336  magenta = 45,
337  cyan = 46,
338  white = 47,
339  none
340  } back;
341 
343  enum class typo {
344  reset = 0,
345  bold = 1,
346  underline = 4,
347  inverse = 7,
348  none
349  } style;
350 
352  fmt() : fore(fg::none), back(bg::none), style(typo::none) {}
353 
356  fmt( fg f, bg b = bg::none, typo s = typo::none) : fore(f), back(b), style(s) {}
357  fmt( fg f, typo s , bg b = bg::none) : fore(f), back(b), style(s) {}
358  fmt( bg b, fg f = fg::none, typo s = typo::none) : fore(f), back(b), style(s) {}
359  fmt( bg b, typo s , fg f = fg::none) : fore(f), back(b), style(s) {}
360  fmt(typo s, fg f = fg::none, bg b = bg::none) : fore(f), back(b), style(s) {}
361  fmt(typo s, bg b , fg f = fg::none) : fore(f), back(b), style(s) {}
364  protected:
366  std::ostream& print_on( std::ostream& os) const
367  {
368  std::vector<int> codes; codes.reserve(3);
369  if(this->fore != fg::none) { codes.push_back(static_cast<int>(this->fore ));}
370  if(this->back != bg::none) { codes.push_back(static_cast<int>(this->back ));}
371  if(this->style != typo::none) { codes.push_back(static_cast<int>(this->style));}
372  if(codes.size() == 0) {return os;}
373 
374  os << "\033[";
375  assert(codes.size() > 0);
376  os << codes[0];
377  for(size_t i=1; i < codes.size(); ++i) {
378  os << ";" << codes[i];
379  }
380  os << "m";
381  return os;
382  }
383 
384  public:
396  friend std::ostream& operator<<(std::ostream& os, const fmt& fmt)
397  {
398  return fmt.print_on(os);
399  }
400 
411  std::string operator()( const std::string& msg ) const
412  {
413  std::ostringstream os;
414  this->print_on(os);
415  fmt reset(fmt::typo::reset);
416  os << msg;
417  reset.print_on(os);
418  return os.str();
419  }
420 
423  std::string str() const
424  {
425  std::ostringstream os;
426  this->print_on(os);
427  return os.str();
428  }
429  }; // fmt class
430 
436  public:
437  clutchlog(clutchlog const&) = delete;
438  void operator=(clutchlog const&) = delete;
439 
440  private:
441  clutchlog() :
442  // system, main, log
444  _level_word({
445  {level::critical,"Critical"},
446  {level::error ,"Error"},
447  {level::warning ,"Warning"},
448  {level::progress,"Progress"},
449  {level::note ,"Note"},
450  {level::info ,"Info"},
451  {level::debug ,"Debug"},
452  {level::xdebug ,"XDebug"}
453  }),
454  _level_fmt({
455  {level::critical,fmt(fmt::fg::red, fmt::typo::underline)},
456  {level::error ,fmt(fmt::fg::red, fmt::typo::bold)},
457  {level::warning ,fmt(fmt::fg::magenta, fmt::typo::bold)},
458  {level::progress,fmt()},
459  {level::note ,fmt()},
460  {level::info ,fmt()},
461  {level::debug ,fmt()},
462  {level::xdebug ,fmt()}
463  }),
466  #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
467  _hfill_char(clutchlog::default_hfill_char),
468  _hfill_fmt(fmt::fg::none),
469  _hfill_max(clutchlog::default_hfill_max),
470  _hfill_min(clutchlog::default_hfill_min),
471  #endif
472  _out(&std::clog),
473  #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
474  _depth(std::numeric_limits<size_t>::max() - _strip_calls),
475  _depth_mark(clutchlog::default_depth_mark),
476  #endif
477  _stage(level::error),
478  _in_file(".*"),
479  _in_func(".*"),
480  _in_line(".*")
481  {
482  // Reverse the level->word map into a word->level map.
483  for(auto& lw : _level_word) {
484  _word_level[lw.second] = lw.first;
485  }
486 #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
487  struct winsize w;
488  ioctl(STDERR_FILENO, TIOCGWINSZ, &w);
489  _nb_columns = std::max(std::min((size_t)w.ws_col, default_hfill_max), default_hfill_min);
490 #endif
491  }
492 
493  protected:
495  size_t _strip_calls;
497  const std::map<level,std::string> _level_word;
499  std::map<std::string,level> _word_level;
501  std::map<level,fmt> _level_fmt;
503  std::string _format_log;
505  std::string _format_dump;
506  #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
507 
508  char _hfill_char;
510  fmt _hfill_fmt;
512  size_t _hfill_max;
514  size_t _hfill_min;
515  #endif
516 
517  std::ostream* _out;
518  #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
519 
520  size_t _depth;
522  std::string _depth_mark;
523  #endif
524 
527  std::regex _in_file;
529  std::regex _in_func;
531  std::regex _in_line;
532 
533 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
534 
535  static const size_t _max_buffer = 4096;
536 #endif
537 
538 #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
539 
540  size_t _nb_columns;
541 #endif
542 
544  public:
545 
549  void format(const std::string& format) {_format_log = format;}
552  std::string format() const {return _format_log;}
553 
555  void format_comment(const std::string& format) {_format_dump = format;}
557  std::string format_comment() const {return _format_dump;}
558 
560  void out(std::ostream& out) {_out = &out;}
562  std::ostream& out() {return *_out;}
563 
564 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
565  void depth(size_t d) {_depth = d;}
568  size_t depth() const {return _depth;}
569 
571  void depth_mark(const std::string mark) {_depth_mark = mark;}
573  std::string depth_mark() const {return _depth_mark;}
574 
576  void strip_calls(const size_t n) {_strip_calls = n;}
578  size_t strip_calls() const {return _strip_calls;}
579 #endif
580 #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
581  void hfill_mark(const char mark) {_hfill_char = mark;}
584  char hfill_mark() const {return _hfill_char;}
586  void hfill_style(fmt style) {_hfill_fmt = style;}
591  template<class ... FMT>
592  void hfill_style(FMT... styles) { this->hfill_style(fmt(styles...)); }
594  fmt hfill_style() const {return _hfill_fmt;}
596  void hfill_max(const size_t nmax) {_hfill_max = nmax;}
598  size_t hfill_max() {return _hfill_max;}
600  void hfill_min(const size_t nmin) {_hfill_min = nmin;}
602  size_t hfill_min() {return _hfill_min;}
603 #endif
604 
606  void threshold(level l) {_stage = l;}
608  void threshold(const std::string& l) {_stage = this->level_of(l);}
610  level threshold() const {return _stage;}
612  const std::map<std::string,level>& levels() const { return _word_level;}
613 
618  level level_of(const std::string name)
619  {
620  const auto ilevel = _word_level.find(name);
621  if( ilevel != std::end(_word_level)) {
622  return ilevel->second;
623  } else {
624  throw std::out_of_range("'" + name + "' is not a valid log level name");
625  }
626  }
627 
629  void file(std::string file) {_in_file = file;}
631  void func(std::string func) {_in_func = func;}
633  void line(std::string line) {_in_line = line;}
634 
636  void location(
637  const std::string& in_file,
638  const std::string& in_function=".*",
639  const std::string& in_line=".*"
640  )
641  {
642  file(in_file);
643  func(in_function);
644  line(in_line);
645  }
646 
651  template<class ... FMT>
652  void style(level stage, FMT... styles) { this->style(stage,fmt(styles...)); }
654  void style(level stage, fmt style) { _level_fmt.at(stage) = style; }
656  fmt style(level stage) const { return _level_fmt.at(stage); }
657 
660  public:
661 
665  struct scope_t {
668  bool matches;
671 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
672 
673  size_t depth;
674 #endif
675 
676  bool there;
679  matches(false),
680  stage(level::xdebug),
682  depth(0),
683 #endif
684  there(false)
685  {}
686  }; // scope_t
687 
688 
691  const level& stage,
692  const std::string& file,
693  const std::string& func,
694  const size_t line
695  ) const
696  {
697  scope_t scope; // False scope by default.
698 
699  /***** Log level stage *****/
700  // Test stage first, because it's fastest.
701  scope.stage = stage;
702  if(not (scope.stage <= _stage)) {
703  // Bypass useless computations if no match
704  // because of the stage.
705  return scope;
706  }
707 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
708  /***** Stack depth *****/
709  // Backtrace in second, quite fast.
710  size_t stack_depth;
711  void *buffer[_max_buffer];
712  stack_depth = backtrace(buffer, _max_buffer);
713  scope.depth = stack_depth;
714  if(not (scope.depth <= _depth + _strip_calls)) {
715  // Bypass if no match.
716  return scope;
717  }
718 #endif
719 
720  /***** Location *****/
721  // Location last, slowest.
722  std::ostringstream sline; sline << line;
723  scope.there =
724  std::regex_search(file, _in_file)
725  and std::regex_search(func, _in_func)
726  and std::regex_search(sline.str(), _in_line);
727 
728  // No need to retest stage and depth, which are true here.
729  scope.matches = scope.there;
730 
731  return scope;
732  } // locate
733 
741  std::string replace(
742  const std::string& form,
743  const std::string& mark,
744  const std::string& tag
745  ) const
746  {
747  // Useless debug code, unless something fancy would be done with name tags.
748  // std::regex re;
749  // try {
750  // re = std::regex(mark);
751  //
752  // } catch(const std::regex_error& e) {
753  // std::cerr << "ERROR with a regular expression \"" << mark << "\": ";
754  // switch(e.code()) {
755  // case std::regex_constants::error_collate:
756  // std::cerr << "the expression contains an invalid collating element name";
757  // break;
758  // case std::regex_constants::error_ctype:
759  // std::cerr << "the expression contains an invalid character class name";
760  // break;
761  // case std::regex_constants::error_escape:
762  // std::cerr << "the expression contains an invalid escaped character or a trailing escape";
763  // break;
764  // case std::regex_constants::error_backref:
765  // std::cerr << "the expression contains an invalid back reference";
766  // break;
767  // case std::regex_constants::error_brack:
768  // std::cerr << "the expression contains mismatched square brackets ('[' and ']')";
769  // break;
770  // case std::regex_constants::error_paren:
771  // std::cerr << "the expression contains mismatched parentheses ('(' and ')')";
772  // break;
773  // case std::regex_constants::error_brace:
774  // std::cerr << "the expression contains mismatched curly braces ('{' and '}')";
775  // break;
776  // case std::regex_constants::error_badbrace:
777  // std::cerr << "the expression contains an invalid range in a {} expression";
778  // break;
779  // case std::regex_constants::error_range:
780  // std::cerr << "the expression contains an invalid character range (e.g. [b-a])";
781  // break;
782  // case std::regex_constants::error_space:
783  // std::cerr << "there was not enough memory to convert the expression into a finite state machine";
784  // break;
785  // case std::regex_constants::error_badrepeat:
786  // std::cerr << "one of *?+{ was not preceded by a valid regular expression";
787  // break;
788  // case std::regex_constants::error_complexity:
789  // std::cerr << "the complexity of an attempted match exceeded a predefined level";
790  // break;
791  // case std::regex_constants::error_stack:
792  // std::cerr << "there was not enough memory to perform a match";
793  // break;
794  // default:
795  // std::cerr << "unknown error";
796  // }
797  // std::cerr << std::endl;
798  // throw;
799  // } // catch
800 
801  const std::regex re(mark);
802  return std::regex_replace(form, re, tag);
803  }
804 
806  std::string replace(
807  const std::string& form,
808  const std::string& mark,
809  const size_t tag
810  ) const
811  {
812  std::ostringstream stag; stag << tag;
813  return replace(form, mark, stag.str());
814  }
815 
817  std::string format(
818  std::string row,
819  const std::string& what,
821  const std::string& name,
822 #endif
823  const level& stage,
824  const std::string& file,
825  const std::string& func,
826  const size_t line
828  ,
829  const size_t depth
830 #endif
831  ) const
832  {
833  row = replace(row, "\\{msg\\}", what);
834  row = replace(row, "\\{file\\}", file);
835  row = replace(row, "\\{func\\}", func);
836  row = replace(row, "\\{line\\}", line);
837 
838  row = replace(row, "\\{level\\}", _level_word.at(stage));
839  std::string letter(1, _level_word.at(stage).at(0)); // char -> string
840  row = replace(row, "\\{level_letter\\}", letter);
841 
842 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
843  row = replace(row, "\\{name\\}", name);
844  row = replace(row, "\\{depth\\}", depth - _strip_calls);
845 
846  std::ostringstream chevrons;
847  for(size_t i = _strip_calls; i < depth; ++i) {
848  chevrons << _depth_mark;
849  }
850  row = replace(row, "\\{depth_marks\\}", chevrons.str());
851 #endif
852 
853  row = replace(row, "\\{level_fmt\\}", _level_fmt.at(stage).str());
854 
855 #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
856  // hfill is replaced last to allow for correct line width estimation.
857  const std::string raw_row = replace(row, "\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]", "");
858  const std::string hfill_tag = "{hfill}";
859  const size_t hfill_pos = row.find(hfill_tag);
860  const size_t raw_hfill_pos = raw_row.find(hfill_tag);
861  const size_t nb_columns = std::max(std::min((size_t)_nb_columns, _hfill_max), _hfill_min);
862  if(hfill_pos != std::string::npos) {
863  assert(raw_hfill_pos != std::string::npos);
864  if(nb_columns > 0) {
865  const size_t left_len = raw_hfill_pos;
866  const size_t right_len = raw_row.size() - raw_hfill_pos - hfill_tag.size();
867  if(right_len+left_len > nb_columns) {
868  // The right part would go over the terminal width: add a new row.
869  if(right_len < nb_columns) {
870  // There is room for the right part on a new line.
871  const std::string hfill(std::max((size_t)0, nb_columns-right_len), _hfill_char);
872  const std::string hfill_styled = _hfill_fmt(hfill);
873  row = replace(row, "\\{hfill\\}", "\n"+hfill_styled);
874  } else {
875  // Right part still goes over columns: let it go.
876  const std::string hfill(1, _hfill_char);
877  const std::string hfill_styled = _hfill_fmt(hfill);
878  row = replace(row, "\\{hfill\\}", "\n"+hfill_styled);
879  }
880  } else {
881  // There is some space in between left and right parts.
882  const std::string hfill(std::max((size_t)0, nb_columns - (right_len+left_len)), _hfill_char);
883  const std::string hfill_styled = _hfill_fmt(hfill);
884  row = replace(row, "\\{hfill\\}", hfill_styled);
885  }
886  } else {
887  // We don't know the terminal width.
888  const std::string hfill(1, _hfill_char);
889  const std::string hfill_styled = _hfill_fmt(hfill);
890  row = replace(row, "\\{hfill\\}", hfill_styled);
891  }
892  }
893 #else
894  // We cannot know the terminal width.
895  const std::string hfill(1, _hfill_char);
896  const std::string hfill_styled = _hfill_fmt(hfill);
897  row = replace(row, "\\{hfill\\}", hfill_styled);
898 #endif
899  return _level_fmt.at(stage)(row);
900  }
901 
903  void log(
904  const level& stage,
905  const std::string& what,
906  const std::string& file, const std::string& func, size_t line
907  ) const
908  {
909  scope_t scope = locate(stage, file, func, line);
910 
911  if(scope.matches) {
912 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
913  *_out << format(_format_log, what, basename(getenv("_")),
914  stage, file, func,
915  line, scope.depth );
916 #else
917  *_out << format(_format_log, what,
918  stage, file, func,
919  line );
920 
921 #endif
922  _out->flush();
923  } // if scopes.matches
924  }
925 
927  template<class In>
928  void dump(
929  const level& stage,
930  const In container_begin, const In container_end,
931  const std::string& file, const std::string& func, size_t line,
932  const std::string& filename_template = "dump_{n}.dat",
933  const std::string sep = dump_default_sep
934  ) const
935  {
936  scope_t scope = locate(stage, file, func, line);
937 
938  if(scope.matches) {
939  const std::string tag = "\\{n\\}";
940  const std::regex re(tag);
941  std::string outfile = "";
942 
943  // If the file name template has the {n} tag.
944  if(std::regex_search(filename_template, re)) {
945  // Increment n until a free one is found.
946  size_t n = 0;
947  do {
948  outfile = replace(filename_template, tag, n);
949  n++;
950  } while( fs::exists( outfile ) );
951 
952  } else {
953  // Use the parameter as is.
954  outfile = filename_template;
955  }
956 
957  std::ofstream fd(outfile);
958 
959  if(_format_dump.size() > 0) {
960 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
961  fd << format(_format_dump, "", basename(getenv("_")),
962  stage, file, func,
963  line, scope.depth );
964 #else
965  fd << format(_format_dump, "",
966  stage, file, func,
967  line );
968 #endif
969  fd << sep; // sep after comment line.
970  }
971 
972  std::copy(container_begin, container_end,
973  std::ostream_iterator<typename In::value_type>(fd, sep.c_str()));
974 
975  fd.close();
976  } // if scopes.matches
977  }
978 
980 };
981 
984 #else // not WITH_CLUTCHLOG
985 
986 
987 /**********************************************************************
988  * Fake implementation
989  **********************************************************************/
990 
991 // Equivalent class with empty methods, will be optimized out
992 // while allowing to actually have calls implemented without WITH_CLUTCHLOG guards.
993 #pragma GCC diagnostic push
994 #pragma GCC diagnostic ignored "-Wreturn-type"
995 class clutchlog
996 {
997  public:
998  static clutchlog& logger() {}
999  enum level {critical=0, error=1, warning=2, progress=3, note=4, info=5, debug=6, xdebug=7};
1000  class fmt {
1001  public:
1002  enum class fg { black, red, green, yellow, blue, magenta, cyan, white, none } fore;
1003  enum class bg { black, red, green, yellow, blue, magenta, cyan, white, none } back;
1004  enum class typo { reset, bold, underline, inverse, none } style;
1005  fmt() : fore(fg::none), back(bg::none), style(typo::none) {}
1006  fmt( fg f, bg b = bg::none, typo s = typo::none) : fore(f), back(b), style(s) {}
1007  fmt( fg f, typo s , bg b = bg::none) : fore(f), back(b), style(s) {}
1008  fmt( bg b, fg f = fg::none, typo s = typo::none) : fore(f), back(b), style(s) {}
1009  fmt( bg b, typo s , fg f = fg::none) : fore(f), back(b), style(s) {}
1010  fmt(typo s, fg f = fg::none, bg b = bg::none) : fore(f), back(b), style(s) {}
1011  fmt(typo s, bg b , fg f = fg::none) : fore(f), back(b), style(s) {}
1012  protected:
1013  std::ostream& print_on(std::ostream&) const {}
1014  public:
1015  friend std::ostream& operator<<(std::ostream&, const fmt&) {}
1016  std::string operator()(const std::string&) const {}
1017  };
1018  public:
1019  clutchlog(clutchlog const&) = delete;
1020  void operator=(clutchlog const&) = delete;
1021  private:
1022  clutchlog() {}
1023  protected:
1024  struct scope_t {};
1025  scope_t locate(
1026  const level&,
1027  const std::string&,
1028  const std::string&,
1029  const size_t
1030  ) const
1031  {}
1032  public:
1033  void format(const std::string&) {}
1034  std::string format() const {}
1035 
1036  void format_comment(const std::string&) {}
1037  std::string format_comment() const {}
1038 
1039  void out(std::ostream&) {}
1040  std::ostream& out() {}
1041 
1042 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
1043  void depth(size_t) {}
1044  size_t depth() const {}
1045 
1046  void depth_mark(const std::string) {}
1047  std::string depth_mark() const {}
1048  void strip_calls(const size_t) {}
1049  size_t strip_calls() const {}
1050 #endif
1051 #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
1052  void hfill_mark(const char) {}
1053  char hfill_mark() const {}
1054  void hfill_fmt(fmt) {}
1055  fmt hfill_fmt() const {}
1056  void hfill_min(const size_t) {}
1057  size_t hfill_min() {}
1058  void hfill_max(const size_t) {}
1059  size_t hfill_max() {}
1060 #endif
1061 
1062  void threshold(level) {}
1063  void threshold(const std::string&) {}
1064  level threshold() const {}
1065  const std::map<std::string,level> levels() const {}
1066  level level_of(const std::string) {}
1067 
1068  void file(std::string) {}
1069  void func(std::string) {}
1070  void line(std::string) {}
1071 
1072 #pragma GCC diagnostic push
1073 #pragma GCC diagnostic ignored "-Wunused-parameter"
1074  void location(
1075  const std::string&,
1076  const std::string& in_function=".*",
1077  const std::string& in_line=".*"
1078  )
1079  {}
1080 #pragma GCC diagnostic pop
1081  template<class ... FMT>
1082  void style(level, FMT...) {}
1083  void style(level, fmt) {}
1084  fmt style(level) const {}
1085  public:
1086  std::string replace(
1087  const std::string&,
1088  const std::string&,
1089  const std::string&
1090  ) const
1091  {}
1092 
1093  std::string replace(
1094  const std::string&,
1095  const std::string&,
1096  const size_t
1097  ) const
1098  {}
1099 
1100  std::string format(
1101  std::string,
1102  const std::string&,
1104  const std::string&,
1105 #endif
1106  const level&,
1107  const std::string&,
1108  const std::string&,
1109  const size_t
1111  ,
1112  const size_t
1113 #endif
1114  ) const
1115  {}
1116 
1117  void log(
1118  const level&,
1119  const std::string&,
1120  const std::string&, const std::string&, size_t
1121  ) const
1122  {}
1123 
1124  template<class In>
1125  void dump(
1126  const level&,
1127  const In, const In,
1128  const std::string&, const std::string&, size_t,
1129  const std::string&,
1130  const std::string
1131  ) const
1132  {}
1133 };
1134 #pragma GCC diagnostic pop
1135 #endif // WITH_CLUTCHLOG
1136 
1137 #endif // CLUTCHLOG_H
clutchlog::default_depth_mark
static std::string default_depth_mark
Default mark for stack depth.
Definition: clutchlog.h:249
clutchlog::_format_log
std::string _format_log
Current format of the standard output.
Definition: clutchlog.h:503
clutchlog::_level_fmt
std::map< level, fmt > _level_fmt
Dictionary of level identifier to their format.
Definition: clutchlog.h:501
clutchlog::log
void log(const level &stage, const std::string &what, const std::string &file, const std::string &func, size_t line) const
Print a log message IF the location matches the given one.
Definition: clutchlog.h:903
clutchlog::fmt::str
std::string str() const
Return the formatting code as a string.
Definition: clutchlog.h:423
clutchlog::line
void line(std::string line)
Set the regular expression filtering the line location.
Definition: clutchlog.h:633
clutchlog::dump_default_format
static std::string dump_default_format
Default format of the comment line in file dump.
Definition: clutchlog.h:235
clutchlog::out
void out(std::ostream &out)
Set the output stream on which to print.
Definition: clutchlog.h:560
clutchlog::dump_default_sep
static std::string dump_default_sep
Default item separator for dump.
Definition: clutchlog.h:242
clutchlog::format
std::string format(std::string row, const std::string &what, const level &stage, const std::string &file, const std::string &func, const size_t line) const
Substitute all tags in the format string with the corresponding information and apply the style corre...
Definition: clutchlog.h:817
CLUTCHLOG_DEFAULT_DEPTH_MARK
#define CLUTCHLOG_DEFAULT_DEPTH_MARK
Compile-time default mark for stack depth.
Definition: clutchlog.h:246
clutchlog::default_strip_calls
static unsigned int default_strip_calls
Number of call stack levels to remove from depth display by default.
Definition: clutchlog.h:256
clutchlog::default_hfill_min
static size_t default_hfill_min
Default minimum width (number of characters) at which to fill for right-aligning the right part of me...
Definition: clutchlog.h:277
clutchlog::replace
std::string replace(const std::string &form, const std::string &mark, const std::string &tag) const
Replace mark by tag in form.
Definition: clutchlog.h:741
clutchlog::fmt::back
enum clutchlog::fmt::bg back
Background color.
clutchlog::fmt::fg
fg
Foreground color codes.
Definition: clutchlog.h:317
clutchlog::fmt::fore
enum clutchlog::fmt::fg fore
Foreground color.
clutchlog::default_hfill_char
static char default_hfill_char
Default character used as a filling for right-align the right part of messages with "{hfill}".
Definition: clutchlog.h:263
clutchlog::scope_t::matches
bool matches
Everything is compatible.
Definition: clutchlog.h:668
clutchlog::fmt::style
enum clutchlog::fmt::typo style
Typographic style.
clutchlog::format_comment
void format_comment(const std::string &format)
Set the template string for dumps.
Definition: clutchlog.h:555
clutchlog::file
void file(std::string file)
Set the regular expression filtering the file location.
Definition: clutchlog.h:629
clutchlog::locate
scope_t locate(const level &stage, const std::string &file, const std::string &func, const size_t line) const
Gather information on the current location of the call.
Definition: clutchlog.h:690
clutchlog::fmt::fmt
fmt()
&#160;Empty constructor, only useful for a no-op formatter.
Definition: clutchlog.h:352
clutchlog::style
void style(level stage, fmt style)
Set the style (color and typo) of the given log level, passing a fmt instance.
Definition: clutchlog.h:654
clutchlog::threshold
void threshold(level l)
Set the log level (below which logs are not printed) with an identifier.
Definition: clutchlog.h:606
clutchlog::threshold
level threshold() const
Get the log level below which logs are not printed.
Definition: clutchlog.h:610
clutchlog::level
level
Available log levels.
Definition: clutchlog.h:303
clutchlog::default_hfill_max
static size_t default_hfill_max
Default maximum width (number of characters) for which to fill for right-aligning the right part of m...
Definition: clutchlog.h:275
clutchlog::scope_t::scope_t
scope_t()
Constructor.
Definition: clutchlog.h:678
clutchlog::_in_func
std::regex _in_func
Current function location filter.
Definition: clutchlog.h:529
clutchlog::fmt::bg
bg
Background color codes.
Definition: clutchlog.h:330
clutchlog::default_format
static std::string default_format
Default format of the messages.
Definition: clutchlog.h:213
clutchlog::logger
static clutchlog & logger()
Get the logger instance.
Definition: clutchlog.h:296
clutchlog::dump
void dump(const level &stage, const In container_begin, const In container_end, const std::string &file, const std::string &func, size_t line, const std::string &filename_template="dump_{n}.dat", const std::string sep=dump_default_sep) const
Dump a serializable container after a comment line with log information.
Definition: clutchlog.h:928
clutchlog::fmt
Color and style formatter for ANSI terminal escape sequences.
Definition: clutchlog.h:314
clutchlog::func
void func(std::string func)
Set the regular expression filtering the function location.
Definition: clutchlog.h:631
clutchlog::format
std::string format() const
Get the template string.
Definition: clutchlog.h:552
clutchlog::_in_file
std::regex _in_file
Current file location filter.
Definition: clutchlog.h:527
clutchlog::style
void style(level stage, FMT... styles)
Set the style (color and typo) of the given log level.
Definition: clutchlog.h:652
clutchlog::level_of
level level_of(const std::string name)
Return the log level tag corresponding to the given pre-configured name.
Definition: clutchlog.h:618
clutchlog::_level_word
const std::map< level, std::string > _level_word
Dictionary of level identifier to their string representation.
Definition: clutchlog.h:497
clutchlog::fmt::operator()
std::string operator()(const std::string &msg) const
Format the given string with the currently encoded format.
Definition: clutchlog.h:411
CLUTCHLOG_DEFAULT_FORMAT
#define CLUTCHLOG_DEFAULT_FORMAT
Compile-time default format of the messages (debug mode: with absolute location).
Definition: clutchlog.h:198
clutchlog::_in_line
std::regex _in_line
Current line location filter.
Definition: clutchlog.h:531
clutchlog::replace
std::string replace(const std::string &form, const std::string &mark, const size_t tag) const
Replace mark by tag in form, converting tag to its string representation first.
Definition: clutchlog.h:806
clutchlog::format_comment
std::string format_comment() const
Get the template string for dumps.
Definition: clutchlog.h:557
clutchlog::_format_dump
std::string _format_dump
Current format of the file output.
Definition: clutchlog.h:505
CLUTCHDUMP_DEFAULT_SEP
#define CLUTCHDUMP_DEFAULT_SEP
Compile-time default item separator for dump.
Definition: clutchlog.h:239
clutchlog::scope_t
Structure holding a location matching.
Definition: clutchlog.h:666
CLUTCHLOG_DEFAULT_HFILL_MARK
#define CLUTCHLOG_DEFAULT_HFILL_MARK
Character used as a filling for right-align the right part of messages with "{hfill}".
Definition: clutchlog.h:260
clutchlog::fmt::print_on
std::ostream & print_on(std::ostream &os) const
Print the currently encoded format escape code on the given output stream.
Definition: clutchlog.h:366
clutchlog::_out
std::ostream * _out
Standard output.
Definition: clutchlog.h:517
clutchlog::out
std::ostream & out()
Get the output stream on which to print.
Definition: clutchlog.h:562
clutchlog::threshold
void threshold(const std::string &l)
Set the log level (below which logs are not printed) with a string.
Definition: clutchlog.h:608
clutchlog::levels
const std::map< std::string, level > & levels() const
Get the map of available log levels string representations toward their identifier....
Definition: clutchlog.h:612
clutchlog::_strip_calls
size_t _strip_calls
Current number of call stack levels to remove from depth display.
Definition: clutchlog.h:495
clutchlog::scope_t::stage
level stage
Current log level.
Definition: clutchlog.h:670
clutchlog::scope_t::there
bool there
Location is compatible.
Definition: clutchlog.h:676
CLUTCHLOG_STRIP_CALLS
#define CLUTCHLOG_STRIP_CALLS
Compile-time number of call stack levels to remove from depth display by default.
Definition: clutchlog.h:253
clutchlog::fmt::operator<<
friend std::ostream & operator<<(std::ostream &os, const fmt &fmt)
Output stream overload.
Definition: clutchlog.h:396
clutchlog::_word_level
std::map< std::string, level > _word_level
Dictionary of level string to their identifier.
Definition: clutchlog.h:499
clutchlog::_stage
level _stage
Current log level.
Definition: clutchlog.h:525
clutchlog::style
fmt style(level stage) const
Get the configured fmt instance of the given log level.
Definition: clutchlog.h:656
clutchlog::fmt::typo
typo
Typographic style codes.
Definition: clutchlog.h:343
clutchlog::location
void location(const std::string &in_file, const std::string &in_function=".*", const std::string &in_line=".*")
Set the regular expressions filtering the location.
Definition: clutchlog.h:636
CLUTCHLOG_HAVE_UNIX_SYSINFO
#define CLUTCHLOG_HAVE_UNIX_SYSINFO
True if POSIX headers necessary for stack depth management are available.
Definition: clutchlog.h:33
clutchlog
The single class which holds everything.
Definition: clutchlog.h:177
CLUTCHDUMP_DEFAULT_FORMAT
#define CLUTCHDUMP_DEFAULT_FORMAT
Compile-time default format of the comment line in file dump.
Definition: clutchlog.h:221