Branch data 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 : 113 : ModuleEntry(const std::string& name, QofLogLevel level) :
84 : 113 : m_name{name}, m_level{level} {
85 : 113 : m_children.reserve(parts);
86 : 113 : }
87 : 113 : ~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 : 84335 : get_modules()
97 : : {
98 : 84335 : if (!_modules)
99 : 109 : _modules = std::make_unique<ModuleEntry>("", default_level);
100 : 84335 : return _modules.get();
101 : : }
102 : :
103 : : static StrVec
104 : 84210 : split_domain (const std::string_view domain)
105 : : {
106 : 84210 : StrVec domain_parts;
107 : 84210 : domain_parts.reserve(parts);
108 : 84210 : int start = 0;
109 : 84210 : auto pos = domain.find(".");
110 : 84210 : if (pos == std::string_view::npos)
111 : : {
112 : 1 : domain_parts.emplace_back(domain);
113 : : }
114 : : else
115 : : {
116 : 168852 : while (pos != std::string_view::npos)
117 : : {
118 : 84643 : auto part_name{domain.substr(start, pos - start)};
119 : 84643 : domain_parts.emplace_back(part_name);
120 : 84643 : start = pos + 1;
121 : 84643 : pos = domain.find(".", start);
122 : : }
123 : 84209 : auto part_name{domain.substr(start, pos)};
124 : 84209 : domain_parts.emplace_back(part_name);
125 : : }
126 : 84210 : return domain_parts;
127 : 0 : }
128 : :
129 : : void
130 : 943 : qof_log_indent(void)
131 : : {
132 : 943 : qof_log_num_spaces += QOF_LOG_INDENT_WIDTH;
133 : 943 : }
134 : :
135 : : void
136 : 943 : qof_log_dedent(void)
137 : : {
138 : : qof_log_num_spaces
139 : 943 : = (qof_log_num_spaces < QOF_LOG_INDENT_WIDTH)
140 : 943 : ? 0
141 : : : qof_log_num_spaces - QOF_LOG_INDENT_WIDTH;
142 : 943 : }
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 : 125 : qof_log_init(void)
157 : : {
158 : 125 : qof_log_init_filename(nullptr);
159 : 125 : }
160 : :
161 : : static void
162 : 7568 : log4glib_handler(const gchar *log_domain,
163 : : GLogLevelFlags log_level,
164 : : const gchar *message,
165 : : gpointer user_data)
166 : : {
167 : 7568 : QofLogLevel level = static_cast<QofLogLevel>(log_level);
168 : 7568 : 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 : 1705 : const char *format_24hour =
176 : : #ifdef G_OS_WIN32
177 : : "%H:%M:%S"
178 : : #else
179 : : "%T"
180 : : #endif
181 : : ;
182 : 1705 : const char *level_str = qof_log_level_to_string(level);
183 : 1705 : now = gnc_time (nullptr);
184 : 1705 : gnc_localtime_r (&now, &now_tm);
185 : 1705 : qof_strftime(timestamp_buf, 9, format_24hour, &now_tm);
186 : :
187 : 1705 : 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 : 1705 : (g_str_has_suffix(message, "\n") ? "" : "\n"));
194 : 1705 : 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 : 125 : qof_log_init_filename(const gchar* log_filename)
208 : : {
209 : 125 : gboolean warn_about_missing_permission = FALSE;
210 : 125 : auto modules = get_modules();
211 : :
212 : 125 : if (!qof_logger_format)
213 : 95 : qof_logger_format = g_strdup ("* %s %*s <%s> %*s%s%s"); //default format
214 : :
215 : 125 : 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 : 125 : if (!fout)
252 : 95 : fout = stderr;
253 : :
254 : 125 : if (previous_handler == nullptr)
255 : 109 : previous_handler = g_log_set_default_handler(log4glib_handler, modules);
256 : :
257 : 125 : if (warn_about_missing_permission)
258 : : {
259 : 0 : g_critical("Cannot open log output file \"%s\", using stderr.", log_filename);
260 : : }
261 : 125 : }
262 : :
263 : : void
264 : 42 : qof_log_shutdown (void)
265 : : {
266 : 42 : if (fout && fout != stderr && fout != stdout)
267 : : {
268 : 0 : fclose(fout);
269 : 0 : fout = nullptr;
270 : : }
271 : :
272 : 42 : if (function_buffer)
273 : : {
274 : 9 : g_free(function_buffer);
275 : 9 : function_buffer = nullptr;
276 : : }
277 : :
278 : 42 : if (_modules != nullptr)
279 : : {
280 : 42 : _modules = nullptr;
281 : : }
282 : :
283 : 42 : if (previous_handler != nullptr)
284 : : {
285 : 42 : g_log_set_default_handler(previous_handler, nullptr);
286 : 42 : previous_handler = nullptr;
287 : : }
288 : 42 : }
289 : :
290 : : void
291 : 3 : qof_log_set_level(QofLogModule log_module, QofLogLevel level)
292 : : {
293 : 3 : if (!log_module || level == QOF_LOG_FATAL)
294 : 0 : return;
295 : :
296 : 3 : if (level > current_max)
297 : 2 : current_max = level;
298 : :
299 : 3 : auto module_parts = split_domain(log_module);
300 : 3 : auto module = get_modules();
301 : 8 : for (auto part : module_parts)
302 : : {
303 : 5 : auto iter = std::find_if(module->m_children.begin(),
304 : : module->m_children.end(),
305 : 10 : [part](auto& child){
306 : 2 : return child && part == child->m_name;
307 : : });
308 : 5 : if (iter == module->m_children.end())
309 : : {
310 : 4 : auto child = std::make_unique<ModuleEntry>(part, default_level);
311 : 4 : module->m_children.emplace_back(std::move(child));
312 : 4 : module = module->m_children.back().get();
313 : 4 : }
314 : : else
315 : : {
316 : 1 : module = iter->get();
317 : : }
318 : 5 : }
319 : 3 : module->m_level = level;
320 : 3 : }
321 : :
322 : :
323 : : gboolean
324 : 1274118 : qof_log_check(QofLogModule domain, QofLogLevel level)
325 : : {
326 : : // Check the global levels
327 : 1274118 : if (level > current_max)
328 : 1188206 : return FALSE;
329 : 85912 : if (level <= default_level)
330 : 1705 : return TRUE;
331 : 84207 : auto module = get_modules();
332 : : // If the level <= the default then no need to look further.
333 : 84207 : if (level <= module->m_level)
334 : 0 : return TRUE;
335 : :
336 : 84207 : if (!domain)
337 : 0 : return FALSE;
338 : :
339 : 84207 : auto domain_vec = split_domain(domain);
340 : :
341 : 85577 : for (const auto& part : domain_vec)
342 : : {
343 : 85577 : auto iter = std::find_if(module->m_children.begin(),
344 : : module->m_children.end(),
345 : 171154 : [part](auto& child) {
346 : 86557 : return child && part == child->m_name; });
347 : :
348 : 85577 : if (iter == module->m_children.end())
349 : 84207 : return FALSE;
350 : :
351 : 4572 : if (level <= (*iter)->m_level)
352 : 3202 : return TRUE;
353 : :
354 : 1370 : module = iter->get();
355 : : }
356 : 0 : return FALSE;
357 : 84207 : }
358 : :
359 : : const char *
360 : 4504 : qof_log_prettify (const char *name)
361 : : {
362 : : gchar *p, *buffer, *begin;
363 : : gint length;
364 : :
365 : 4504 : if (!name)
366 : : {
367 : 0 : return "";
368 : : }
369 : : /* Clang's __func__ displays the whole signature, like a good C++
370 : : * compier should. Gcc displays only the name of the function. Strip
371 : : * the extras from Clang's output so that log messages are the same
372 : : * regardless of compiler.
373 : : */
374 : 4504 : buffer = g_strndup(name, QOF_LOG_MAX_CHARS_WITH_ALLOWANCE - 1);
375 : 4504 : length = strlen(buffer);
376 : 4504 : p = g_strstr_len (buffer, length, "(");
377 : 4504 : if (p) *p = '\0';
378 : 4504 : begin = g_strrstr (buffer, "*");
379 : 4504 : if (begin == nullptr)
380 : 3089 : begin = g_strrstr (buffer, " ");
381 : 1415 : else if (* (begin + 1) == ' ')
382 : 1413 : ++ begin;
383 : 4504 : if (begin != nullptr)
384 : 4466 : p = begin + 1;
385 : : else
386 : 38 : p = buffer;
387 : :
388 : 4504 : if (function_buffer)
389 : 4472 : g_free(function_buffer);
390 : 4504 : function_buffer = g_strdup(p);
391 : 4504 : g_free(buffer);
392 : 4504 : return function_buffer;
393 : : }
394 : :
395 : : void
396 : 7 : qof_log_init_filename_special(const char *log_to_filename)
397 : : {
398 : 7 : if (g_ascii_strcasecmp("stderr", log_to_filename) == 0)
399 : : {
400 : 7 : qof_log_init();
401 : 7 : qof_log_set_file(stderr);
402 : : }
403 : 0 : else if (g_ascii_strcasecmp("stdout", log_to_filename) == 0)
404 : : {
405 : 0 : qof_log_init();
406 : 0 : qof_log_set_file(stdout);
407 : : }
408 : : else
409 : : {
410 : 0 : qof_log_init_filename(log_to_filename);
411 : : }
412 : 7 : }
413 : :
414 : : void
415 : 0 : qof_log_parse_log_config(const char *filename)
416 : : {
417 : 0 : const gchar *levels_group = "levels", *output_group = "output";
418 : 0 : GError *err = nullptr;
419 : 0 : GKeyFile *conf = g_key_file_new();
420 : :
421 : 0 : if (!g_key_file_load_from_file(conf, filename, G_KEY_FILE_NONE, &err))
422 : : {
423 : 0 : g_warning("unable to parse [%s]: %s", filename, err->message);
424 : 0 : g_error_free(err);
425 : 0 : return;
426 : : }
427 : :
428 : 0 : DEBUG("parsing log config from [%s]", filename);
429 : 0 : if (g_key_file_has_group(conf, levels_group))
430 : : {
431 : : gsize num_levels;
432 : : unsigned int key_idx;
433 : : gchar **levels;
434 : 0 : gint logger_max_name_length = 12;
435 : 0 : gchar *str = nullptr;
436 : :
437 : 0 : levels = g_key_file_get_keys(conf, levels_group, &num_levels, nullptr);
438 : :
439 : 0 : for (key_idx = 0; key_idx < num_levels && levels[key_idx] != nullptr; key_idx++)
440 : : {
441 : : QofLogLevel level;
442 : 0 : gchar *logger_name = nullptr, *level_str = nullptr;
443 : :
444 : 0 : logger_name = g_strdup(levels[key_idx]);
445 : 0 : logger_max_name_length = MAX (logger_max_name_length, (gint) strlen (logger_name));
446 : 0 : level_str = g_key_file_get_string(conf, levels_group, logger_name, nullptr);
447 : 0 : level = qof_log_level_from_string(level_str);
448 : :
449 : 0 : DEBUG("setting log [%s] to level [%s=%d]", logger_name, level_str, level);
450 : 0 : qof_log_set_level(logger_name, level);
451 : :
452 : 0 : g_free(logger_name);
453 : 0 : g_free(level_str);
454 : : }
455 : :
456 : 0 : str = g_strdup_printf ("%d", logger_max_name_length);
457 : 0 : if (qof_logger_format)
458 : 0 : g_free (qof_logger_format);
459 : 0 : qof_logger_format = g_strconcat ("* %s %*s <%-", str, ".", str, "s> %*s%s%s", nullptr);
460 : :
461 : 0 : g_free (str);
462 : 0 : g_strfreev(levels);
463 : : }
464 : :
465 : 0 : if (g_key_file_has_group(conf, output_group))
466 : : {
467 : : gsize num_outputs;
468 : : unsigned int output_idx;
469 : : gchar **outputs;
470 : :
471 : 0 : outputs = g_key_file_get_keys(conf, output_group, &num_outputs, nullptr);
472 : 0 : for (output_idx = 0; output_idx < num_outputs && outputs[output_idx] != nullptr; output_idx++)
473 : : {
474 : 0 : gchar *key = outputs[output_idx];
475 : : gchar *value;
476 : :
477 : 0 : if (g_ascii_strcasecmp("to", key) != 0)
478 : : {
479 : 0 : g_warning("unknown key [%s] in [outputs], skipping", key);
480 : 0 : continue;
481 : : }
482 : :
483 : 0 : value = g_key_file_get_string(conf, output_group, key, nullptr);
484 : 0 : DEBUG("setting [output].to=[%s]", value);
485 : 0 : qof_log_init_filename_special(value);
486 : 0 : g_free(value);
487 : : }
488 : 0 : g_strfreev(outputs);
489 : : }
490 : :
491 : 0 : g_key_file_free(conf);
492 : : }
493 : :
494 : : const gchar*
495 : 1705 : qof_log_level_to_string(QofLogLevel log_level)
496 : : {
497 : : const char *level_str;
498 : 1705 : switch (log_level)
499 : : {
500 : 0 : case QOF_LOG_FATAL:
501 : 0 : level_str = "FATAL";
502 : 0 : break;
503 : 1426 : case QOF_LOG_ERROR:
504 : 1426 : level_str = "ERROR";
505 : 1426 : break;
506 : 279 : case QOF_LOG_WARNING:
507 : 279 : level_str = "WARN";
508 : 279 : break;
509 : 0 : case QOF_LOG_MESSAGE:
510 : 0 : level_str = "MESSG";
511 : 0 : break;
512 : 0 : case QOF_LOG_INFO:
513 : 0 : level_str = "INFO";
514 : 0 : break;
515 : 0 : case QOF_LOG_DEBUG:
516 : 0 : level_str = "DEBUG";
517 : 0 : break;
518 : 0 : default:
519 : 0 : level_str = "OTHER";
520 : 0 : break;
521 : : }
522 : 1705 : return level_str;
523 : : }
524 : :
525 : : QofLogLevel
526 : 0 : qof_log_level_from_string(const gchar *str)
527 : : {
528 : 0 : if (g_ascii_strncasecmp("error", str, 5) == 0) return QOF_LOG_FATAL;
529 : 0 : if (g_ascii_strncasecmp("crit", str, 4) == 0) return QOF_LOG_ERROR;
530 : 0 : if (g_ascii_strncasecmp("warn", str, 4) == 0) return QOF_LOG_WARNING;
531 : 0 : if (g_ascii_strncasecmp("mess", str, 4) == 0) return QOF_LOG_MESSAGE;
532 : 0 : if (g_ascii_strncasecmp("info", str, 4) == 0) return QOF_LOG_INFO;
533 : 0 : if (g_ascii_strncasecmp("debug", str, 5) == 0) return QOF_LOG_DEBUG;
534 : 0 : return QOF_LOG_DEBUG;
535 : : }
|