Coverage Report

Created: 2025-03-11 06:49

/src/neomutt/mutt_logging.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * NeoMutt Logging
4
 *
5
 * @authors
6
 * Copyright (C) 2018-2023 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
8
 *
9
 * @copyright
10
 * This program is free software: you can redistribute it and/or modify it under
11
 * the terms of the GNU General Public License as published by the Free Software
12
 * Foundation, either version 2 of the License, or (at your option) any later
13
 * version.
14
 *
15
 * This program is distributed in the hope that it will be useful, but WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18
 * details.
19
 *
20
 * You should have received a copy of the GNU General Public License along with
21
 * this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
/**
25
 * @page neo_mutt_logging NeoMutt Logging
26
 *
27
 * NeoMutt Logging
28
 */
29
30
#include "config.h"
31
#include <errno.h>
32
#include <stdarg.h>
33
#include <stdbool.h>
34
#include <stdint.h>
35
#include <stdio.h>
36
#include <string.h>
37
#include <time.h>
38
#include "mutt/lib.h"
39
#include "config/lib.h"
40
#include "core/lib.h"
41
#include "gui/lib.h"
42
#include "mutt_logging.h"
43
#include "color/lib.h"
44
#include "globals.h"
45
#include "muttlib.h"
46
47
static uint64_t LastError = 0; ///< Time of the last error message (in milliseconds since the Unix epoch)
48
49
static char *CurrentFile = NULL; ///< The previous log file name
50
static const int NumOfLogs = 5;  ///< How many log files to rotate
51
52
0
#define S_TO_MS 1000L
53
54
/**
55
 * error_pause - Wait for an error message to be read
56
 *
57
 * If '$sleep_time' seconds hasn't elapsed since LastError, then wait
58
 */
59
static void error_pause(void)
60
0
{
61
0
  const short c_sleep_time = cs_subset_number(NeoMutt->sub, "sleep_time");
62
0
  const uint64_t elapsed = mutt_date_now_ms() - LastError;
63
0
  const uint64_t sleep = c_sleep_time * S_TO_MS;
64
0
  if ((LastError == 0) || (elapsed >= sleep))
65
0
    return;
66
67
0
  mutt_refresh();
68
0
  mutt_date_sleep_ms(sleep - elapsed);
69
0
}
70
71
/**
72
 * mutt_clear_error - Clear the message line (bottom line of screen)
73
 */
74
void mutt_clear_error(void)
75
0
{
76
  /* Make sure the error message has had time to be read */
77
0
  if (OptMsgErr)
78
0
    error_pause();
79
80
0
  ErrorBufMessage = false;
81
0
  if (!OptNoCurses)
82
0
    msgwin_clear_text(NULL);
83
0
}
84
85
/**
86
 * log_disp_curses - Display a log line in the message line - Implements ::log_dispatcher_t - @ingroup logging_api
87
 */
88
int log_disp_curses(time_t stamp, const char *file, int line, const char *function,
89
                    enum LogLevel level, const char *format, ...)
90
0
{
91
0
  const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
92
0
  if (level > c_debug_level)
93
0
    return 0;
94
95
0
  char buf[LOG_LINE_MAX_LEN] = { 0 };
96
97
0
  va_list ap;
98
0
  va_start(ap, format);
99
0
  int rc = vsnprintf(buf, sizeof(buf), format, ap);
100
0
  va_end(ap);
101
102
0
  if ((level == LL_PERROR) && (rc >= 0) && (rc < sizeof(buf)))
103
0
  {
104
0
    char *buf2 = buf + rc;
105
0
    int len = sizeof(buf) - rc;
106
0
    const char *p = strerror(errno);
107
0
    if (!p)
108
0
      p = _("unknown error");
109
110
0
    rc += snprintf(buf2, len, ": %s (errno = %d)", p, errno);
111
0
  }
112
113
0
  const bool dupe = (mutt_str_equal(buf, ErrorBuf));
114
0
  if (!dupe)
115
0
  {
116
    /* Only log unique messages */
117
0
    log_disp_file(stamp, file, line, function, level, "%s", buf);
118
0
    if (stamp == 0)
119
0
      log_disp_queue(stamp, file, line, function, level, "%s", buf);
120
0
  }
121
122
  /* Don't display debugging message on screen */
123
0
  if (level > LL_MESSAGE)
124
0
    return 0;
125
126
  /* Only pause if this is a message following an error */
127
0
  if ((level > LL_ERROR) && OptMsgErr && !dupe)
128
0
    error_pause();
129
130
0
  mutt_str_copy(ErrorBuf, buf, sizeof(ErrorBuf));
131
0
  ErrorBufMessage = true;
132
133
0
  if (!OptKeepQuiet)
134
0
  {
135
0
    enum ColorId cid = MT_COLOR_NORMAL;
136
0
    switch (level)
137
0
    {
138
0
      case LL_ERROR:
139
0
        mutt_beep(false);
140
0
        cid = MT_COLOR_ERROR;
141
0
        break;
142
0
      case LL_WARNING:
143
0
        cid = MT_COLOR_WARNING;
144
0
        break;
145
0
      default:
146
0
        cid = MT_COLOR_MESSAGE;
147
0
        break;
148
0
    }
149
150
0
    msgwin_set_text(NULL, ErrorBuf, cid);
151
0
  }
152
153
0
  if ((level <= LL_ERROR) && !dupe)
154
0
  {
155
0
    OptMsgErr = true;
156
0
    LastError = mutt_date_now_ms();
157
0
  }
158
0
  else
159
0
  {
160
0
    OptMsgErr = false;
161
0
    LastError = 0;
162
0
  }
163
164
0
  window_redraw(msgwin_get_window());
165
0
  return rc;
166
0
}
167
168
/**
169
 * mutt_log_prep - Prepare to log
170
 */
