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)
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 ***********************************************************************/
15 #include <fc_config.h>
33 #define MAX_LEN_LOG_LINE 5120
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
);
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
;
45 static fc_mutex logfile_mutex
;
48 static const enum log_level max_level
= LOG_DEBUG
;
50 static const enum log_level max_level
= LOG_VERBOSE
;
51 #endif /* FREECIV_DEBUG */
53 static enum log_level fc_log_level
= LOG_NORMAL
;
54 static int fc_fatal_assertions
= -1;
63 static int log_num_files
= 0;
64 static struct log_fileinfo
*log_files
= NULL
;
65 #endif /* FREECIV_DEBUG */
67 static char *log_level_names
[] = {
68 "Fatal", "Error", "Warning", "Normal", "Verbose", "Debug", NULL
71 /* A helper variable to indicate that there is no log message. The '%s' is
72 * added to use it as format string as well as the argument. */
73 const char *nologmsg
= "nologmsg:%s";
75 /**************************************************************************
76 level_str should be either "0", "1", "2", "3", "4" or
77 "4:filename" or "4:file1:file2" or "4:filename,100,200" etc
79 If everything goes ok, returns TRUE. If there was a parsing problem,
80 prints to stderr, and returns FALSE.
82 Return in ret_level the requested level only if level_str is a simple
83 number (like "0", "1", "2").
85 Also sets up the log_files data structure. Does _not_ set fc_log_level.
86 **************************************************************************/
87 bool log_parse_level_str(const char *level_str
, enum log_level
*ret_level
)
90 int n
= 0; /* number of filenames */
99 #endif /* FREECIV_DEBUG */
103 while ((c
= strchr(c
, ':'))) {
105 first_len
= c
- level_str
;
111 /* Global log level. */
112 if (!str_to_int(level_str
, &level
)) {
114 for (ln
= 0; log_level_names
[ln
] != NULL
&& level
< 0; ln
++) {
115 if (!fc_strncasecmp(level_str
, log_level_names
[ln
], strlen(level_str
))) {
120 fc_fprintf(stderr
, _("Bad log level \"%s\".\n"), level_str
);
124 if (level
>= LOG_FATAL
&& level
<= max_level
) {
125 if (NULL
!= ret_level
) {
130 fc_fprintf(stderr
, _("Bad log level %d in \"%s\".\n"),
132 #ifndef FREECIV_DEBUG
133 if (level
== max_level
+ 1) {
135 _("Freeciv must be compiled with the FREECIV_DEBUG flag "
136 "to use debug level %d.\n"), max_level
+ 1);
138 #endif /* FREECIV_DEBUG */
147 for (ln
= 0; log_level_names
[ln
] != NULL
&& level
< 0; ln
++) {
148 if (!fc_strncasecmp(level_str
, log_level_names
[ln
], first_len
)) {
156 if (level
< LOG_FATAL
|| level
> max_level
) {
157 fc_fprintf(stderr
, _("Bad log level %c in \"%s\".\n"),
162 fc_fprintf(stderr
, _("Badly formed log level argument \"%s\".\n"),
169 log_files
= fc_realloc(log_files
,
170 log_num_files
* sizeof(struct log_fileinfo
));
172 dupled
= fc_strdup(c
+ 2);
173 tok
= strtok(dupled
, ":");
176 fc_fprintf(stderr
, _("Badly formed log level argument \"%s\".\n"),
182 struct log_fileinfo
*pfile
= log_files
+ i
;
183 char *d
= strchr(tok
, ',');
187 pfile
->level
= level
;
192 d
= strchr(d
+ 1, ',');
193 if (d
&& *pc
!= '\0' && d
[1] != '\0') {
195 if (!str_to_int(pc
, &pfile
->min
)) {
196 fc_fprintf(stderr
, _("Not an integer: '%s'\n"), pc
);
200 if (!str_to_int(d
+ 1, &pfile
->max
)) {
201 fc_fprintf(stderr
, _("Not an integer: '%s'\n"), d
+ 1);
207 if (strlen(tok
) == 0) {
208 fc_fprintf(stderr
, _("Empty filename in log level argument \"%s\".\n"),
213 pfile
->name
= fc_strdup(tok
);
215 tok
= strtok(NULL
, ":");
218 if (i
!= log_num_files
) {
219 fc_fprintf(stderr
, _("Badly formed log level argument \"%s\".\n"),
228 #else /* FREECIV_DEBUG */
230 _("Freeciv must be compiled with the FREECIV_DEBUG flag "
231 "to use advanced log levels based on files.\n"));
233 #endif /* FREECIV_DEBUG */
236 /**************************************************************************
237 Initialise the log module. Either 'filename' or 'callback' may be NULL.
238 If both are NULL, print to stderr. If both are non-NULL, both callback,
239 and fprintf to file. Pass -1 for fatal_assertions to don't raise any
240 signal on failed assertion.
241 **************************************************************************/
242 void log_init(const char *filename
, enum log_level initial_level
,
243 log_callback_fn callback
, log_prefix_fn prefix
,
244 int fatal_assertions
)
246 fc_log_level
= initial_level
;
251 if (filename
&& strlen(filename
) > 0) {
252 log_filename
= fc_strdup(filename
);
256 log_callback
= callback
;
258 fc_fatal_assertions
= fatal_assertions
;
259 fc_init_mutex(&logfile_mutex
);
260 log_verbose("log started");
261 log_debug("LOG_DEBUG test");
264 /**************************************************************************
265 Deinitialize logging module.
266 **************************************************************************/
269 fc_destroy_mutex(&logfile_mutex
);
272 /*****************************************************************************
273 Adjust the log preparation callback function.
274 *****************************************************************************/
275 log_pre_callback_fn
log_set_pre_callback(log_pre_callback_fn precallback
)
277 log_pre_callback_fn old
= log_pre_callback
;
279 log_pre_callback
= precallback
;
284 /*****************************************************************************
285 Adjust the callback function after initial log_init().
286 *****************************************************************************/
287 log_callback_fn
log_set_callback(log_callback_fn callback
)
289 log_callback_fn old
= log_callback
;
291 log_callback
= callback
;
296 /**************************************************************************
297 Adjust the prefix callback function after initial log_init().
298 **************************************************************************/
299 log_prefix_fn
log_set_prefix(log_prefix_fn prefix
)
301 log_prefix_fn old
= log_prefix
;
308 /**************************************************************************
309 Adjust the logging level after initial log_init().
310 **************************************************************************/
311 void log_set_level(enum log_level level
)
313 fc_log_level
= level
;
316 /**************************************************************************
317 Returns the current log level.
318 **************************************************************************/
319 enum log_level
log_get_level(void)
324 /**************************************************************************
325 Return name of the given log level
326 **************************************************************************/
327 const char *log_level_name(enum log_level lvl
)
329 if (lvl
< LOG_FATAL
|| lvl
> LOG_DEBUG
) {
333 return log_level_names
[lvl
];
337 /**************************************************************************
338 Returns wether we should do an output for this level, in this file,
340 **************************************************************************/
341 bool log_do_output_for_level_at_location(enum log_level level
,
342 const char *file
, int line
)
344 struct log_fileinfo
*pfile
;
347 for (i
= 0, pfile
= log_files
; i
< log_num_files
; i
++, pfile
++) {
348 if (pfile
->level
>= level
349 && 0 == strcmp(pfile
->name
, file
)
350 && ((0 == pfile
->min
&& 0 == pfile
->max
)
351 || (pfile
->min
<= line
&& pfile
->max
>= line
))) {
355 return (fc_log_level
>= level
);
357 #endif /* FREECIV_DEBUG */
359 /*****************************************************************************
360 Unconditionally print a simple string.
361 Let the callback do its own level formatting and add a '\n' if it wants.
362 *****************************************************************************/
363 static void log_write(FILE *fs
, enum log_level level
, bool print_from_where
,
364 const char *where
, const char *message
)
366 if (log_filename
|| (!log_callback
)) {
370 /* Get the log prefix. */
371 fc_snprintf(prefix
, sizeof(prefix
), "[%s] ", log_prefix());
376 if (log_filename
|| (print_from_where
&& where
)) {
377 fc_fprintf(fs
, "%d: %s%s%s\n", level
, prefix
, where
, message
);
379 fc_fprintf(fs
, "%d: %s%s\n", level
, prefix
, message
);
385 if (print_from_where
) {
386 char buf
[MAX_LEN_LOG_LINE
];
388 fc_snprintf(buf
, sizeof(buf
), "%s%s", where
, message
);
389 log_callback(level
, buf
, log_filename
!= NULL
);
391 log_callback(level
, message
, log_filename
!= NULL
);
396 /*****************************************************************************
397 Unconditionally print a log message. This function is usually protected
399 *****************************************************************************/
400 void vdo_log(const char *file
, const char *function
, int line
,
401 bool print_from_where
, enum log_level level
,
402 char *buf
, int buflen
, const char *message
, va_list args
)
404 char buf_where
[MAX_LEN_LOG_LINE
];
406 /* There used to be check against recursive logging here, but
407 * the way it worked prevented any kind of simultaneous logging,
408 * not just recursive. Multiple threads should be able to log
411 fc_vsnprintf(buf
, buflen
, message
, args
);
412 fc_snprintf(buf_where
, sizeof(buf_where
), "in %s() [%s::%d]: ",
413 function
, file
, line
);
415 /* In the default configuration log_pre_callback is equal to log_real(). */
416 if (log_pre_callback
) {
417 log_pre_callback(level
, print_from_where
, buf_where
, buf
);
421 /*****************************************************************************
422 Really print a log message.
423 For repeat message, may wait and print instead "last message repeated ..."
425 Calls log_callback if non-null, else prints to stderr.
426 *****************************************************************************/
427 static void log_real(enum log_level level
, bool print_from_where
,
428 const char *where
, const char *msg
)
430 static char last_msg
[MAX_LEN_LOG_LINE
] = "";
431 static unsigned int repeated
= 0; /* total times current message repeated */
432 static unsigned int next
= 2; /* next total to print update */
433 static unsigned int prev
= 0; /* total on last update */
434 /* only count as repeat if same level */
435 static enum log_level prev_level
= -1;
436 char buf
[MAX_LEN_LOG_LINE
];
440 fc_allocate_mutex(&logfile_mutex
);
441 if (!(fs
= fc_fopen(log_filename
, "a"))) {
443 _("Couldn't open logfile: %s for appending \"%s\".\n"),
451 if (level
== prev_level
&& 0 == strncmp(msg
, last_msg
,
452 MAX_LEN_LOG_LINE
- 1)){
454 if (repeated
== next
) {
455 fc_snprintf(buf
, sizeof(buf
),
456 PL_("last message repeated %d time",
457 "last message repeated %d times",
458 repeated
-prev
), repeated
-prev
);
460 cat_snprintf(buf
, sizeof(buf
),
461 PL_(" (total %d repeat)",
462 " (total %d repeats)",
463 repeated
), repeated
);
465 log_write(fs
, prev_level
, print_from_where
, where
, buf
);
470 if (repeated
> 0 && repeated
!= prev
) {
472 /* just repeat the previous message: */
473 log_write(fs
, prev_level
, print_from_where
, where
, last_msg
);
475 fc_snprintf(buf
, sizeof(buf
),
476 PL_("last message repeated %d time",
477 "last message repeated %d times",
478 repeated
- prev
), repeated
- prev
);
480 cat_snprintf(buf
, sizeof(buf
),
481 PL_(" (total %d repeat)", " (total %d repeats)",
482 repeated
), repeated
);
484 log_write(fs
, prev_level
, print_from_where
, where
, buf
);
491 log_write(fs
, level
, print_from_where
, where
, msg
);
493 /* Save last message. */
494 sz_strlcpy(last_msg
, msg
);
499 fc_release_mutex(&logfile_mutex
);
503 /**************************************************************************
504 Unconditionally print a log message. This function is usually protected
506 For repeat message, may wait and print instead
507 "last message repeated ..." at some later time.
508 Calls log_callback if non-null, else prints to stderr.
509 **************************************************************************/
510 void do_log(const char *file
, const char *function
, int line
,
511 bool print_from_where
, enum log_level level
,
512 const char *message
, ...)
514 char buf
[MAX_LEN_LOG_LINE
];
517 va_start(args
, message
);
518 vdo_log(file
, function
, line
, print_from_where
, level
,
519 buf
, MAX_LEN_LOG_LINE
, message
, args
);
523 /**************************************************************************
524 Set what signal the fc_assert* macros should raise on failed assertion
526 **************************************************************************/
527 void fc_assert_set_fatal(int fatal_assertions
)
529 fc_fatal_assertions
= fatal_assertions
;
532 #ifndef FREECIV_NDEBUG
533 /**************************************************************************
534 Returns wether the fc_assert* macros should raise a signal on failed
536 **************************************************************************/
537 void fc_assert_fail(const char *file
, const char *function
, int line
,
538 const char *assertion
, const char *message
, ...)
540 enum log_level level
= (0 <= fc_fatal_assertions
? LOG_FATAL
: LOG_ERROR
);
542 if (NULL
!= assertion
) {
543 do_log(file
, function
, line
, TRUE
, level
,
544 "assertion '%s' failed.", assertion
);
547 if (NULL
!= message
&& NOLOGMSG
!= message
) {
548 /* Additional message. */
549 char buf
[MAX_LEN_LOG_LINE
];
552 va_start(args
, message
);
553 vdo_log(file
, function
, line
, FALSE
, level
, buf
, MAX_LEN_LOG_LINE
,
558 do_log(file
, function
, line
, FALSE
, level
,
559 /* TRANS: No full stop after the URL, could cause confusion. */
560 _("Please report this message at %s"), BUG_URL
);
562 if (0 <= fc_fatal_assertions
) {
564 raise(fc_fatal_assertions
);
567 #endif /* FREECIV_NDEBUG */