GnuCash c935c2f+
Loading...
Searching...
No Matches
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
64static FILE *fout = nullptr;
65static gchar* function_buffer = nullptr;
66static gint qof_log_num_spaces = 0;
67static GLogFunc previous_handler = nullptr;
68static gchar* qof_logger_format = nullptr;
69static QofLogModule log_module = "qof";
70
71using StrVec = std::vector<std::string>;
72
73struct ModuleEntry;
74using ModuleEntryPtr = std::unique_ptr<ModuleEntry>;
75using MEVec = std::vector<ModuleEntryPtr>;
76
77static constexpr int parts = 4; //Log domain parts vector preallocation size
78static constexpr QofLogLevel default_level = QOF_LOG_WARNING;
79static 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
93static ModuleEntryPtr _modules = nullptr;
94
95static ModuleEntry*
96get_modules()
97{
98 if (!_modules)
99 _modules = std::make_unique<ModuleEntry>("", default_level);
100 return _modules.get();
101}
102
103static StrVec
104split_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
129void
131{
132 qof_log_num_spaces += QOF_LOG_INDENT_WIDTH;
133}
134
135void
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
144void
145qof_log_set_file(FILE *outfile)
146{
147 if (!outfile)
148 {
149 fout = stderr;
150 return;
151 }
152 fout = outfile;
153}
154
155void
157{
158 qof_log_init_filename(nullptr);
159}
160
161static void
162log4glib_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
206void
207qof_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
263void
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
296void
297qof_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
329gboolean
330qof_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
365const char *
366qof_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
401void
402qof_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
420void
421qof_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
500const gchar*
501qof_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
531QofLogLevel
532qof_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}
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:115
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
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 ...
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
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_init(void)
Initialize the error logging subsystem.
Definition qoflog.cpp:156
void qof_log_dedent(void)
De-dent one level, capped at 0; see LEAVE macro.
Definition qoflog.cpp:136
void qof_log_shutdown(void)
Be nice, close the logfile if possible.
Definition qoflog.cpp:264
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
void qof_log_parse_log_config(const char *filename)
Parse a log-configuration file.
Definition qoflog.cpp:421
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
void qof_log_set_file(FILE *outfile)
Specify an alternate log output, to pipe or file.
Definition qoflog.cpp:145
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