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