HAL
log.cpp
Go to the documentation of this file.
2 
3 #include <iostream>
4 #include <spdlog/common.h>
5 #include <spdlog/fmt/fmt.h>
6 #include <spdlog/sinks/ansicolor_sink.h>
7 #include <spdlog/sinks/basic_file_sink.h>
8 #include <spdlog/sinks/null_sink.h>
9 #include <spdlog/sinks/stdout_sinks.h>
10 
11 namespace hal
12 {
13  std::map<std::string, std::shared_ptr<LogManager::log_sink>> LogManager::m_file_sinks;
14 
15  LogManager* LogManager::m_instance = nullptr;
16 
17  LogManager::LogManager(const std::filesystem::path& file_name)
18  {
19  m_file_path = (file_name.empty()) ? utils::get_default_log_directory() / "hal.log" : file_name;
20  std::filesystem::create_directories(m_file_path.parent_path());
21 
22  m_level = {
23  {"trace", spdlog::level::level_enum::trace},
24  {"debug", spdlog::level::level_enum::debug},
25  {"info", spdlog::level::level_enum::info},
26  {"warn", spdlog::level::level_enum::warn},
27  {"err", spdlog::level::level_enum::err},
28  {"critical", spdlog::level::level_enum::critical},
29  {"off", spdlog::level::level_enum::off},
30  };
31 
32  auto gui_sink = LogManager::create_gui_sink();
33 
34  spdlog::sinks_init_list stdout_init_list = {std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>(), gui_sink->spdlog_sink};
35 
36  m_logger = {
37  // initialize null channel
38  {"null", spdlog::create<spdlog::sinks::null_sink_mt>("null")},
39  // initialize multi-threaded, colored stdout channel logger
40  {"stdout", std::make_shared<spdlog::logger>("stdout", stdout_init_list)},
41  };
42 
43  spdlog::details::registry::instance().initialize_logger(m_logger.at("stdout"));
44 
45  spdlog::set_error_handler([](const std::string& msg) { throw std::invalid_argument("[!] internal log error: " + msg); });
46 
47  //set_format_pattern("[%c %z] [%n] [%l] %v");
48  set_format_pattern("[%n] [%l] %v");
49 
50  m_default_sinks = {gui_sink, LogManager::create_stdout_sink(), LogManager::create_file_sink(m_file_path)};
51  }
52 
53  LogManager::~LogManager()
54  {
55  std::cerr << "~LogManager" << std::endl;
56  for (const auto& it : m_logger)
57  it.second->flush();
58  spdlog::drop_all();
59  }
60 
61  LogManager* LogManager::get_instance(const std::filesystem::path& file_name)
62  {
63  if (!m_instance)
64  {
65  m_instance = new LogManager(file_name);
66  }
67  return m_instance;
68  }
69 
70  void LogManager::set_format_pattern(const std::string& format)
71  {
72  spdlog::set_pattern(format);
73  }
74 
75  std::shared_ptr<spdlog::logger> LogManager::get_channel(const std::string& channel)
76  {
77  auto it = m_logger.find(channel);
78  if (it == m_logger.end())
79  {
80  if (channel != "stdout") // avoid infinite recursion
81  {
82  log_warning("stdout", "log channel '{}' was not registered so far, creating default channel.", channel);
83  }
84  return add_channel(channel, m_default_sinks, "info");
85  }
86 
87  return it->second;
88  }
89 
90  std::set<std::string> LogManager::get_channels() const
91  {
92  std::set<std::string> channels;
93  for (const auto& it : m_logger)
94  {
95  channels.insert(it.first);
96  }
97  return channels;
98  }
99 
100  std::shared_ptr<spdlog::logger> LogManager::add_channel(const std::string& channel_name, const std::vector<std::shared_ptr<log_sink>>& sinks, const std::string& level)
101  {
102  if (auto it = m_logger.find(channel_name); it != m_logger.end())
103  {
104  return it->second;
105  }
106 
107  std::vector<std::shared_ptr<spdlog::sinks::sink>> vec;
108  for (const auto& sink : sinks)
109  {
110  vec.push_back(sink->spdlog_sink);
111  }
112 
113  auto channel = std::make_shared<spdlog::logger>(channel_name, vec.begin(), vec.end());
114  m_logger[channel_name] = channel;
115  m_logger[channel_name]->flush_on(spdlog::level::info);
116 
117  spdlog::details::registry::instance().initialize_logger(m_logger.at(channel_name));
118 
119  m_logger_sinks[channel_name] = sinks;
120 
121  if (m_enforce_level.empty())
122  {
123  this->set_level_of_channel(channel_name, level);
124  }
125  else
126  {
127  this->set_level_of_channel(channel_name, m_enforce_level);
128  }
129 
130  return channel;
131  }
132 
133  void LogManager::remove_channel(const std::string& channel_name)
134  {
135  if (m_logger.find(channel_name) == m_logger.end())
136  {
137  return;
138  }
139  m_logger[channel_name]->flush();
140  spdlog::drop(channel_name);
141  m_logger.erase(channel_name);
142  m_logger_sinks.erase(channel_name);
143  }
144 
145  std::string LogManager::get_level_of_channel(const std::string& channel_name) const
146  {
147  auto it_channel = m_logger.find(channel_name);
148  if (it_channel == m_logger.end())
149  {
150  return std::string("");
151  }
152 
153  for (const auto& it_level : m_level)
154  {
155  if (it_level.second == it_channel->second->level())
156  {
157  return it_level.first;
158  }
159  }
160 
161  return std::string("");
162  }
163 
164  void LogManager::set_level_of_channel(const std::string& channel_name, const std::string& level)
165  {
166  auto it_channel = m_logger.find(channel_name);
167  auto it_level = m_level.find(level);
168  if (it_channel == m_logger.end() || it_level == m_level.end())
169  {
170  return;
171  }
172 
173  auto& channel = it_channel->second;
174  channel->set_level(it_level->second);
175  m_logger[channel_name] = channel;
176  }
177 
178  void LogManager::activate_channel(const std::string& channel_name)
179  {
180  this->set_level_of_channel(channel_name, "info");
181  }
182 
184  {
185  for (const auto& channel : m_logger)
186  {
187  set_level_of_channel(channel.first, "info");
188  }
189  }
190 
191  void LogManager::deactivate_channel(const std::string& channel_name)
192  {
193  this->set_level_of_channel(channel_name, "off");
194  }
195 
197  {
198  for (const auto& channel : m_logger)
199  {
200  set_level_of_channel(channel.first, "off");
201  }
202  }
203 
204  std::vector<std::shared_ptr<hal::LogManager::log_sink>> LogManager::get_default_sinks()
205  {
206  return m_default_sinks;
207  }
208 
209  void LogManager::remove_sink_from_default(const std::string& sink_type)
210  {
211  m_default_sinks.erase(
212  std::remove_if(m_default_sinks.begin(), m_default_sinks.end(), [sink_type](const std::shared_ptr<hal::LogManager::log_sink> sink) { return sink->sink_type == sink_type; }),
213  m_default_sinks.end());
214  }
215 
216  std::shared_ptr<LogManager::log_sink> LogManager::create_stdout_sink(const bool colored)
217  {
218  auto sink = std::make_shared<log_sink>();
219  sink->is_file_sink = false;
220  sink->sink_type = "stdout";
221 
222  if (!colored)
223  {
224  sink->spdlog_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
225  }
226  else
227  {
228 #ifdef _WIN32
229  // todo: to be implemented for windows
230  sink->spdlog_sink = nullptr;
231  log_error("stdout", "create_stdout_sink() has to be implemented for Windows.");
232 #else
233  auto stdout_sink = std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
234  stdout_sink->set_color(spdlog::level::trace, stdout_sink->green);
235  stdout_sink->set_color(spdlog::level::debug, stdout_sink->blue);
236  stdout_sink->set_color(spdlog::level::info, stdout_sink->reset);
237  stdout_sink->set_color(spdlog::level::warn, stdout_sink->yellow);
238  stdout_sink->set_color(spdlog::level::err, stdout_sink->red);
239  stdout_sink->set_color(spdlog::level::critical, stdout_sink->red_bold);
240  sink->spdlog_sink = stdout_sink;
241 #endif
242  }
243 
244  return sink;
245  }
246 
247  std::shared_ptr<LogManager::log_sink> LogManager::create_file_sink(const std::filesystem::path& file_name, const bool truncate)
248  {
249  std::filesystem::path path = file_name;
250  if (file_name.empty())
251  {
252  path = get_instance()->m_file_path;
253  }
254 
255  auto it = m_file_sinks.find(path.string());
256  if (it != m_file_sinks.end())
257  {
258  return it->second;
259  }
260 
261  auto sink = std::make_shared<log_sink>();
262  sink->is_file_sink = true;
263  sink->sink_type = "file";
264  sink->truncate = truncate;
265 
266  sink->path = path;
267  sink->spdlog_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.string(), truncate);
268 
269  m_file_sinks[path.string()] = sink;
270 
271  return sink;
272  }
273 
274  std::shared_ptr<LogManager::log_sink> LogManager::create_gui_sink()
275  {
276  auto sink = std::make_shared<log_sink>();
277  sink->spdlog_sink = std::make_shared<log_gui_sink>();
278  sink->is_file_sink = false;
279  sink->sink_type = "gui";
280  return sink;
281  }
282 
283  std::set<std::string> LogManager::get_available_log_levels() const
284  {
285  std::set<std::string> levels;
286  for (const auto& it : m_level)
287  levels.insert(it.first);
288  return levels;
289  }
290 
291  void LogManager::set_file_name(const std::filesystem::path& file_path)
292  {
293  log_info("core", "setting log file to '{}'.", file_path.string());
294 
295  // search through all existing channels
296  // mark every channel for recreation which contains a file sink that pointed to the old path
297  std::vector<std::string> channels_to_recreate;
298  for (const auto& it : m_logger_sinks)
299  {
300  auto name = it.first;
301  auto sinks = it.second;
302 
303  for (const auto& sink : sinks)
304  {
305  if (sink->is_file_sink && (sink->path == m_file_path))
306  {
307  channels_to_recreate.push_back(name);
308  break;
309  }
310  }
311  }
312 
313  // recreate all marked channels
314  // keep all sinks and rebuild file sinks with the new path
315  for (const auto& name : channels_to_recreate)
316  {
317  auto level = get_level_of_channel(name);
318 
319  std::vector<std::shared_ptr<log_sink>> new_sinks;
320  for (const auto& sink : m_logger_sinks[name])
321  {
322  if (sink->is_file_sink && (sink->path == m_file_path))
323  new_sinks.push_back(create_file_sink(file_path, sink->truncate));
324  else
325  new_sinks.push_back(sink);
326  }
328  add_channel(name, new_sinks, level);
329  }
330 
331  // update default sinks
332  for (u32 sink_idx = 0; sink_idx < m_default_sinks.size(); sink_idx++)
333  {
334  if (m_default_sinks.at(sink_idx)->is_file_sink && m_default_sinks.at(sink_idx)->path == m_file_path)
335  {
336  m_default_sinks.at(sink_idx) = create_file_sink(file_path, m_default_sinks.at(sink_idx)->truncate);
337  }
338  }
339 
340  // close all unused file sinks
341  for (auto it = m_file_sinks.cbegin(); it != m_file_sinks.cend(); /* no increment */)
342  {
343  if (it->second.unique())
344  m_file_sinks.erase(it++);
345  else
346  ++it;
347  }
348 
349  // adjust path
350  m_file_path = file_path;
351  }
352 
354  {
355  if (m_descriptions.get_options().empty())
356  {
357  m_descriptions.add("--log.level", "set default log level", {ProgramOptions::A_REQUIRED_PARAMETER});
358  m_descriptions.add("--log.enabled", "default setting for enable logging", {ProgramOptions::A_REQUIRED_PARAMETER});
359 
360  for (const auto& channel : this->get_channels())
361  {
362  auto start = "--log." + channel;
363  auto level_string = start + ".level";
364  auto enabled_string = start + ".enabled";
365 
366  m_descriptions.add(level_string, "set log level for channel: " + channel, {ProgramOptions::A_REQUIRED_PARAMETER});
367  m_descriptions.add(enabled_string, "enable logging level for channel: " + channel, {ProgramOptions::A_REQUIRED_PARAMETER});
368  }
369  }
370 
371  return m_descriptions;
372  }
373 
375  {
376  bool default_enabled = true;
377  if (args.is_option_set("--log.enabled"))
378  {
379  auto arg = args.get_parameter("--log.enabled");
380  default_enabled = (arg == "true" || arg == "1");
381  for (const auto& channel : this->get_channels())
382  {
383  if (default_enabled)
384  this->activate_channel(channel);
385  else
386  this->deactivate_channel(channel);
387  }
388  }
389  if (args.is_option_set("--log.level"))
390  {
391  auto level = args.get_parameter("--log.level");
392  if (m_level.find(level) != m_level.end())
393  {
394  m_enforce_level = level;
395  for (const auto& channel : this->get_channels())
396  {
397  if (default_enabled)
398  this->set_level_of_channel(channel, level);
399  }
400  }
401  else
402  log_warning("core", "default log level {} provided is not valid.", level);
403  }
404  for (const auto& channel : this->get_channels())
405  {
406  bool enabled = true;
407  if (args.is_option_set("--log." + channel + ".enabled"))
408  {
409  auto arg = args.get_parameter("--log." + channel + ".enabled");
410  enabled = (arg == "true" || arg == "1");
411  if (enabled)
412  this->activate_channel(channel);
413  else
414  this->deactivate_channel(channel);
415  }
416 
417  if (args.is_option_set("--log." + channel + ".level"))
418  {
419  auto level = args.get_parameter("--log." + channel + ".level");
420  if (m_level.find(level) != m_level.end())
421  {
422  if (enabled)
423  this->set_level_of_channel(channel, level);
424  }
425  else
426  log_warning("core", "log level {} provided for {} is not valid.", level, channel);
427  }
428  }
429  }
430 
431  CallbackHook<void(const spdlog::level::level_enum&, const std::string&, const std::string&)>& LogManager::get_gui_callback()
432  {
433  return m_gui_callback;
434  }
435 
436  /*
437  * log gui sink implementation
438  */
439  void log_gui_sink::sink_it_(const spdlog::details::log_msg& msg)
440  {
441  spdlog::memory_buf_t formatted;
442  formatter_->format(msg, formatted);
443  LogManager::get_instance()->get_gui_callback()(msg.level, std::string(msg.logger_name.data()), std::string(formatted.data(), formatted.size()));
444  }
445 
447  {
448  }
449 } // namespace hal
void set_level_of_channel(const std::string &channel_name, const std::string &level)
Definition: log.cpp:164
std::shared_ptr< spdlog::logger > add_channel(const std::string &channel_name, const std::vector< std::shared_ptr< log_sink >> &sinks, const std::string &level="info")
Definition: log.cpp:100
void activate_channel(const std::string &channel_name)
Definition: log.cpp:178
void deactivate_all_channels()
Definition: log.cpp:196
void deactivate_channel(const std::string &channel_name)
Definition: log.cpp:191
void remove_channel(const std::string &channel_name)
Definition: log.cpp:133
ProgramOptions & get_option_descriptions()
Definition: log.cpp:353
std::string get_level_of_channel(const std::string &channel_name) const
Definition: log.cpp:145
static std::shared_ptr< log_sink > create_gui_sink()
Definition: log.cpp:274
void activate_all_channels()
Definition: log.cpp:183
std::vector< std::shared_ptr< hal::LogManager::log_sink > > get_default_sinks()
Definition: log.cpp:204
std::set< std::string > get_channels() const
Definition: log.cpp:90
void set_format_pattern(const std::string &format)
Definition: log.cpp:70
std::shared_ptr< spdlog::logger > get_channel(const std::string &channel_name="stdout")
Definition: log.cpp:75
static std::shared_ptr< log_sink > create_file_sink(const std::filesystem::path &file_name="", const bool truncate=false)
Definition: log.cpp:247
void remove_sink_from_default(const std::string &sink_type)
Definition: log.cpp:209
void handle_options(ProgramArguments &args)
Definition: log.cpp:374
CallbackHook< void(const spdlog::level::level_enum &, const std::string &, const std::string &)> & get_gui_callback()
Definition: log.cpp:431
static std::shared_ptr< log_sink > create_stdout_sink(const bool colored=true)
Definition: log.cpp:216
static LogManager * get_instance(const std::filesystem::path &file_name="")
Definition: log.cpp:61
std::set< std::string > get_available_log_levels() const
Definition: log.cpp:283
void set_file_name(const std::filesystem::path &file_name)
Definition: log.cpp:291
std::vector< std::tuple< std::set< std::string >, std::string > > get_options() const
bool add(const std::string &flag, const std::string &description, const std::initializer_list< std::string > &parameters={})
static const std::string A_REQUIRED_PARAMETER
constant to specify that a parameter is required and does not have a default value.
void sink_it_(const spdlog::details::log_msg &msg) override
Definition: log.cpp:439
void flush_() override
Definition: log.cpp:446
#define log_error(channel,...)
Definition: log.h:78
#define log_info(channel,...)
Definition: log.h:70
#define log_warning(channel,...)
Definition: log.h:76
std::filesystem::path get_default_log_directory(std::filesystem::path source_file)
Definition: utils.cpp:178
quint32 u32
std::string name