28 #include <glib/gstdio.h> 43 # warning "<unistd.h> required." 54 #define G_LOG_DOMAIN "qof.log" 58 #include <string_view> 63 #define QOF_LOG_MAX_CHARS 50 64 #define QOF_LOG_MAX_CHARS_WITH_ALLOWANCE 100 65 #define QOF_LOG_INDENT_WIDTH 4 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";
75 using StrVec = std::vector<std::string>;
78 using ModuleEntryPtr = std::unique_ptr<ModuleEntry>;
79 using MEVec = std::vector<ModuleEntryPtr>;
81 static constexpr
int parts = 4;
82 static constexpr QofLogLevel default_level = QOF_LOG_WARNING;
83 static QofLogLevel current_max{default_level};
87 ModuleEntry(
const std::string& name, QofLogLevel level) :
88 m_name{name}, m_level{level} {
89 m_children.reserve(parts);
97 static ModuleEntryPtr _modules = NULL;
103 _modules = std::make_unique<ModuleEntry>(
"", default_level);
104 return _modules.get();
108 split_domain (
const std::string_view domain)
111 domain_parts.reserve(parts);
113 auto pos = domain.find(
".");
114 if (pos == std::string_view::npos)
116 domain_parts.emplace_back(domain);
120 while (pos != std::string_view::npos)
122 auto part_name{domain.substr(start, pos - start)};
123 domain_parts.emplace_back(part_name);
125 pos = domain.find(
".", start);
127 auto part_name{domain.substr(start, pos)};
128 domain_parts.emplace_back(part_name);
136 qof_log_num_spaces += QOF_LOG_INDENT_WIDTH;
143 = (qof_log_num_spaces < QOF_LOG_INDENT_WIDTH)
145 : qof_log_num_spaces - QOF_LOG_INDENT_WIDTH;
166 log4glib_handler(
const gchar *log_domain,
167 GLogLevelFlags log_level,
168 const gchar *message,
171 QofLogLevel level =
static_cast<QofLogLevel
>(log_level);
176 char timestamp_buf[10];
179 const char *format_24hour =
186 const char *level_str = qof_log_level_to_string(level);
191 fprintf(fout, qof_logger_format,
194 (log_domain == NULL ?
"" : log_domain),
195 qof_log_num_spaces,
"",
197 (g_str_has_suffix(message,
"\n") ?
"" :
"\n"));
213 gboolean warn_about_missing_permission = FALSE;
214 auto modules = get_modules();
216 if (!qof_logger_format)
217 qof_logger_format = g_strdup (
"* %s %*s <%s> %*s%s%s");
224 if (fout != NULL && fout != stderr && fout != stdout)
227 fname = g_strconcat(log_filename,
".XXXXXX.log",
nullptr);
229 if ((fd = g_mkstemp(fname)) != -1)
231 #if PLATFORM(WINDOWS) 234 fout = g_fopen(fname,
"wb");
237 g_assert(g_strcmp0(log_filename,
"/dev/null") != 0);
241 g_rename(fname, log_filename);
242 fout = fdopen(fd,
"w");
245 warn_about_missing_permission = TRUE;
249 warn_about_missing_permission = TRUE;
258 if (previous_handler == NULL)
259 previous_handler = g_log_set_default_handler(log4glib_handler, modules);
261 if (warn_about_missing_permission)
263 g_critical(
"Cannot open log output file \"%s\", using stderr.", log_filename);
270 if (fout && fout != stderr && fout != stdout)
278 g_free(function_buffer);
279 function_buffer = NULL;
282 if (_modules != NULL)
287 if (previous_handler != NULL)
289 g_log_set_default_handler(previous_handler, NULL);
290 previous_handler = NULL;
297 if (!log_module || level == QOF_LOG_FATAL)
300 if (level > current_max)
303 auto module_parts = split_domain(log_module);
304 auto module = get_modules();
305 for (
auto part : module_parts)
307 auto iter = std::find_if(module->m_children.begin(),
308 module->m_children.end(),
310 return child && part == child->m_name;
312 if (iter == module->m_children.end())
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();
320 module = iter->get();
323 module->m_level = level;
331 if (level > current_max)
333 if (level <= default_level)
335 auto module = get_modules();
337 if (level <= module->m_level)
343 auto domain_vec = split_domain(domain);
345 for (
auto part : domain_vec)
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; });
352 if (iter == module->m_children.end())
355 if (level <= (*iter)->m_level)
358 module = iter->get();
366 gchar *p, *buffer, *begin;
378 buffer = g_strndup(name, QOF_LOG_MAX_CHARS_WITH_ALLOWANCE - 1);
379 length = strlen(buffer);
380 p = g_strstr_len (buffer, length,
"(");
382 begin = g_strrstr (buffer,
"*");
384 begin = g_strrstr (buffer,
" ");
385 else if (* (begin + 1) ==
' ')
393 g_free(function_buffer);
394 function_buffer = g_strdup(p);
396 return function_buffer;
402 if (g_ascii_strcasecmp(
"stderr", log_to_filename) == 0)
407 else if (g_ascii_strcasecmp(
"stdout", log_to_filename) == 0)
421 const gchar *levels_group =
"levels", *output_group =
"output";
423 GKeyFile *conf = g_key_file_new();
425 if (!g_key_file_load_from_file(conf, filename, G_KEY_FILE_NONE, &err))
427 g_warning(
"unable to parse [%s]: %s", filename, err->message);
432 DEBUG(
"parsing log config from [%s]", filename);
433 if (g_key_file_has_group(conf, levels_group))
436 unsigned int key_idx;
438 gint logger_max_name_length = 12;
441 levels = g_key_file_get_keys(conf, levels_group, &num_levels, NULL);
443 for (key_idx = 0; key_idx < num_levels && levels[key_idx] != NULL; key_idx++)
446 gchar *logger_name = NULL, *level_str = NULL;
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);
453 DEBUG(
"setting log [%s] to level [%s=%d]", logger_name, level_str, level);
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);
469 if (g_key_file_has_group(conf, output_group))
472 unsigned int output_idx;
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++)
478 gchar *key = outputs[output_idx];
481 if (g_ascii_strcasecmp(
"to", key) != 0)
483 g_warning(
"unknown key [%s] in [outputs], skipping", key);
487 value = g_key_file_get_string(conf, output_group, key, NULL);
488 DEBUG(
"setting [output].to=[%s]", value);
495 g_key_file_free(conf);
499 qof_log_level_to_string(QofLogLevel log_level)
501 const char *level_str;
510 case QOF_LOG_WARNING:
513 case QOF_LOG_MESSAGE:
530 qof_log_level_from_string(
const gchar *str)
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;
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 ...
void qof_log_set_level(QofLogModule log_module, QofLogLevel level)
Set the logging level of the given log_module.
void qof_log_dedent(void)
De-dent one level, capped at 0; see LEAVE macro.
#define DEBUG(format, args...)
Print a debugging message.
void qof_log_init(void)
Initialize the error logging subsystem.
void qof_log_shutdown(void)
Be nice, close the logfile if possible.
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.
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 ...
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. ...
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.
void qof_log_indent(void)
Indents one level; see ENTER macro.
time64 gnc_time(time64 *tbuf)
get the current local time
gint64 time64
Many systems, including Microsoft Windows and BSD-derived Unixes like Darwin, are retaining the int-3...
void qof_log_parse_log_config(const char *filename)
Parse a log-configuration file.
void qof_log_set_file(FILE *outfile)
Specify an alternate log output, to pipe or file.