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
85 struct ModuleEntry
86 {
ModuleEntryModuleEntry87 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*
get_modules()100 get_modules()
101 {
102 if (!_modules)
103 _modules = std::make_unique<ModuleEntry>("", default_level);
104 return _modules.get();
105 }
106
107 static StrVec
split_domain(const std::string_view domain)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
qof_log_indent(void)134 qof_log_indent(void)
135 {
136 qof_log_num_spaces += QOF_LOG_INDENT_WIDTH;
137 }
138
139 void
qof_log_dedent(void)140 qof_log_dedent(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
qof_log_set_file(FILE * outfile)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
qof_log_init(void)160 qof_log_init(void)
161 {
162 qof_log_init_filename(NULL);
163 }
164
165 static void
log4glib_handler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)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
qof_log_init_filename(const gchar * log_filename)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
qof_log_shutdown(void)268 qof_log_shutdown (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
qof_log_set_level(QofLogModule log_module,QofLogLevel level)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
qof_log_check(QofLogModule domain,QofLogLevel level)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 *
qof_log_prettify(const char * name)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
qof_log_init_filename_special(const char * log_to_filename)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
qof_log_parse_log_config(const char * filename)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);
489 qof_log_init_filename_special(value);
490 g_free(value);
491 }
492 g_strfreev(outputs);
493 }
494
495 g_key_file_free(conf);
496 }
497
498 const gchar*
qof_log_level_to_string(QofLogLevel log_level)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
qof_log_level_from_string(const gchar * str)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 }
540