GnuCash  5.6-150-g038405b370+
qoflog.cpp
1 /* **************************************************************************
2  * qoflog.c
3  *
4  * Mon Nov 21 14:41:59 2005
5  * Author: Rob Clark (rclark@cs.hmc.edu)
6  * Copyright (C) 1997-2003 Linas Vepstas <linas@linas.org>
7  * Copyright 2005 Neil Williams <linux@codehelp.co.uk>
8  * Copyright 2007 Joshua Sled <jsled@asynchronous.org>
9  *************************************************************************** */
10 
11 /*
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25  * 02110-1301, USA
26  */
27 #include <glib.h>
28 #include <glib/gstdio.h>
29 
30 #include <config.h>
31 
32 #include <platform.h>
33 #if PLATFORM(WINDOWS)
34 #include <windows.h>
35 #endif
36 
37 #ifdef HAVE_UNISTD_H
38 # include <unistd.h>
39 #else
40 # ifdef __GNUC__
41 # warning "<unistd.h> required."
42 # endif
43 #endif
44 #include <stdarg.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <stdio.h>
48 
49 #undef G_LOG_DOMAIN
50 #define G_LOG_DOMAIN "qof.log"
51 #include "qof.h"
52 #include "qoflog.h"
53 #include <string>
54 #include <string_view>
55 #include <vector>
56 #include <memory>
57 #include <algorithm>
58 
59 #define QOF_LOG_MAX_CHARS 50
60 #define QOF_LOG_MAX_CHARS_WITH_ALLOWANCE 100
61 #define QOF_LOG_INDENT_WIDTH 4
62 #define NUM_CLOCKS 10
63 
64 static FILE *fout = nullptr;
65 static gchar* function_buffer = nullptr;
66 static gint qof_log_num_spaces = 0;
67 static GLogFunc previous_handler = nullptr;
68 static gchar* qof_logger_format = nullptr;
69 static QofLogModule log_module = "qof";
70 
71 using StrVec = std::vector<std::string>;
72 
73 struct ModuleEntry;
74 using ModuleEntryPtr = std::unique_ptr<ModuleEntry>;
75 using MEVec = std::vector<ModuleEntryPtr>;
76 
77 static constexpr int parts = 4; //Log domain parts vector preallocation size
78 static constexpr QofLogLevel default_level = QOF_LOG_WARNING;
79 static QofLogLevel current_max{default_level};
80 
82 {
83  ModuleEntry(const std::string& name, QofLogLevel level) :
84  m_name{name}, m_level{level} {
85  m_children.reserve(parts);
86  }
87  ~ModuleEntry() = default;
88  std::string m_name;
89  QofLogLevel m_level;
90  MEVec m_children;
91 };
92 
93 static ModuleEntryPtr _modules = nullptr;
94 
95 static ModuleEntry*
96 get_modules()
97 {
98  if (!_modules)
99  _modules = std::make_unique<ModuleEntry>("", default_level);
100  return _modules.get();
101 }
102 
103 static StrVec
104 split_domain (const std::string_view domain)
105 {
106  StrVec domain_parts;
107  domain_parts.reserve(parts);
108  int start = 0;
109  auto pos = domain.find(".");
110  if (pos == std::string_view::npos)
111  {
112  domain_parts.emplace_back(domain);
113  }
114  else
115  {
116  while (pos != std::string_view::npos)
117  {
118  auto part_name{domain.substr(start, pos - start)};
119  domain_parts.emplace_back(part_name);
120  start = pos + 1;
121  pos = domain.find(".", start);
122  }
123  auto part_name{domain.substr(start, pos)};
124  domain_parts.emplace_back(part_name);
125  }
126  return domain_parts;
127 }
128 
129 void
131 {
132  qof_log_num_spaces += QOF_LOG_INDENT_WIDTH;
133 }
134 
135 void
137 {
138  qof_log_num_spaces
139  = (qof_log_num_spaces < QOF_LOG_INDENT_WIDTH)
140  ? 0
141  : qof_log_num_spaces - QOF_LOG_INDENT_WIDTH;
142 }
143 
144 void
145 qof_log_set_file(FILE *outfile)
146 {
147  if (!outfile)
148  {
149  fout = stderr;
150  return;
151  }
152  fout = outfile;
153 }
154 
155 void
157 {
158  qof_log_init_filename(nullptr);
159 }
160 
161 static void
162 log4glib_handler(const gchar *log_domain,
163  GLogLevelFlags log_level,
164  const gchar *message,
165  gpointer user_data)
166 {
167  QofLogLevel level = static_cast<QofLogLevel>(log_level);
168  if (G_LIKELY(!qof_log_check(log_domain, level)))
169  return;
170 
171  {
172  char timestamp_buf[10];
173  time64 now;
174  struct tm now_tm;
175  const char *format_24hour =
176 #ifdef G_OS_WIN32
177  "%H:%M:%S"
178 #else
179  "%T"
180 #endif
181  ;
182  const char *level_str = qof_log_level_to_string(level);
183  now = gnc_time (nullptr);
184  gnc_localtime_r (&now, &now_tm);
185  qof_strftime(timestamp_buf, 9, format_24hour, &now_tm);
186 
187  fprintf(fout, qof_logger_format,
188  timestamp_buf,
189  5, level_str,
190  (log_domain == nullptr ? "" : log_domain),
191  qof_log_num_spaces, "",
192  message,
193  (g_str_has_suffix(message, "\n") ? "" : "\n"));
194  fflush(fout);
195  }
196 
197  /* chain? ignore? Only chain if it's going to be quiet...
198  else
199  {
200  // chain
201  previous_handler(log_domain, log_level, message, nullptr);
202  }
203  */
204 }
205 
206 void
207 qof_log_init_filename(const gchar* log_filename)
208 {
209  gboolean warn_about_missing_permission = FALSE;
210  auto modules = get_modules();
211 
212  if (!qof_logger_format)
213  qof_logger_format = g_strdup ("* %s %*s <%s> %*s%s%s"); //default format
214 
215  if (log_filename)
216  {
217  int fd;
218  gchar *fname;
219 
220  if (fout != nullptr && fout != stderr && fout != stdout)
221  fclose(fout);
222 
223  fname = g_strconcat(log_filename, ".XXXXXX.log", nullptr);
224 
225  if ((fd = g_mkstemp(fname)) != -1)
226  {
227 #if PLATFORM(WINDOWS)
228  /* MSVC compiler: Somehow the OS thinks file descriptor from above
229  * still isn't open. So we open normally with the file name and that's it. */
230  fout = g_fopen(fname, "wb");
231 #else
232  /* We must not overwrite /dev/null */
233  g_assert(g_strcmp0(log_filename, "/dev/null") != 0);
234 
235  /* Windows prevents renaming of open files, so the next command silently fails there
236  * No problem, the filename on Windows will simply have the random characters */
237  g_rename(fname, log_filename);
238  fout = fdopen(fd, "w");
239 #endif
240  if (!fout)
241  warn_about_missing_permission = TRUE;
242  }
243  else
244  {
245  warn_about_missing_permission = TRUE;
246  fout = stderr;
247  }
248  g_free(fname);
249  }
250 
251  if (!fout)
252  fout = stderr;
253 
254  if (previous_handler == nullptr)
255  previous_handler = g_log_set_default_handler(log4glib_handler, modules);
256 
257  if (warn_about_missing_permission)
258  {
259  g_critical("Cannot open log output file \"%s\", using stderr.", log_filename);
260  }
261 }
262 
263 void
265 {
266  if (fout && fout != stderr && fout != stdout)
267  {
268  fclose(fout);
269  fout = nullptr;
270  }
271 
272  if (qof_logger_format)
273  {
274  g_free (qof_logger_format);
275  qof_logger_format = nullptr;
276  }
277 
278  if (function_buffer)
279  {
280  g_free(function_buffer);
281  function_buffer = nullptr;
282  }
283 
284  if (_modules != nullptr)
285  {
286  _modules = nullptr;
287  }
288 
289  if (previous_handler != nullptr)
290  {
291  g_log_set_default_handler(previous_handler, nullptr);
292  previous_handler = nullptr;
293  }
294 }
295 
296 void
297 qof_log_set_level(QofLogModule log_module, QofLogLevel level)
298 {
299  if (!log_module || level == QOF_LOG_FATAL)
300  return;
301 
302  if (level > current_max)
303  current_max = level;
304 
305  auto module_parts = split_domain(log_module);
306  auto module = get_modules();
307  for (auto part : module_parts)
308  {
309  auto iter = std::find_if(module->m_children.begin(),
310  module->m_children.end(),
311  [part](auto& child){
312  return child && part == child->m_name;
313  });
314  if (iter == module->m_children.end())
315  {
316  auto child = std::make_unique<ModuleEntry>(part, default_level);
317  module->m_children.emplace_back(std::move(child));
318  module = module->m_children.back().get();
319  }
320  else
321  {
322  module = iter->get();
323  }
324  }
325  module->m_level = level;
326 }
327 
328 
329 gboolean
330 qof_log_check(QofLogModule domain, QofLogLevel level)
331 {
332 // Check the global levels
333  if (level > current_max)
334  return FALSE;
335  if (level <= default_level)
336  return TRUE;
337  auto module = get_modules();
338  // If the level <= the default then no need to look further.
339  if (level <= module->m_level)
340  return TRUE;
341 
342  if (!domain)
343  return FALSE;
344 
345  auto domain_vec = split_domain(domain);
346 
347  for (const auto& part : domain_vec)
348  {
349  auto iter = std::find_if(module->m_children.begin(),
350  module->m_children.end(),
351  [part](auto& child) {
352  return child && part == child->m_name; });
353 
354  if (iter == module->m_children.end())
355  return FALSE;
356 
357  if (level <= (*iter)->m_level)
358  return TRUE;
359 
360  module = iter->get();
361  }
362  return FALSE;
363 }
364 
365 const char *
366 qof_log_prettify (const char *name)
367 {
368  gchar *p, *buffer, *begin;
369  gint length;
370 
371  if (!name)
372  {
373  return "";
374  }
375 /* Clang's __func__ displays the whole signature, like a good C++
376  * compier should. Gcc displays only the name of the function. Strip
377  * the extras from Clang's output so that log messages are the same
378  * regardless of compiler.
379  */
380  buffer = g_strndup(name, QOF_LOG_MAX_CHARS_WITH_ALLOWANCE - 1);
381  length = strlen(buffer);
382  p = g_strstr_len (buffer, length, "(");
383  if (p) *p = '\0';
384  begin = g_strrstr (buffer, "*");
385  if (begin == nullptr)
386  begin = g_strrstr (buffer, " ");
387  else if (* (begin + 1) == ' ')
388  ++ begin;
389  if (begin != nullptr)
390  p = begin + 1;
391  else
392  p = buffer;
393 
394  if (function_buffer)
395  g_free(function_buffer);
396  function_buffer = g_strdup(p);
397  g_free(buffer);
398  return function_buffer;
399 }
400 
401 void
402 qof_log_init_filename_special(const char *log_to_filename)
403 {
404  if (g_ascii_strcasecmp("stderr", log_to_filename) == 0)
405  {
406  qof_log_init();
407  qof_log_set_file(stderr);
408  }
409  else if (g_ascii_strcasecmp("stdout", log_to_filename) == 0)
410  {
411  qof_log_init();
412  qof_log_set_file(stdout);
413  }
414  else
415  {
416  qof_log_init_filename(log_to_filename);
417  }
418 }
419 
420 void
421 qof_log_parse_log_config(const char *filename)
422 {
423  const gchar *levels_group = "levels", *output_group = "output";
424  GError *err = nullptr;
425  GKeyFile *conf = g_key_file_new();
426 
427  if (!g_key_file_load_from_file(conf, filename, G_KEY_FILE_NONE, &err))
428  {
429  g_warning("unable to parse [%s]: %s", filename, err->message);
430  g_error_free(err);
431  return;
432  }
433 
434  DEBUG("parsing log config from [%s]", filename);
435  if (g_key_file_has_group(conf, levels_group))
436  {
437  gsize num_levels;
438  unsigned int key_idx;
439  gchar **levels;
440  gint logger_max_name_length = 12;
441  gchar *str = nullptr;
442 
443  levels = g_key_file_get_keys(conf, levels_group, &num_levels, nullptr);
444 
445  for (key_idx = 0; key_idx < num_levels && levels[key_idx] != nullptr; key_idx++)
446  {
447  QofLogLevel level;
448  gchar *logger_name = nullptr, *level_str = nullptr;
449 
450  logger_name = g_strdup(levels[key_idx]);
451  logger_max_name_length = MAX (logger_max_name_length, (gint) strlen (logger_name));
452  level_str = g_key_file_get_string(conf, levels_group, logger_name, nullptr);
453  level = qof_log_level_from_string(level_str);
454 
455  DEBUG("setting log [%s] to level [%s=%d]", logger_name, level_str, level);
456  qof_log_set_level(logger_name, level);
457 
458  g_free(logger_name);
459  g_free(level_str);
460  }
461 
462  str = g_strdup_printf ("%d", logger_max_name_length);
463  if (qof_logger_format)
464  g_free (qof_logger_format);
465  qof_logger_format = g_strconcat ("* %s %*s <%-", str, ".", str, "s> %*s%s%s", nullptr);
466 
467  g_free (str);
468  g_strfreev(levels);
469  }
470 
471  if (g_key_file_has_group(conf, output_group))
472  {
473  gsize num_outputs;
474  unsigned int output_idx;
475  gchar **outputs;
476 
477  outputs = g_key_file_get_keys(conf, output_group, &num_outputs, nullptr);
478  for (output_idx = 0; output_idx < num_outputs && outputs[output_idx] != nullptr; output_idx++)
479  {
480  gchar *key = outputs[output_idx];
481  gchar *value;
482 
483  if (g_ascii_strcasecmp("to", key) != 0)
484  {
485  g_warning("unknown key [%s] in [outputs], skipping", key);
486  continue;
487  }
488 
489  value = g_key_file_get_string(conf, output_group, key, nullptr);
490  DEBUG("setting [output].to=[%s]", value);
492  g_free(value);
493  }
494  g_strfreev(outputs);
495  }
496 
497  g_key_file_free(conf);
498 }
499 
500 const gchar*
501 qof_log_level_to_string(QofLogLevel log_level)
502 {
503  const char *level_str;
504  switch (log_level)
505  {
506  case QOF_LOG_FATAL:
507  level_str = "FATAL";
508  break;
509  case QOF_LOG_ERROR:
510  level_str = "ERROR";
511  break;
512  case QOF_LOG_WARNING:
513  level_str = "WARN";
514  break;
515  case QOF_LOG_MESSAGE:
516  level_str = "MESSG";
517  break;
518  case QOF_LOG_INFO:
519  level_str = "INFO";
520  break;
521  case QOF_LOG_DEBUG:
522  level_str = "DEBUG";
523  break;
524  default:
525  level_str = "OTHER";
526  break;
527  }
528  return level_str;
529 }
530 
531 QofLogLevel
532 qof_log_level_from_string(const gchar *str)
533 {
534  if (g_ascii_strncasecmp("error", str, 5) == 0) return QOF_LOG_FATAL;
535  if (g_ascii_strncasecmp("crit", str, 4) == 0) return QOF_LOG_ERROR;
536  if (g_ascii_strncasecmp("warn", str, 4) == 0) return QOF_LOG_WARNING;
537  if (g_ascii_strncasecmp("mess", str, 4) == 0) return QOF_LOG_MESSAGE;
538  if (g_ascii_strncasecmp("info", str, 4) == 0) return QOF_LOG_INFO;
539  if (g_ascii_strncasecmp("debug", str, 5) == 0) return QOF_LOG_DEBUG;
540  return QOF_LOG_DEBUG;
541 }
gsize qof_strftime(gchar *buf, gsize max, const gchar *format, const struct tm *tm)
qof_strftime calls qof_format_time to print a given time and afterwards tries to put the result into ...
Definition: gnc-date.cpp:1062
void qof_log_set_level(QofLogModule log_module, QofLogLevel level)
Set the logging level of the given log_module.
Definition: qoflog.cpp:297
void qof_log_dedent(void)
De-dent one level, capped at 0; see LEAVE macro.
Definition: qoflog.cpp:136
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
void qof_log_init(void)
Initialize the error logging subsystem.
Definition: qoflog.cpp:156
void qof_log_shutdown(void)
Be nice, close the logfile if possible.
Definition: qoflog.cpp:264
struct tm * gnc_localtime_r(const time64 *secs, struct tm *time)
fill out a time struct from a 64-bit time value adjusted for the current time zone.
Definition: gnc-date.cpp:113
void qof_log_init_filename_special(const char *log_to_filename)
If log_to_filename is "stderr" or "stdout" (exactly, case-insensitive), then those special files are ...
Definition: qoflog.cpp:402
gboolean qof_log_check(QofLogModule domain, QofLogLevel level)
Check to see if the given log_module is configured to log at the given log_level. ...
Definition: qoflog.cpp:330
const gchar * qof_log_prettify(const gchar *name)
Cleans up subroutine names.
void qof_log_init_filename(const gchar *log_filename)
Specify a filename for log output.
Definition: qoflog.cpp:207
void qof_log_indent(void)
Indents one level; see ENTER macro.
Definition: qoflog.cpp:130
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:260
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
void qof_log_parse_log_config(const char *filename)
Parse a log-configuration file.
Definition: qoflog.cpp:421
void qof_log_set_file(FILE *outfile)
Specify an alternate log output, to pipe or file.
Definition: qoflog.cpp:145