Line data Source code
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 :
81 : struct ModuleEntry
82 : {
83 115 : ModuleEntry(const std::string& name, QofLogLevel level) :
84 115 : m_name{name}, m_level{level} {
85 115 : m_children.reserve(parts);
86 115 : }
87 115 : ~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 85937 : get_modules()
97 : {
98 85937 : if (!_modules)
99 111 : _modules = std::make_unique<ModuleEntry>("", default_level);
100 85937 : return _modules.get();
101 : }
102 :
103 : static StrVec
104 85810 : split_domain (const std::string_view domain)
105 : {
106 85810 : StrVec domain_parts;
107 85810 : domain_parts.reserve(parts);
108 85810 : int start = 0;
109 85810 : auto pos = domain.find(".");
110 85810 : if (pos == std::string_view::npos)
111 : {
112 1 : domain_parts.emplace_back(domain);
113 : }
114 : else
115 : {
116 172052 : while (pos != std::string_view::npos)
117 : {
118 86243 : auto part_name{domain.substr(start, pos - start)};
119 86243 : domain_parts.emplace_back(part_name);
120 86243 : start = pos + 1;
121 86243 : pos = domain.find(".", start);
122 : }
123 85809 : auto part_name{domain.substr(start, pos)};
124 85809 : domain_parts.emplace_back(part_name);
125 : }
126 85810 : return domain_parts;
127 0 : }
128 :
129 : void
130 942 : qof_log_indent(void)
131 : {
132 942 : qof_log_num_spaces += QOF_LOG_INDENT_WIDTH;
133 942 : }
134 :
135 : void
136 942 : qof_log_dedent(void)
137 : {
138 : qof_log_num_spaces
139 942 : = (qof_log_num_spaces < QOF_LOG_INDENT_WIDTH)
140 942 : ? 0
141 : : qof_log_num_spaces - QOF_LOG_INDENT_WIDTH;
142 942 : }
143 :
144 : void
145 7 : qof_log_set_file(FILE *outfile)
146 : {
147 7 : if (!outfile)
148 : {
149 0 : fout = stderr;
150 0 : return;
151 : }
152 7 : fout = outfile;
153 : }
154 :
155 : void
156 127 : qof_log_init(void)
157 : {
158 127 : qof_log_init_filename(nullptr);
159 127 : }
160 :
161 : static void
162 7642 : log4glib_handler(const gchar *log_domain,
163 : GLogLevelFlags log_level,
164 : const gchar *message,
165 : gpointer user_data)
166 : {
167 7642 : QofLogLevel level = static_cast<QofLogLevel>(log_level);
168 7642 : if (G_LIKELY(!qof_log_check(log_domain, level)))
169 5863 : return;
170 :
171 : {
172 : char timestamp_buf[10];
173 : time64 now;
174 : struct tm now_tm;
175 1779 : const char *format_24hour =
176 : #ifdef G_OS_WIN32
177 : "%H:%M:%S"
178 : #else
179 : "%T"
180 : #endif
181 : ;
182 1779 : const char *level_str = qof_log_level_to_string(level);
183 1779 : now = gnc_time (nullptr);
184 1779 : gnc_localtime_r (&now, &now_tm);
185 1779 : qof_strftime(timestamp_buf, 9, format_24hour, &now_tm);
186 :
187 1779 : 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 1779 : (g_str_has_suffix(message, "\n") ? "" : "\n"));
194 1779 : 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 127 : qof_log_init_filename(const gchar* log_filename)
208 : {
209 127 : gboolean warn_about_missing_permission = FALSE;
210 127 : auto modules = get_modules();
211 :
212 127 : if (!qof_logger_format)
213 111 : qof_logger_format = g_strdup ("* %s %*s <%s> %*s%s%s"); //default format
214 :
215 127 : if (log_filename)
216 : {
217 : int fd;
218 : gchar *fname;
219 :
220 0 : if (fout != nullptr && fout != stderr && fout != stdout)
221 0 : fclose(fout);
222 :
223 0 : fname = g_strconcat(log_filename, ".XXXXXX.log", nullptr);
224 :
225 0 : 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 0 : 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 0 : g_rename(fname, log_filename);
238 0 : fout = fdopen(fd, "w");
239 : #endif
240 0 : if (!fout)
241 0 : warn_about_missing_permission = TRUE;
242 : }
243 : else
244 : {
245 0 : warn_about_missing_permission = TRUE;
246 0 : fout = stderr;
247 : }
248 0 : g_free(fname);
249 : }
250 :
251 127 : if (!fout)
252 97 : fout = stderr;
253 :
254 127 : if (previous_handler == nullptr)
255 111 : previous_handler = g_log_set_default_handler(log4glib_handler, modules);
256 :
257 127 : if (warn_about_missing_permission)
258 : {
259 0 : g_critical("Cannot open log output file \"%s\", using stderr.", log_filename);
260 : }
261 127 : }
262 :
263 : void
264 43 : qof_log_shutdown (void)
265 : {
266 43 : if (fout && fout != stderr && fout != stdout)
267 : {
268 0 : fclose(fout);
269 0 : fout = nullptr;
270 : }
271 :
272 43 : if (qof_logger_format)
273 : {
274 43 : g_free (qof_logger_format);
275 43 : qof_logger_format = nullptr;
276 : }
277 :
278 43 : if (function_buffer)
279 : {
280 10 : g_free(function_buffer);
281 10 : function_buffer = nullptr;
282 : }
283 :
284 43 : if (_modules != nullptr)
285 : {
286 43 : _modules = nullptr;
287 : }
288 :
289 43 : if (previous_handler != nullptr)
290 : {
291 43 : g_log_set_default_handler(previous_handler, nullptr);
292 43 : previous_handler = nullptr;
293 : }
294 43 : }
295 :
296 : void
297 3 : qof_log_set_level(QofLogModule log_module, QofLogLevel level)
298 : {
299 3 : if (!log_module || level == QOF_LOG_FATAL)
300 0 : return;
301 :
302 3 : if (level > current_max)
303 2 : current_max = level;
304 :
305 3 : auto module_parts = split_domain(log_module);
306 3 : auto module = get_modules();
307 11 : for (const auto& part : module_parts)
308 : {
309 5 : auto iter = std::find_if(module->m_children.begin(),
310 : module->m_children.end(),
311 10 : [part](const auto& child){
312 2 : return child && part == child->m_name;
313 : });
314 10 : if (iter == module->m_children.end())
315 : {
316 4 : auto child = std::make_unique<ModuleEntry>(part, default_level);
317 8 : module->m_children.emplace_back(std::move(child));
318 4 : module = module->m_children.back().get();
319 4 : }
320 : else
321 : {
322 1 : module = iter->get();
323 : }
324 : }
325 3 : module->m_level = level;
326 3 : }
327 :
328 :
329 : gboolean
330 1382328 : qof_log_check(QofLogModule domain, QofLogLevel level)
331 : {
332 : // Check the global levels
333 1382328 : if (level > current_max)
334 1294742 : return FALSE;
335 87586 : if (level <= default_level)
336 1779 : return TRUE;
337 85807 : auto module = get_modules();
338 : // If the level <= the default then no need to look further.
339 85807 : if (level <= module->m_level)
340 0 : return TRUE;
341 :
342 85807 : if (!domain)
343 0 : return FALSE;
344 :
345 85807 : auto domain_vec = split_domain(domain);
346 :
347 173093 : for (const auto& part : domain_vec)
348 : {
349 87286 : auto iter = std::find_if(module->m_children.begin(),
350 : module->m_children.end(),
351 174572 : [part](const auto& child) {
352 88358 : return child && part == child->m_name; });
353 :
354 174572 : if (iter == module->m_children.end())
355 85807 : return FALSE;
356 :
357 4715 : if (level <= (*iter)->m_level)
358 3236 : return TRUE;
359 :
360 1479 : module = iter->get();
361 : }
362 0 : return FALSE;
363 85807 : }
364 :
365 : const char *
366 4535 : qof_log_prettify (const char *name)
367 : {
368 : gchar *p, *buffer, *begin;
369 : gint length;
370 :
371 4535 : if (!name)
372 : {
373 0 : 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 4535 : buffer = g_strndup(name, QOF_LOG_MAX_CHARS_WITH_ALLOWANCE - 1);
381 4535 : length = strlen(buffer);
382 4535 : p = g_strstr_len (buffer, length, "(");
383 4535 : if (p) *p = '\0';
384 4535 : begin = g_strrstr (buffer, "*");
385 4535 : if (begin == nullptr)
386 3153 : begin = g_strrstr (buffer, " ");
387 1382 : else if (* (begin + 1) == ' ')
388 1380 : ++ begin;
389 4535 : if (begin != nullptr)
390 4497 : p = begin + 1;
391 : else
392 38 : p = buffer;
393 :
394 4535 : if (function_buffer)
395 4501 : g_free(function_buffer);
396 4535 : function_buffer = g_strdup(p);
397 4535 : g_free(buffer);
398 4535 : return function_buffer;
399 : }
400 :
401 : void
402 7 : qof_log_init_filename_special(const char *log_to_filename)
403 : {
404 7 : if (g_ascii_strcasecmp("stderr", log_to_filename) == 0)
405 : {
406 7 : qof_log_init();
407 7 : qof_log_set_file(stderr);
408 : }
409 0 : else if (g_ascii_strcasecmp("stdout", log_to_filename) == 0)
410 : {
411 0 : qof_log_init();
412 0 : qof_log_set_file(stdout);
413 : }
414 : else
415 : {
416 0 : qof_log_init_filename(log_to_filename);
417 : }
418 7 : }
419 :
420 : void
421 0 : qof_log_parse_log_config(const char *filename)
422 : {
423 0 : const gchar *levels_group = "levels", *output_group = "output";
424 0 : GError *err = nullptr;
425 0 : GKeyFile *conf = g_key_file_new();
426 :
427 0 : if (!g_key_file_load_from_file(conf, filename, G_KEY_FILE_NONE, &err))
428 : {
429 0 : g_warning("unable to parse [%s]: %s", filename, err->message);
430 0 : g_error_free(err);
431 0 : g_key_file_free(conf);
432 0 : return;
433 : }
434 :
435 0 : DEBUG("parsing log config from [%s]", filename);
436 0 : if (g_key_file_has_group(conf, levels_group))
437 : {
438 : gsize num_levels;
439 : unsigned int key_idx;
440 : gchar **levels;
441 0 : gint logger_max_name_length = 12;
442 0 : gchar *str = nullptr;
443 :
444 0 : levels = g_key_file_get_keys(conf, levels_group, &num_levels, nullptr);
445 :
446 0 : for (key_idx = 0; key_idx < num_levels && levels[key_idx] != nullptr; key_idx++)
447 : {
448 : QofLogLevel level;
449 0 : gchar *logger_name = nullptr, *level_str = nullptr;
450 :
451 0 : logger_name = g_strdup(levels[key_idx]);
452 0 : logger_max_name_length = MAX (logger_max_name_length, (gint) strlen (logger_name));
453 0 : level_str = g_key_file_get_string(conf, levels_group, logger_name, nullptr);
454 0 : level = qof_log_level_from_string(level_str);
455 :
456 0 : DEBUG("setting log [%s] to level [%s=%d]", logger_name, level_str, level);
457 0 : qof_log_set_level(logger_name, level);
458 :
459 0 : g_free(logger_name);
460 0 : g_free(level_str);
461 : }
462 :
463 0 : str = g_strdup_printf ("%d", logger_max_name_length);
464 0 : if (qof_logger_format)
465 0 : g_free (qof_logger_format);
466 0 : qof_logger_format = g_strconcat ("* %s %*s <%-", str, ".", str, "s> %*s%s%s", nullptr);
467 :
468 0 : g_free (str);
469 0 : g_strfreev(levels);
470 : }
471 :
472 0 : if (g_key_file_has_group(conf, output_group))
473 : {
474 : gsize num_outputs;
475 : unsigned int output_idx;
476 : gchar **outputs;
477 :
478 0 : outputs = g_key_file_get_keys(conf, output_group, &num_outputs, nullptr);
479 0 : for (output_idx = 0; output_idx < num_outputs && outputs[output_idx] != nullptr; output_idx++)
480 : {
481 0 : gchar *key = outputs[output_idx];
482 : gchar *value;
483 :
484 0 : if (g_ascii_strcasecmp("to", key) != 0)
485 : {
486 0 : g_warning("unknown key [%s] in [outputs], skipping", key);
487 0 : continue;
488 : }
489 :
490 0 : value = g_key_file_get_string(conf, output_group, key, nullptr);
491 0 : DEBUG("setting [output].to=[%s]", value);
492 0 : qof_log_init_filename_special(value);
493 0 : g_free(value);
494 : }
495 0 : g_strfreev(outputs);
496 : }
497 :
498 0 : g_key_file_free(conf);
499 : }
500 :
501 : const gchar*
502 1779 : qof_log_level_to_string(QofLogLevel log_level)
503 : {
504 : const char *level_str;
505 1779 : switch (log_level)
506 : {
507 0 : case QOF_LOG_FATAL:
508 0 : level_str = "FATAL";
509 0 : break;
510 1522 : case QOF_LOG_ERROR:
511 1522 : level_str = "ERROR";
512 1522 : break;
513 257 : case QOF_LOG_WARNING:
514 257 : level_str = "WARN";
515 257 : break;
516 0 : case QOF_LOG_MESSAGE:
517 0 : level_str = "MESSG";
518 0 : break;
519 0 : case QOF_LOG_INFO:
520 0 : level_str = "INFO";
521 0 : break;
522 0 : case QOF_LOG_DEBUG:
523 0 : level_str = "DEBUG";
524 0 : break;
525 0 : default:
526 0 : level_str = "OTHER";
527 0 : break;
528 : }
529 1779 : return level_str;
530 : }
531 :
532 : QofLogLevel
533 0 : qof_log_level_from_string(const gchar *str)
534 : {
535 0 : if (g_ascii_strncasecmp("error", str, 5) == 0) return QOF_LOG_FATAL;
536 0 : if (g_ascii_strncasecmp("crit", str, 4) == 0) return QOF_LOG_ERROR;
537 0 : if (g_ascii_strncasecmp("warn", str, 4) == 0) return QOF_LOG_WARNING;
538 0 : if (g_ascii_strncasecmp("mess", str, 4) == 0) return QOF_LOG_MESSAGE;
539 0 : if (g_ascii_strncasecmp("info", str, 4) == 0) return QOF_LOG_INFO;
540 0 : if (g_ascii_strncasecmp("debug", str, 5) == 0) return QOF_LOG_DEBUG;
541 0 : return QOF_LOG_DEBUG;
542 : }
|