LCOV - code coverage report
Current view: top level - mutt - logging.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 90 179 50.3 %
Date: 2022-03-09 12:17:43 Functions: 15 16 93.8 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  * Logging Dispatcher
       4             :  *
       5             :  * @authors
       6             :  * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
       7             :  *
       8             :  * @copyright
       9             :  * This program is free software: you can redistribute it and/or modify it under
      10             :  * the terms of the GNU General Public License as published by the Free Software
      11             :  * Foundation, either version 2 of the License, or (at your option) any later
      12             :  * version.
      13             :  *
      14             :  * This program is distributed in the hope that it will be useful, but WITHOUT
      15             :  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
      16             :  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
      17             :  * details.
      18             :  *
      19             :  * You should have received a copy of the GNU General Public License along with
      20             :  * this program.  If not, see <http://www.gnu.org/licenses/>.
      21             :  */
      22             : 
      23             : /**
      24             :  * @page mutt_logging Logging Dispatcher
      25             :  *
      26             :  * Logging Dispatcher
      27             :  */
      28             : 
      29             : #include "config.h"
      30             : #include <errno.h>
      31             : #include <stdarg.h>
      32             : #include <stdbool.h>
      33             : #include <stdio.h>
      34             : #include <string.h>
      35             : #include <time.h>
      36             : #include <unistd.h>
      37             : #include "logging.h"
      38             : #include "date.h"
      39             : #include "file.h"
      40             : #include "memory.h"
      41             : #include "message.h"
      42             : #include "queue.h"
      43             : #include "string2.h"
      44             : 
      45             : const char *LevelAbbr = "PEWM12345N"; ///< Abbreviations of logging level names
      46             : 
      47             : /**
      48             :  * MuttLogger - The log dispatcher - @ingroup logging_api
      49             :  *
      50             :  * This function pointer controls where log messages are redirected.
      51             :  */
      52             : log_dispatcher_t MuttLogger = log_disp_terminal;
      53             : 
      54             : FILE *LogFileFP = NULL;      ///< Log file handle
      55             : char *LogFileName = NULL;    ///< Log file name
      56             : int LogFileLevel = 0;        ///< Log file level
      57             : char *LogFileVersion = NULL; ///< Program version
      58             : 
      59             : /**
      60             :  * LogQueue - In-memory list of log lines
      61             :  */
      62             : static struct LogLineList LogQueue = STAILQ_HEAD_INITIALIZER(LogQueue);
      63             : 
      64             : int LogQueueCount = 0; ///< Number of entries currently in the log queue
      65             : int LogQueueMax = 0;   ///< Maximum number of entries in the log queue
      66             : 
      67             : /**
      68             :  * timestamp - Create a YYYY-MM-DD HH:MM:SS timestamp
      69             :  * @param stamp Unix time
      70             :  * @retval ptr Timestamp string
      71             :  *
      72             :  * If stamp is 0, then the current time will be used.
      73             :  *
      74             :  * @note This function returns a pointer to a static buffer.
      75             :  *       Do not free it.
      76             :  */
      77           0 : static const char *timestamp(time_t stamp)
      78             : {
      79             :   static char buf[23] = { 0 };
      80             :   static time_t last = 0;
      81             : 
      82           0 :   if (stamp == 0)
      83           0 :     stamp = mutt_date_epoch();
      84             : 
      85           0 :   if (stamp != last)
      86             :   {
      87           0 :     mutt_date_localtime_format(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", stamp);
      88           0 :     last = stamp;
      89             :   }
      90             : 
      91           0 :   return buf;
      92             : }
      93             : 
      94             : /**
      95             :  * log_file_close - Close the log file
      96             :  * @param verbose If true, then log the event
      97             :  */
      98           2 : void log_file_close(bool verbose)
      99             : {
     100           2 :   if (!LogFileFP)
     101           2 :     return;
     102             : 
     103           0 :   fprintf(LogFileFP, "[%s] Closing log.\n", timestamp(0));
     104           0 :   fprintf(LogFileFP, "# vim: syntax=neomuttlog\n");
     105           0 :   mutt_file_fclose(&LogFileFP);
     106           0 :   if (verbose)
     107           0 :     mutt_message(_("Closed log file: %s"), LogFileName);
     108             : }
     109             : 
     110             : /**
     111             :  * log_file_open - Start logging to a file
     112             :  * @param verbose If true, then log the event
     113             :  * @retval  0 Success
     114             :  * @retval -1 Error, see errno
     115             :  *
     116             :  * Before opening a log file, call log_file_set_version(), log_file_set_level()
     117             :  * and log_file_set_filename().
     118             :  */
     119           2 : int log_file_open(bool verbose)
     120             : {
     121           2 :   if (!LogFileName)
     122           2 :     return -1;
     123             : 
     124           0 :   if (LogFileFP)
     125           0 :     log_file_close(false);
     126             : 
     127           0 :   if (LogFileLevel < LL_DEBUG1)
     128           0 :     return -1;
     129             : 
     130           0 :   LogFileFP = mutt_file_fopen(LogFileName, "a+");
     131           0 :   if (!LogFileFP)
     132           0 :     return -1;
     133           0 :   setvbuf(LogFileFP, NULL, _IOLBF, 0);
     134             : 
     135           0 :   fprintf(LogFileFP, "[%s] NeoMutt%s debugging at level %d\n", timestamp(0),
     136           0 :           NONULL(LogFileVersion), LogFileLevel);
     137           0 :   if (verbose)
     138           0 :     mutt_message(_("Debugging at level %d to file '%s'"), LogFileLevel, LogFileName);
     139           0 :   return 0;
     140             : }
     141             : 
     142             : /**
     143             :  * log_file_set_filename - Set the filename for the log
     144             :  * @param file Name to use
     145             :  * @param verbose If true, then log the event
     146             :  * @retval  0 Success, file opened
     147             :  * @retval -1 Error, see errno
     148             :  */
     149           2 : int log_file_set_filename(const char *file, bool verbose)
     150             : {
     151           2 :   if (!file)
     152           2 :     return -1;
     153             : 
     154             :   /* also handles both being NULL */
     155           0 :   if (mutt_str_equal(LogFileName, file))
     156           0 :     return 0;
     157             : 
     158           0 :   mutt_str_replace(&LogFileName, file);
     159             : 
     160           0 :   if (!LogFileName)
     161           0 :     log_file_close(verbose);
     162             : 
     163           0 :   return log_file_open(verbose);
     164             : }
     165             : 
     166             : /**
     167             :  * log_file_set_level - Set the logging level
     168             :  * @param level Logging level
     169             :  * @param verbose If true, then log the event
     170             :  * @retval  0 Success
     171             :  * @retval -1 Error, level is out of range
     172             :  *
     173             :  * The level should be: LL_MESSAGE <= level < LL_MAX.
     174             :  */
     175           2 : int log_file_set_level(enum LogLevel level, bool verbose)
     176             : {
     177           2 :   if ((level < LL_MESSAGE) || (level >= LL_MAX))
     178           2 :     return -1;
     179             : 
     180           0 :   if (level == LogFileLevel)
     181           0 :     return 0;
     182             : 
     183           0 :   LogFileLevel = level;
     184             : 
     185           0 :   if (level == LL_MESSAGE)
     186             :   {
     187           0 :     log_file_close(verbose);
     188             :   }
     189           0 :   else if (LogFileFP)
     190             :   {
     191           0 :     if (verbose)
     192           0 :       mutt_message(_("Logging at level %d to file '%s'"), LogFileLevel, LogFileName);
     193           0 :     fprintf(LogFileFP, "[%s] NeoMutt%s debugging at level %d\n", timestamp(0),
     194           0 :             NONULL(LogFileVersion), LogFileLevel);
     195             :   }
     196             :   else
     197             :   {
     198           0 :     log_file_open(verbose);
     199             :   }
     200             : 
     201           0 :   if (LogFileLevel >= LL_DEBUG5)
     202             :   {
     203           0 :     fprintf(LogFileFP,
     204             :             "\n"
     205             :             "WARNING:\n"
     206             :             "    Logging at this level can reveal personal information.\n"
     207             :             "    Review the log carefully before posting in bug reports.\n"
     208             :             "\n");
     209             :   }
     210             : 
     211           0 :   return 0;
     212             : }
     213             : 
     214             : /**
     215             :  * log_file_set_version - Set the program's version number
     216             :  * @param version Version number
     217             :  *
     218             :  * The string will be appended directly to 'NeoMutt', so it should begin with a
     219             :  * hyphen.
     220             :  */
     221           2 : void log_file_set_version(const char *version)
     222             : {
     223           2 :   mutt_str_replace(&LogFileVersion, version);
     224           2 : }
     225             : 
     226             : /**
     227             :  * log_file_running - Is the log file running?
     228             :  * @retval true The log file is running
     229             :  */
     230           2 : bool log_file_running(void)
     231             : {
     232           2 :   return LogFileFP;
     233             : }
     234             : 
     235             : /**
     236             :  * log_disp_file - Save a log line to a file - Implements ::log_dispatcher_t - @ingroup logging_api
     237             :  *
     238             :  * This log dispatcher saves a line of text to a file.  The format is:
     239             :  * * `[TIMESTAMP]<LEVEL> FUNCTION() FORMATTED-MESSAGE`
     240             :  *
     241             :  * The caller must first set #LogFileName and #LogFileLevel, then call
     242             :  * log_file_open().  Any logging above #LogFileLevel will be ignored.
     243             :  *
     244             :  * If stamp is 0, then the current time will be used.
     245             :  */
     246        4454 : int log_disp_file(time_t stamp, const char *file, int line,
     247             :                   const char *function, enum LogLevel level, ...)
     248             : {
     249        4454 :   if (!LogFileFP || (level < LL_PERROR) || (level > LogFileLevel))
     250        4454 :     return 0;
     251             : 
     252           0 :   int ret = 0;
     253           0 :   int err = errno;
     254             : 
     255           0 :   if (!function)
     256           0 :     function = "UNKNOWN";
     257             : 
     258           0 :   ret += fprintf(LogFileFP, "[%s]<%c> %s() ", timestamp(stamp),
     259           0 :                  LevelAbbr[level + 3], function);
     260             : 
     261             :   va_list ap;
     262           0 :   va_start(ap, level);
     263           0 :   const char *fmt = va_arg(ap, const char *);
     264           0 :   ret += vfprintf(LogFileFP, fmt, ap);
     265           0 :   va_end(ap);
     266             : 
     267           0 :   if (level == LL_PERROR)
     268             :   {
     269           0 :     fprintf(LogFileFP, ": %s\n", strerror(err));
     270             :   }
     271           0 :   else if (level <= LL_MESSAGE)
     272             :   {
     273           0 :     fputs("\n", LogFileFP);
     274           0 :     ret++;
     275             :   }
     276             : 
     277           0 :   return ret;
     278             : }
     279             : 
     280             : /**
     281             :  * log_queue_add - Add a LogLine to the queue
     282             :  * @param ll LogLine to add
     283             :  * @retval num Entries in the queue
     284             :  *
     285             :  * If #LogQueueMax is non-zero, the queue will be limited to this many items.
     286             :  */
     287           6 : int log_queue_add(struct LogLine *ll)
     288             : {
     289           6 :   if (!ll)
     290           2 :     return -1;
     291             : 
     292           4 :   STAILQ_INSERT_TAIL(&LogQueue, ll, entries);
     293             : 
     294           4 :   if ((LogQueueMax > 0) && (LogQueueCount >= LogQueueMax))
     295             :   {
     296           0 :     ll = STAILQ_FIRST(&LogQueue);
     297           0 :     STAILQ_REMOVE_HEAD(&LogQueue, entries);
     298           0 :     FREE(&ll->message);
     299           0 :     FREE(&ll);
     300             :   }
     301             :   else
     302             :   {
     303           4 :     LogQueueCount++;
     304             :   }
     305           4 :   return LogQueueCount;
     306             : }
     307             : 
     308             : /**
     309             :  * log_queue_set_max_size - Set a upper limit for the queue length
     310             :  * @param size New maximum queue length
     311             :  *
     312             :  * @note size of 0 means unlimited
     313             :  */
     314           2 : void log_queue_set_max_size(int size)
     315             : {
     316           2 :   if (size < 0)
     317           2 :     size = 0;
     318           2 :   LogQueueMax = size;
     319           2 : }
     320             : 
     321             : /**
     322             :  * log_queue_empty - Free the contents of the queue
     323             :  *
     324             :  * Free any log lines in the queue.
     325             :  */
     326           4 : void log_queue_empty(void)
     327             : {
     328           4 :   struct LogLine *ll = NULL;
     329           4 :   struct LogLine *tmp = NULL;
     330             : 
     331           8 :   STAILQ_FOREACH_SAFE(ll, &LogQueue, entries, tmp)
     332             :   {
     333           4 :     STAILQ_REMOVE(&LogQueue, ll, LogLine, entries);
     334           4 :     FREE(&ll->message);
     335           4 :     FREE(&ll);
     336             :   }
     337             : 
     338           4 :   LogQueueCount = 0;
     339           4 : }
     340             : 
     341             : /**
     342             :  * log_queue_flush - Replay the log queue
     343             :  * @param disp Log dispatcher - Implements ::log_dispatcher_t
     344             :  *
     345             :  * Pass all of the log entries in the queue to the log dispatcher provided.
     346             :  * The queue will be emptied afterwards.
     347             :  */
     348           2 : void log_queue_flush(log_dispatcher_t disp)
     349             : {
     350           2 :   struct LogLine *ll = NULL;
     351           2 :   STAILQ_FOREACH(ll, &LogQueue, entries)
     352             :   {
     353           0 :     disp(ll->time, ll->file, ll->line, ll->function, ll->level, "%s", ll->message);
     354             :   }
     355             : 
     356           2 :   log_queue_empty();
     357           2 : }
     358             : 
     359             : /**
     360             :  * log_queue_save - Save the contents of the queue to a temporary file
     361             :  * @param fp Open file handle
     362             :  * @retval num Lines written to the file
     363             :  *
     364             :  * The queue is written to a temporary file.  The format is:
     365             :  * * `[HH:MM:SS]<LEVEL> FORMATTED-MESSAGE`
     366             :  *
     367             :  * @note The caller should delete the file
     368             :  */
     369           2 : int log_queue_save(FILE *fp)
     370             : {
     371           2 :   if (!fp)
     372           2 :     return 0;
     373             : 
     374             :   char buf[32];
     375           0 :   int count = 0;
     376           0 :   struct LogLine *ll = NULL;
     377           0 :   STAILQ_FOREACH(ll, &LogQueue, entries)
     378             :   {
     379           0 :     mutt_date_localtime_format(buf, sizeof(buf), "%H:%M:%S", ll->time);
     380           0 :     fprintf(fp, "[%s]<%c> %s", buf, LevelAbbr[ll->level + 3], ll->message);
     381           0 :     if (ll->level <= LL_MESSAGE)
     382           0 :       fputs("\n", fp);
     383           0 :     count++;
     384             :   }
     385             : 
     386           0 :   return count;
     387             : }
     388             : 
     389             : /**
     390             :  * log_disp_queue - Save a log line to an internal queue - Implements ::log_dispatcher_t - @ingroup logging_api
     391             :  *
     392             :  * This log dispatcher saves a line of text to a queue.
     393             :  * The format string and parameters are expanded and the other parameters are
     394             :  * stored as they are.
     395             :  *
     396             :  * @sa log_queue_set_max_size(), log_queue_flush(), log_queue_empty()
     397             :  *
     398             :  * @warning Log lines are limited to 1024 bytes.
     399             :  */
     400           4 : int log_disp_queue(time_t stamp, const char *file, int line,
     401             :                    const char *function, enum LogLevel level, ...)
     402             : {
     403           4 :   char buf[1024] = { 0 };
     404           4 :   int err = errno;
     405             : 
     406             :   va_list ap;
     407           4 :   va_start(ap, level);
     408           4 :   const char *fmt = va_arg(ap, const char *);
     409           4 :   int ret = vsnprintf(buf, sizeof(buf), fmt, ap);
     410           4 :   va_end(ap);
     411             : 
     412           4 :   if (level == LL_PERROR)
     413             :   {
     414           0 :     if ((ret >= 0) && (ret < sizeof(buf)))
     415           0 :       ret += snprintf(buf + ret, sizeof(buf) - ret, ": %s", strerror(err));
     416           0 :     level = LL_ERROR;
     417             :   }
     418             : 
     419           4 :   struct LogLine *ll = mutt_mem_calloc(1, sizeof(*ll));
     420           4 :   ll->time = (stamp != 0) ? stamp : mutt_date_epoch();
     421           4 :   ll->file = file;
     422           4 :   ll->line = line;
     423           4 :   ll->function = function;
     424           4 :   ll->level = level;
     425           4 :   ll->message = mutt_str_dup(buf);
     426             : 
     427           4 :   log_queue_add(ll);
     428             : 
     429           4 :   return ret;
     430             : }
     431             : 
     432             : /**
     433             :  * log_disp_terminal - Save a log line to the terminal - Implements ::log_dispatcher_t - @ingroup logging_api
     434             :  *
     435             :  * This log dispatcher saves a line of text to the terminal.
     436             :  * The format is:
     437             :  * * `[TIMESTAMP]<LEVEL> FUNCTION() FORMATTED-MESSAGE`
     438             :  *
     439             :  * @note The output will be coloured using ANSI escape sequences,
     440             :  *       unless the output is redirected.
     441             :  */
     442        4450 : int log_disp_terminal(time_t stamp, const char *file, int line,
     443             :                       const char *function, enum LogLevel level, ...)
     444             : {
     445             :   char buf[1024];
     446             : 
     447             :   va_list ap;
     448        4450 :   va_start(ap, level);
     449        4450 :   const char *fmt = va_arg(ap, const char *);
     450        4450 :   int ret = vsnprintf(buf, sizeof(buf), fmt, ap);
     451        4450 :   va_end(ap);
     452             : 
     453        4450 :   log_disp_file(stamp, file, line, function, level, "%s", buf);
     454             : 
     455        4450 :   if ((level < LL_PERROR) || (level > LL_MESSAGE))
     456        4446 :     return 0;
     457             : 
     458           4 :   FILE *fp = (level < LL_MESSAGE) ? stderr : stdout;
     459           4 :   int err = errno;
     460           4 :   int colour = 0;
     461           4 :   bool tty = (isatty(fileno(fp)) == 1);
     462             : 
     463           4 :   if (tty)
     464             :   {
     465           4 :     switch (level)
     466             :     {
     467           0 :       case LL_PERROR:
     468             :       case LL_ERROR:
     469           0 :         colour = 31;
     470           0 :         break;
     471           0 :       case LL_WARNING:
     472           0 :         colour = 33;
     473           0 :         break;
     474           4 :       case LL_MESSAGE:
     475             :         // colour = 36;
     476           4 :         break;
     477           0 :       default:
     478           0 :         break;
     479             :     }
     480             :   }
     481             : 
     482           4 :   if (colour > 0)
     483           0 :     ret += fprintf(fp, "\033[1;%dm", colour); // Escape
     484             : 
     485           4 :   fputs(buf, fp);
     486             : 
     487           4 :   if (level == LL_PERROR)
     488           0 :     ret += fprintf(fp, ": %s", strerror(err));
     489             : 
     490           4 :   if (colour > 0)
     491           0 :     ret += fprintf(fp, "\033[0m"); // Escape
     492             : 
     493           4 :   ret += fprintf(fp, "\n");
     494             : 
     495           4 :   return ret;
     496             : }
     497             : 
     498             : /**
     499             :  * log_disp_null - Discard log lines - Implements ::log_dispatcher_t - @ingroup logging_api
     500             :  */
     501          22 : int log_disp_null(time_t stamp, const char *file, int line,
     502             :                   const char *function, enum LogLevel level, ...)
     503             : {
     504          22 :   return 0;
     505             : }

Generated by: LCOV version 1.14