PURIFY
Next-generation radio interferometric imaging
logging.cc
Go to the documentation of this file.
1 #include "purify/logging.h"
2 #include <ctime>
3 #include <iostream>
4 #include <ostream>
5 #include <unistd.h>
6 
7 using namespace std;
8 
9 namespace purify::logging {
10 
11 thread_local Log::LogMap Log::existingLogs;
12 thread_local Log::LevelMap Log::defaultLevels;
13 bool Log::showTimestamp = false;
14 bool Log::showLogLevel = true;
15 bool Log::showLoggerName = true;
16 bool Log::useShellColors = true;
17 const int Log::end_color;
18 
19 Log::Log(const string& name) : _name(name), _level(info) {}
20 
21 Log::Log(const string& name, int level) : _name(name), _level(level) {}
22 
24 void _updateLevels(const Log::LevelMap& defaultLevels, Log::LogMap& existingLogs) {
26  for (Log::LevelMap::const_iterator lev = defaultLevels.begin(); lev != defaultLevels.end();
27  ++lev) {
28  for (Log::LogMap::iterator log = existingLogs.begin(); log != existingLogs.end(); ++log) {
29  if (log->first.find(lev->first) == 0) {
30  log->second.setLevel(lev->second);
31  }
32  }
33  }
34 }
35 
36 void Log::setLevel(const string& name, int level) {
37  defaultLevels[name] = level;
38  // cout << name << " -> " << level << '\n';
39  _updateLevels(defaultLevels, existingLogs);
40 }
41 
42 void Log::setLevels(const LevelMap& logLevels) {
43  for (LevelMap::const_iterator lev = logLevels.begin(); lev != logLevels.end(); ++lev) {
44  defaultLevels[lev->first] = lev->second;
45  }
46  _updateLevels(defaultLevels, existingLogs);
47 }
48 
49 Log& Log::getLog(const string& name) {
50  auto theLog = existingLogs.find(name);
51  if (theLog == existingLogs.end()) {
52  int level = info;
53  // Try running through all parent classes to find an existing level
54  string tmpname = name;
55  bool triedAllParents = false;
56  while (!triedAllParents) {
57  // Is there a default level?
58  if (defaultLevels.find(tmpname) != defaultLevels.end()) {
59  level = defaultLevels.find(tmpname)->second;
60  break;
61  }
62  // Is there already such a logger? (NB. tmpname != name in later iterations)
63  if (existingLogs.find(tmpname) != existingLogs.end()) {
64  level = existingLogs.find(tmpname)->second.getLevel();
65  break;
66  }
67  // Crop the string back to the next parent level
68  size_t lastDot = tmpname.find_last_of(".");
69  if (lastDot != string::npos) {
70  tmpname = tmpname.substr(0, lastDot);
71  } else {
72  triedAllParents = true;
73  }
74  }
75  // for (LevelMap::const_iterator l = defaultLevels.begin(); l != defaultLevels.end(); ++l) {
76  //
77  // }
78 
79  // emplace returns pair<iterator,bool>
80  auto result = existingLogs.emplace(name, Log(name, level));
81  theLog = result.first;
82  }
83  return theLog->second;
84 }
85 
86 string Log::getLevelName(int level) {
87  switch (level) {
88  case trace:
89  return "trace";
90  case debug:
91  return "debug";
92  case info:
93  return "info";
94  case warn:
95  return "warn";
96  case error:
97  return "error";
98  case critical:
99  return "critical";
100  default:
101  return "";
102  }
103 }
104 
105 string Log::getColorCode(int level) {
106  // Skip codes if
107  if (!Log::useShellColors) return "";
108  const static bool IS_TTY = isatty(1);
109  if (!IS_TTY) return "";
110 
111  static const ColorCodes TTY_CODES = {
112  {trace, "\033[0;36m"}, {debug, "\033[0;34m"}, {info, "\033[0;32m"}, {warn, "\033[0;33m"},
113  {error, "\033[0;31m"}, {critical, "\033[0;31m"}, {end_color, "\033[0m"} // end-color code
114  };
115  try {
116  return TTY_CODES.at(level);
117  } catch (...) {
118  return "";
119  }
120 }
121 
122 Log::Level Log::getLevelFromName(const string& level) {
123  if (level == "trace") return trace;
124  if (level == "debug") return debug;
125  if (level == "info") return info;
126  if (level == "warn") return warn;
127  if (level == "error") return error;
128  if (level == "critical") return critical;
129  throw std::runtime_error("Couldn't create a log level from string '" + level + "'");
130 }
131 
132 string Log::formatMessage(int level, const string& message) {
133  string out;
134  out += getColorCode(level);
135 
136  if (Log::showLoggerName) {
137  out += getName();
138  // out += ": ";
139  }
140 
141  if (Log::showLogLevel) {
142  out += Log::getLevelName(level);
143  out += " ";
144  }
145 
146  if (Log::showTimestamp) {
147  time_t rawtime;
148  time(&rawtime);
149  char* timestr = ctime(&rawtime);
150  timestr[24] = ' ';
151  out += timestr;
152  out += " ";
153  }
154 
155  out += getColorCode(end_color);
156  out += " ";
157  out += message;
158 
159  return out;
160 }
161 
162 void Log::log(int level, const string& message) {
163  if (isActive(level)) {
164  if (level > warning)
165  cerr << formatMessage(level, message) << '\n';
166  else
167  cout << formatMessage(level, message) << '\n';
168  }
169 }
170 
171 ostream& operator<<(Log& log, int level) {
172  if (log.isActive(level)) {
173  if (level > Log::warning) {
174  cerr << log.formatMessage(level, "");
175  return cerr;
176  } else {
177  cout << log.formatMessage(level, "");
178  return cout;
179  }
180  } else {
181  static ostream devNull(nullptr);
182  return devNull;
183  }
184 }
185 } // namespace purify::logging
Logging system for controlled & formatted writing to stdout.
Definition: logging.h:16
std::map< std::string, Log > LogMap
Typedef for a collection of named logs.
Definition: logging.h:32
Level
Log priority levels.
Definition: logging.h:19
bool isActive(int level) const
Will this log level produce output on this logger at the moment?
Definition: logging.h:109
int getLevel() const
Get the priority level of this logger.
Definition: logging.h:85
std::map< std::string, int > LevelMap
Typedef for a collection of named log levels.
Definition: logging.h:35
void _updateLevels(const Log::LevelMap &defaultLevels, Log::LogMap &existingLogs)
Definition: logging.cc:24
Log & getLog()
Access method to default Log object.
Definition: logging.h:134
ostream & operator<<(Log &log, int level)
Streaming output to a logger must have a Log::Level/int as its first argument.
Definition: logging.cc:171