1 /**********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
13
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17
18 #include <signal.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <string.h>
22
23 /* utility */
24 #include "fciconv.h"
25 #include "fcintl.h"
26 #include "fcthread.h"
27 #include "mem.h"
28 #include "shared.h"
29 #include "support.h"
30
31 #include "log.h"
32
33 #define MAX_LEN_LOG_LINE 512
34
35 static void log_write(FILE *fs, enum log_level level, bool print_from_where,
36 const char *where, const char *message);
37 static void log_real(enum log_level level, bool print_from_where,
38 const char *where, const char *msg);
39
40 static char *log_filename = NULL;
41 static log_pre_callback_fn log_pre_callback = log_real;
42 static log_callback_fn log_callback = NULL;
43 static log_prefix_fn log_prefix = NULL;
44
45 static fc_mutex logfile_mutex;
46
47 #ifdef DEBUG
48 static const enum log_level max_level = LOG_DEBUG;
49 #else
50 static const enum log_level max_level = LOG_VERBOSE;
51 #endif /* DEBUG */
52
53 static enum log_level fc_log_level = LOG_NORMAL;
54 static int fc_fatal_assertions = -1;
55
56 #ifdef DEBUG
57 struct log_fileinfo {
58 char *name;
59 enum log_level level;
60 unsigned int min;
61 unsigned int max;
62 };
63 static int log_num_files = 0;
64 static struct log_fileinfo *log_files = NULL;
65 #endif /* DEBUG */
66
67 /* A helper variable to indicate that there is no log message. The '%s' is
68 * added to use it as format string as well as the argument. */
69 const char *nologmsg = "nologmsg:%s";
70
71 /**************************************************************************
72 level_str should be either "0", "1", "2", "3", "4" or
73 "4:filename" or "4:file1:file2" or "4:filename,100,200" etc
74
75 If everything goes ok, returns TRUE. If there was a parsing problem,
76 prints to stderr, and returns FALSE.
77
78 Return in ret_level the requested level only if level_str is a simple
79 number (like "0", "1", "2").
80
81 Also sets up the log_files data structure. Does _not_ set fc_log_level.
82 **************************************************************************/
log_parse_level_str(const char * level_str,enum log_level * ret_level)83 bool log_parse_level_str(const char *level_str, enum log_level *ret_level)
84 {
85 const char *c;
86 int n = 0; /* number of filenames */
87 unsigned int level;
88 #ifdef DEBUG
89 const char *tok;
90 int i;
91 char *dupled;
92 bool ret = TRUE;
93 #endif /* DEBUG */
94
95 c = level_str;
96 n = 0;
97 while ((c = strchr(c, ':'))) {
98 c++;
99 n++;
100 }
101 if (n == 0) {
102 /* Global log level. */
103 if (!str_to_uint(level_str, &level)) {
104 fc_fprintf(stderr, _("Bad log level \"%s\".\n"), level_str);
105 return FALSE;
106 }
107 if (level >= LOG_FATAL && level <= max_level) {
108 if (NULL != ret_level) {
109 *ret_level = level;
110 }
111 return TRUE;
112 } else {
113 fc_fprintf(stderr, _("Bad log level %d in \"%s\".\n"),
114 level, level_str);
115 #ifndef DEBUG
116 if (level == max_level + 1) {
117 fc_fprintf(stderr, _("Freeciv must be compiled with the DEBUG flag"
118 " to use debug level %d.\n"), max_level + 1);
119 }
120 #endif /* DEBUG */
121 return FALSE;
122 }
123 }
124
125 #ifdef DEBUG
126 c = level_str;
127 level = c[0] - '0';
128 if (c[1] == ':') {
129 if (level < LOG_FATAL || level > max_level) {
130 fc_fprintf(stderr, _("Bad log level %d in \"%s\".\n"),
131 level, level_str);
132 return FALSE;
133 }
134 } else {
135 fc_fprintf(stderr, _("Badly formed log level argument \"%s\".\n"),
136 level_str);
137 return FALSE;
138 }
139 i = log_num_files;
140 log_num_files += n;
141 log_files = fc_realloc(log_files,
142 log_num_files * sizeof(struct log_fileinfo));
143
144 dupled = fc_strdup(c + 2);
145 tok = strtok(dupled, ":");
146
147 if (!tok) {
148 fc_fprintf(stderr, _("Badly formed log level argument \"%s\".\n"),
149 level_str);
150 ret = FALSE;
151 goto out;
152 }
153 do {
154 struct log_fileinfo *pfile = log_files + i;
155 char *d = strchr(tok, ',');
156
157 pfile->min = 0;
158 pfile->max = 0;
159 pfile->level = level;
160 if (d) {
161 char *pc = d + 1;
162
163 d[0] = '\0';
164 d = strchr(d + 1, ',');
165 if (d && *pc != '\0' && d[1] != '\0') {
166 d[0] = '\0';
167 if (!str_to_uint(pc, &pfile->min)) {
168 fc_fprintf(stderr, _("Not an unsigned integer: '%s'\n"), pc);
169 ret = FALSE;
170 goto out;
171 }
172 if (!str_to_uint(d + 1, &pfile->max)) {
173 fc_fprintf(stderr, _("Not an unsigned integer: '%s'\n"), d + 1);
174 ret = FALSE;
175 goto out;
176 }
177 }
178 }
179 if (strlen(tok) == 0) {
180 fc_fprintf(stderr, _("Empty filename in log level argument \"%s\".\n"),
181 level_str);
182 ret = FALSE;
183 goto out;
184 }
185 pfile->name = fc_strdup(tok);
186 i++;
187 tok = strtok(NULL, ":");
188 } while(tok);
189
190 if (i != log_num_files) {
191 fc_fprintf(stderr, _("Badly formed log level argument \"%s\".\n"),
192 level_str);
193 ret = FALSE;
194 goto out;
195 }
196
197 out:
198 free(dupled);
199 return ret;
200 #else /* DEBUG */
201 fc_fprintf(stderr, _("Freeciv must be compiled with the DEBUG flag "
202 "to use advanced log levels based on files.\n"));
203 return FALSE;
204 #endif /* DEBUG */
205 }
206
207 /**************************************************************************
208 Initialise the log module. Either 'filename' or 'callback' may be NULL.
209 If both are NULL, print to stderr. If both are non-NULL, both callback,
210 and fprintf to file. Pass -1 for fatal_assertions to don't raise any
211 signal on failed assertion.
212 **************************************************************************/
log_init(const char * filename,enum log_level initial_level,log_callback_fn callback,log_prefix_fn prefix,int fatal_assertions)213 void log_init(const char *filename, enum log_level initial_level,
214 log_callback_fn callback, log_prefix_fn prefix,
215 int fatal_assertions)
216 {
217 fc_log_level = initial_level;
218 if (log_filename) {
219 free(log_filename);
220 log_filename = NULL;
221 }
222 if (filename && strlen(filename) > 0) {
223 log_filename = fc_strdup(filename);
224 } else {
225 log_filename = NULL;
226 }
227 log_callback = callback;
228 log_prefix = prefix;
229 fc_fatal_assertions = fatal_assertions;
230 fc_init_mutex(&logfile_mutex);
231 log_verbose("log started");
232 log_debug("LOG_DEBUG test");
233 }
234
235 /**************************************************************************
236 Deinitialize logging module.
237 **************************************************************************/
log_close(void)238 void log_close(void)
239 {
240 fc_destroy_mutex(&logfile_mutex);
241 }
242
243 /*****************************************************************************
244 Adjust the log preparation callback function.
245 *****************************************************************************/
log_set_pre_callback(log_pre_callback_fn precallback)246 log_pre_callback_fn log_set_pre_callback(log_pre_callback_fn precallback)
247 {
248 log_pre_callback_fn old = log_pre_callback;
249
250 log_pre_callback = precallback;
251
252 return old;
253 }
254
255 /*****************************************************************************
256 Adjust the callback function after initial log_init().
257 *****************************************************************************/
log_set_callback(log_callback_fn callback)258 log_callback_fn log_set_callback(log_callback_fn callback)
259 {
260 log_callback_fn old = log_callback;
261
262 log_callback = callback;
263
264 return old;
265 }
266
267 /**************************************************************************
268 Adjust the prefix callback function after initial log_init().
269 **************************************************************************/
log_set_prefix(log_prefix_fn prefix)270 log_prefix_fn log_set_prefix(log_prefix_fn prefix)
271 {
272 log_prefix_fn old = log_prefix;
273
274 log_prefix = prefix;
275
276 return old;
277 }
278
279 /**************************************************************************
280 Adjust the logging level after initial log_init().
281 **************************************************************************/
log_set_level(enum log_level level)282 void log_set_level(enum log_level level)
283 {
284 fc_log_level = level;
285 }
286
287 /**************************************************************************
288 Returns the current log level.
289 **************************************************************************/
log_get_level(void)290 enum log_level log_get_level(void)
291 {
292 return fc_log_level;
293 }
294
295 #ifdef DEBUG
296 /**************************************************************************
297 Returns wether we should do an output for this level, in this file,
298 at this line.
299 **************************************************************************/
log_do_output_for_level_at_location(enum log_level level,const char * file,int line)300 bool log_do_output_for_level_at_location(enum log_level level,
301 const char *file, int line)
302 {
303 struct log_fileinfo *pfile;
304 int i;
305
306 for (i = 0, pfile = log_files; i < log_num_files; i++, pfile++) {
307 if (pfile->level >= level
308 && 0 == strcmp(pfile->name, file)
309 && ((0 == pfile->min && 0 == pfile->max)
310 || (pfile->min <= line && pfile->max >= line))) {
311 return TRUE;
312 }
313 }
314 return (fc_log_level >= level);
315 }
316 #endif /* DEBUG */
317
318 /*****************************************************************************
319 Unconditionally print a simple string.
320 Let the callback do its own level formatting and add a '\n' if it wants.
321 *****************************************************************************/
log_write(FILE * fs,enum log_level level,bool print_from_where,const char * where,const char * message)322 static void log_write(FILE *fs, enum log_level level, bool print_from_where,
323 const char *where, const char *message)
324 {
325 if (log_filename || (!log_callback)) {
326 char prefix[128];
327
328 if (log_prefix) {
329 /* Get the log prefix. */
330 fc_snprintf(prefix, sizeof(prefix), "[%s] ", log_prefix());
331 } else {
332 prefix[0] = '\0';
333 }
334
335 if (log_filename || (print_from_where && where)) {
336 fc_fprintf(fs, "%d: %s%s%s\n", level, prefix, where, message);
337 } else {
338 fc_fprintf(fs, "%d: %s%s\n", level, prefix, message);
339 }
340 fflush(fs);
341 }
342
343 if (log_callback) {
344 if (print_from_where) {
345 char buf[MAX_LEN_LOG_LINE];
346
347 fc_snprintf(buf, sizeof(buf), "%s%s", where, message);
348 log_callback(level, buf, log_filename != NULL);
349 } else {
350 log_callback(level, message, log_filename != NULL);
351 }
352 }
353 }
354
355 /*****************************************************************************
356 Unconditionally print a log message. This function is usually protected
357 by do_log_for().
358 *****************************************************************************/
vdo_log(const char * file,const char * function,int line,bool print_from_where,enum log_level level,char * buf,int buflen,const char * message,va_list args)359 void vdo_log(const char *file, const char *function, int line,
360 bool print_from_where, enum log_level level,
361 char *buf, int buflen, const char *message, va_list args)
362 {
363 char buf_where[MAX_LEN_LOG_LINE];
364
365 /* There used to be check against recursive logging here, but
366 * the way it worked prevented any kind of simultaneous logging,
367 * not just recursive. Multiple threads should be able to log
368 * simultaneously. */
369
370 fc_vsnprintf(buf, buflen, message, args);
371 fc_snprintf(buf_where, sizeof(buf_where), "in %s() [%s::%d]: ",
372 function, file, line);
373
374 /* In the default configuration log_pre_callback is equal to log_real(). */
375 if (log_pre_callback) {
376 log_pre_callback(level, print_from_where, buf_where, buf);
377 }
378 }
379
380 /*****************************************************************************
381 Really print a log message.
382 For repeat message, may wait and print instead "last message repeated ..."
383 at some later time.
384 Calls log_callback if non-null, else prints to stderr.
385 *****************************************************************************/
log_real(enum log_level level,bool print_from_where,const char * where,const char * msg)386 static void log_real(enum log_level level, bool print_from_where,
387 const char *where, const char *msg)
388 {
389 static char last_msg[MAX_LEN_LOG_LINE] = "";
390 static unsigned int repeated = 0; /* total times current message repeated */
391 static unsigned int next = 2; /* next total to print update */
392 static unsigned int prev = 0; /* total on last update */
393 /* only count as repeat if same level */
394 static enum log_level prev_level = -1;
395 char buf[MAX_LEN_LOG_LINE];
396 FILE *fs;
397
398 if (log_filename) {
399 fc_allocate_mutex(&logfile_mutex);
400 if (!(fs = fc_fopen(log_filename, "a"))) {
401 fc_fprintf(stderr,
402 _("Couldn't open logfile: %s for appending \"%s\".\n"),
403 log_filename, msg);
404 exit(EXIT_FAILURE);
405 }
406 } else {
407 fs = stderr;
408 }
409
410 if (level == prev_level && 0 == strncmp(msg, last_msg,
411 MAX_LEN_LOG_LINE - 1)){
412 repeated++;
413 if (repeated == next) {
414 fc_snprintf(buf, sizeof(buf),
415 PL_("last message repeated %d time",
416 "last message repeated %d times",
417 repeated-prev), repeated-prev);
418 if (repeated > 2) {
419 cat_snprintf(buf, sizeof(buf),
420 /* TRANS: preserve leading space */
421 PL_(" (total %d repeat)",
422 " (total %d repeats)",
423 repeated), repeated);
424 }
425 log_write(fs, prev_level, print_from_where, where, buf);
426 prev = repeated;
427 next *= 2;
428 }
429 } else {
430 if (repeated > 0 && repeated != prev) {
431 if (repeated == 1) {
432 /* just repeat the previous message: */
433 log_write(fs, prev_level, print_from_where, where, last_msg);
434 } else {
435 fc_snprintf(buf, sizeof(buf),
436 PL_("last message repeated %d time",
437 "last message repeated %d times",
438 repeated - prev), repeated - prev);
439 if (repeated > 2) {
440 cat_snprintf(buf, sizeof(buf),
441 PL_(" (total %d repeat)", " (total %d repeats)",
442 repeated), repeated);
443 }
444 log_write(fs, prev_level, print_from_where, where, buf);
445 }
446 }
447 prev_level = level;
448 repeated = 0;
449 next = 2;
450 prev = 0;
451 log_write(fs, level, print_from_where, where, msg);
452 }
453 /* Save last message. */
454 sz_strlcpy(last_msg, msg);
455
456 fflush(fs);
457 if (log_filename) {
458 fclose(fs);
459 fc_release_mutex(&logfile_mutex);
460 }
461 }
462
463 /**************************************************************************
464 Unconditionally print a log message. This function is usually protected
465 by do_log_for().
466 For repeat message, may wait and print instead
467 "last message repeated ..." at some later time.
468 Calls log_callback if non-null, else prints to stderr.
469 **************************************************************************/
do_log(const char * file,const char * function,int line,bool print_from_where,enum log_level level,const char * message,...)470 void do_log(const char *file, const char *function, int line,
471 bool print_from_where, enum log_level level,
472 const char *message, ...)
473 {
474 char buf[MAX_LEN_LOG_LINE];
475 va_list args;
476
477 va_start(args, message);
478 vdo_log(file, function, line, print_from_where, level,
479 buf, MAX_LEN_LOG_LINE, message, args);
480 va_end(args);
481 }
482
483 /**************************************************************************
484 Set what signal the fc_assert* macros should raise on failed assertion
485 (-1 to disable).
486 **************************************************************************/
fc_assert_set_fatal(int fatal_assertions)487 void fc_assert_set_fatal(int fatal_assertions)
488 {
489 fc_fatal_assertions = fatal_assertions;
490 }
491
492 #ifndef FREECIV_NDEBUG
493 /**************************************************************************
494 Returns wether the fc_assert* macros should raise a signal on failed
495 assertion.
496 **************************************************************************/
fc_assert_fail(const char * file,const char * function,int line,const char * assertion,const char * message,...)497 void fc_assert_fail(const char *file, const char *function, int line,
498 const char *assertion, const char *message, ...)
499 {
500 enum log_level level = (0 <= fc_fatal_assertions ? LOG_FATAL : LOG_ERROR);
501
502 if (NULL != assertion) {
503 do_log(file, function, line, TRUE, level,
504 "assertion '%s' failed.", assertion);
505 }
506
507 if (NULL != message && NOLOGMSG != message) {
508 /* Additional message. */
509 char buf[MAX_LEN_LOG_LINE];
510 va_list args;
511
512 va_start(args, message);
513 vdo_log(file, function, line, FALSE, level, buf, MAX_LEN_LOG_LINE,
514 message, args);
515 va_end(args);
516 }
517
518 do_log(file, function, line, FALSE, level,
519 /* TRANS: No full stop after the URL, could cause confusion. */
520 _("Please report this message at %s"), BUG_URL);
521
522 if (0 <= fc_fatal_assertions) {
523 /* Emit a signal. */
524 raise(fc_fatal_assertions);
525 }
526 }
527 #endif /* FREECIV_NDEBUG */
528