171
void mutt_log_prep(void)
172
0
{
173
0
  char ver[64] = { 0 };
174
0
  snprintf(ver, sizeof(ver), "-%s%s", PACKAGE_VERSION, GitVer);
175
0
  log_file_set_version(ver);
176
0
}
177
178
/**
179
 * mutt_log_stop - Close the log file
180
 */
181
void mutt_log_stop(void)
182
0
{
183
0
  log_file_close(false);
184
0
  FREE(&CurrentFile);
185
0
}
186
187
/**
188
 * mutt_log_set_file - Change the logging file
189
 * @param file Name to use
190
 * @retval  0 Success, file opened
191
 * @retval -1 Error, see errno
192
 *
193
 * Close the old log, rotate the new logs and open the new log.
194
 */
195
int mutt_log_set_file(const char *file)
196
0
{
197
0
  const char *const c_debug_file = cs_subset_path(NeoMutt->sub, "debug_file");
198
0
  if (!mutt_str_equal(CurrentFile, c_debug_file))
199
0
  {
200
0
    struct Buffer *expanded = buf_pool_get();
201
0
    buf_addstr(expanded, c_debug_file);
202
0
    buf_expand_path(expanded);
203
204
0
    const char *name = mutt_file_rotate(buf_string(expanded), NumOfLogs);
205
0
    buf_pool_release(&expanded);
206
0
    if (!name)
207
0
      return -1;
208
209
0
    log_file_set_filename(name, false);
210
0
    FREE(&name);
211
0
    mutt_str_replace(&CurrentFile, c_debug_file);
212
0
  }
213
214
0
  cs_subset_str_string_set(NeoMutt->sub, "debug_file", file, NULL);
215
216
0
  return 0;
217
0
}
218
219
/**
220
 * mutt_log_set_level - Change the logging level
221
 * @param level Logging level
222
 * @param verbose If true, then log the event
223
 * @retval  0 Success
224
 * @retval -1 Error, level is out of range
225
 */
226
int mutt_log_set_level(enum LogLevel level, bool verbose)
227
0
{
228
0
  if (!CurrentFile)
229
0
  {
230
0
    const char *const c_debug_file = cs_subset_path(NeoMutt->sub, "debug_file");
231
0
    mutt_log_set_file(c_debug_file);
232
0
  }
233
234
0
  if (log_file_set_level(level, verbose) != 0)
235
0
    return -1;
236
237
0
  cs_subset_str_native_set(NeoMutt->sub, "debug_level", level, NULL);
238
0
  return 0;
239
0
}
240
241
/**
242
 * mutt_log_start - Enable file logging
243
 * @retval  0 Success, or already running
244
 * @retval -1 Failed to start
245
 *
246
 * This also handles file rotation.
247
 */
248
int mutt_log_start(void)
249
0
{
250
0
  const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
251
0
  if (c_debug_level < 1)
252
0
    return 0;
253
254
0
  if (log_file_running())
255
0
    return 0;
256
257
0
  const char *const c_debug_file = cs_subset_path(NeoMutt->sub, "debug_file");
258
0
  mutt_log_set_file(c_debug_file);
259
260
  /* This will trigger the file creation */
261
0
  if (log_file_set_level(c_debug_level, true) < 0)
262
0
    return -1;
263
264
0
  return 0;
265
0
}
266
267
/**
268
 * level_validator - Validate the "debug_level" config variable - Implements ConfigDef::validator() - @ingroup cfg_def_validator
269
 */
270
int level_validator(const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
271
0
{
272
0
  if ((value < 0) || (value >= LL_MAX))
273
0
  {
274
0
    buf_printf(err, _("Invalid value for option %s: %ld"), cdef->name, (long) value);
275
0
    return CSR_ERR_INVALID;
276
0
  }
277
278
0
  return CSR_SUCCESS;
279
0
}
280
281
/**
282
 * main_log_observer - Notification that a Config Variable has changed - Implements ::observer_t - @ingroup observer_api
283
 */
284
int main_log_observer(struct NotifyCallback *nc)
285
0
{
286
0
  if (nc->event_type != NT_CONFIG)
287
0
    return 0;
288
0
  if (!nc->event_data)
289
0
    return -1;
290
291
0
  struct EventConfig *ev_c = nc->event_data;
292
293
0
  if (mutt_str_equal(ev_c->name, "debug_file"))
294
0
  {
295
0
    const char *const c_debug_file = cs_subset_path(NeoMutt->sub, "debug_file");
296
0
    mutt_log_set_file(c_debug_file);
297
0
  }
298
0
  else if (mutt_str_equal(ev_c->name, "debug_level"))
299
0
  {
300
0
    const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
301
0
    mutt_log_set_level(c_debug_level, true);
302
0
  }
303
0
  else
304
0
  {
305
0
    return 0;
306
0
  }
307
308
0
  mutt_debug(LL_DEBUG5, "log done\n");
309
0
  return 0;
310
0
}