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