LCOV - code coverage report
Current view: top level - libgnucash/engine - qoflog.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 60.9 % 238 145
Test Date: 2026-11-02 18:19:43 Functions: 90.0 % 20 18
Legend: Lines:     hit not hit

            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              : }
        

Generated by: LCOV version 2.0-1