1 /*
2 *
3 * CLEX File Manager
4 *
5 * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6 *
7 * CLEX is free software without warranty of any kind; see the
8 * GNU General Public License as set out in the "COPYING" document
9 * which accompanies the CLEX File Manager package.
10 *
11 * CLEX can be downloaded from http://www.clex.sk
12 *
13 */
14
15 #include "clexheaders.h"
16
17 #include <errno.h> /* errno */
18 #include <stdarg.h> /* va_list */
19 #include <stdio.h> /* fprintf() */
20 #include <string.h> /* strerror() */
21 #include <time.h> /* strftime() */
22
23 #include "log.h"
24
25 #include "control.h" /* err_exit() */
26 #include "inout.h" /* win_sethelp() */
27 #include "match.h" /* match_substr() */
28 #include "mbwstring.h" /* usw_convert2w() */
29 #include "panel.h" /* panel_adjust() */
30 #include "ustringutil.h" /* us_vprintf() */
31
32 static FILE *logfp = 0;
33 static LOG_ENTRY logbook[LOG_LINES]; /* circular buffer */
34 static int base = 0, cnt = 0;
35
36 static int
find_nl(const char * str)37 find_nl(const char *str)
38 {
39 const char *nl;
40
41 nl = strchr(str,'\n');
42 return nl ? nl - str : -1;
43 }
44
45 static const char *
strip_nl(const char * str)46 strip_nl(const char *str)
47 {
48 static USTRING nonl = UNULL;
49 char *p;
50 int nl;
51
52 if ((nl = find_nl(str)) < 0)
53 return str;
54
55 p = us_copy(&nonl,str);
56 do {
57 p += nl;
58 *p++ = ' ';
59 nl = find_nl(p);
60 } while (nl >= 0);
61 return USTR(nonl);
62 }
63
64 static void
append_record(const char * timestamp,const char * levelstr,const char * msg)65 append_record(const char *timestamp, const char *levelstr, const char *msg)
66 {
67 fprintf(logfp,"%s %-15s %s\n",timestamp,levelstr,msg);
68 fflush(logfp);
69 }
70
71 void
logfile_open(const char * logfile)72 logfile_open(const char *logfile)
73 {
74 int i;
75 LOG_ENTRY *plog;
76
77 if ( (logfp = fopen(logfile,"a")) == 0)
78 msgout(MSG_W,"Could not open the logfile \"%s\" (%s)",logfile,strerror(errno));
79 else {
80 /* write records collected so far */
81 for (i = 0; i < cnt; i++) {
82 plog = logbook + (base + i) % LOG_LINES;
83 append_record(plog->timestamp,plog->levelstr,convert2mb(USTR(plog->msg)));
84 }
85 msgout(MSG_DEBUG,"Logfile: \"%s\"",logfile);
86 }
87 }
88
89 /* this is a cleanup function (see err_exit() in control.c) */
90 void
logfile_close(void)91 logfile_close(void)
92 {
93 if (logfp) {
94 fclose(logfp);
95 logfp = 0;
96 }
97 }
98
99 static void
store_timestamp(char * dst)100 store_timestamp(char *dst)
101 {
102 static FLAG format_ok = 1;
103 time_t now;
104
105 now = time(0);
106 if (format_ok && strftime(dst,TIMESTAMP_STR,"%c",localtime(&now)) == 0) {
107 format_ok = 0;
108 msgout(MSG_NOTICE,"LOG: Using YYYY-MM-DD HH:MM:SS date/time format "
109 "because the default format (defined by locale) is too long");
110 }
111 if (!format_ok)
112 strftime(dst,TIMESTAMP_STR,"%Y-%m-%d %H:%M:%S",localtime(&now));
113 }
114
115 static void
log_record(int level,const char * logmsg)116 log_record(int level, const char *logmsg)
117 {
118 static struct {
119 const char *str; /* should not exceed 15 chars */
120 FLAG panel; /* insert into panel + append to the logfile */
121 FLAG screen; /* display on the screen */
122 FLAG iswarning; /* is a warning */
123 } levdef [_MSG_TOTAL_] = {
124 { /* heading */ 0,0,0,0 },
125 { "DEBUG", 1, 0, 0 },
126 { "NOTICE", 1, 0, 1 },
127 { "AUDIT", 1, 0, 0 },
128 { "INFO", 1, 1, 0 },
129 { "INFO", 0, 1, 0 },
130 { "WARNING", 1, 1, 1 },
131 { "WARNING", 0, 1, 1 }
132 };
133 static USTRING heading_buff = UNULL;
134 static int notify_hint = 2; /* display the hint twice */
135 USTRINGW wmsg_buff;
136 const wchar_t *wmsg;
137 const char *msg, *heading;
138 LOG_ENTRY *plog;
139 FLAG notify = 0;
140 int i;
141
142 /* modifiers */
143 if ((level & MSG_NOTIFY) && notify_hint)
144 notify = 1;
145 level &= MSG_MASK;
146
147 if (level < 0 || level >= _MSG_TOTAL_)
148 err_exit("BUG: invalid message priority level %d",level);
149
150 if (level == MSG_HEADING) {
151 /* if 'msg' is a null ptr, the stored heading is invalidated */
152 us_copy(&heading_buff,logmsg);
153 return;
154 }
155 if (levdef[level].iswarning && (heading = USTR(heading_buff)) && logmsg != heading) {
156 /* emit the heading string first */
157 log_record(level,heading);
158 us_reset(&heading_buff);
159 }
160
161 msg = strip_nl(logmsg);
162 US_INIT(wmsg_buff);
163 wmsg = usw_convert2w(msg,&wmsg_buff);
164
165 /* append it to the log panel and log file */
166 if (levdef[level].panel) {
167 if (cnt < LOG_LINES)
168 /* adding new record */
169 plog = logbook + (base + cnt++) % LOG_LINES;
170 else {
171 /* replacing the oldest one */
172 plog = logbook + base;
173 base = (base + 1) % LOG_LINES;
174 if (plog->cols == panel_log.maxcols)
175 /* replacing the longest message -> must recalculate the max */
176 panel_log.maxcols = 0;
177 }
178
179 plog->level = level;
180 plog->levelstr = levdef[level].str;
181 usw_copy(&plog->msg,wmsg);
182 store_timestamp(plog->timestamp);
183 plog->cols = wc_cols(wmsg,0,-1);
184
185 /* max length */
186 if (cnt < LOG_LINES || panel_log.maxcols > 0) {
187 if (plog->cols > panel_log.maxcols)
188 panel_log.maxcols = plog->cols;
189 }
190 else
191 for (i = 0; i < LOG_LINES; i++)
192 if (logbook[i].cols > panel_log.maxcols)
193 panel_log.maxcols = logbook[i].cols;
194
195 /* live view */
196 if (get_current_mode() == MODE_LOG) {
197 log_panel_data();
198 panel->curs = panel->cnt - 1;
199 pan_adjust(panel);
200 win_panel();
201 }
202
203 if (logfp)
204 append_record(plog->timestamp,plog->levelstr,msg);
205 }
206
207 /* display it on the screen */
208 if (levdef[level].screen) {
209 if (disp_data.curses)
210 win_sethelp(levdef[level].iswarning ? HELPMSG_WARNING : HELPMSG_INFO,wmsg);
211 else {
212 puts(logmsg); /* original message possibly with newlines */
213 fflush(stdout);
214 disp_data.wait = 1;
215 }
216 if (notify) {
217 notify_hint--; /* display this hint only N times */
218 win_sethelp(HELPMSG_TMP,L"alt-N = notification panel");
219 }
220 }
221 }
222
223 void
vmsgout(int level,const char * format,va_list argptr)224 vmsgout(int level, const char *format, va_list argptr)
225 {
226 static USTRING buff = UNULL;
227
228 us_vprintf(&buff,format,argptr);
229 log_record(level,USTR(buff));
230 }
231
232 void
msgout(int level,const char * format,...)233 msgout(int level, const char *format, ...)
234 {
235 va_list argptr;
236
237 if (format && strchr(format,'%')) {
238 va_start(argptr,format);
239 vmsgout(level,format,argptr);
240 va_end(argptr);
241 }
242 else
243 log_record(level,format);
244 }
245
246 void
log_panel_data(void)247 log_panel_data(void)
248 {
249 int i, j;
250 LOG_ENTRY *plog, *curs;
251
252 curs = VALID_CURSOR(panel_log.pd) ? panel_log.line[panel_log.pd->curs] : 0;
253 if (panel_log.pd->filtering)
254 match_substr_set(panel_log.pd->filter->line);
255
256 for (i = j = 0; i < cnt; i++) {
257 plog = logbook + (base + i) % LOG_LINES;
258 if (plog == curs)
259 panel_log.pd->curs = j;
260 if (panel_log.pd->filtering && !match_substr(USTR(plog->msg)))
261 continue;
262 panel_log.line[j++] = plog;
263 }
264 panel_log.pd->cnt = j;
265 }
266
267 int
log_prepare(void)268 log_prepare(void)
269 {
270 panel_log.pd->filtering = 0;
271 panel_log.pd->curs = -1;
272 log_panel_data();
273 panel_log.pd->top = panel_user.pd->min;
274 panel_log.pd->curs = panel_log.pd->cnt - 1;
275
276 panel = panel_log.pd;
277 textline = 0;
278 return 0;
279 }
280
281 #define SCROLL_UNIT 12
282
283 void
cx_log_right(void)284 cx_log_right(void)
285 {
286 if (panel_log.scroll < panel_log.maxcols - disp_data.scrcols / 2) {
287 panel_log.scroll += SCROLL_UNIT;
288 win_panel();
289 }
290 }
291
292 void
cx_log_left(void)293 cx_log_left(void)
294 {
295 if (panel_log.scroll >= SCROLL_UNIT) {
296 panel_log.scroll -= SCROLL_UNIT;
297 win_panel();
298 }
299 }
300
301 void
cx_log_mark(void)302 cx_log_mark(void)
303 {
304 msgout(MSG_DEBUG,"-- mark --");
305 }
306
307 void
cx_log_home(void)308 cx_log_home(void)
309 {
310 panel_log.scroll = 0;
311 win_panel();
312 }
313