update epan/dissectors/pidl/drsuapi/drsuapi.idl from samba
[wireshark-sm.git] / wsutil / wslog.c
blob4b35c294961ec5a70bb80f372750b5087c0cbe78
1 /*
2 * Copyright 2021, João Valverde <j@v6e.pt>
4 * Wireshark - Network traffic analyzer
5 * By Gerald Combs <gerald@wireshark.org>
6 * Copyright 1998 Gerald Combs
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
11 #include "config.h"
13 #include "wslog.h"
15 #include <stdlib.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <time.h>
19 /* Because ws_assert() dependes on ws_error() we do not use it
20 * here and fall back on assert() instead. */
21 #include <assert.h>
22 #ifdef HAVE_UNISTD_H
23 #include <unistd.h>
24 #endif
25 #ifdef _WIN32
26 #include <process.h>
27 #include <windows.h>
28 #include <conio.h>
29 #endif
31 #include "file_util.h"
32 #include "time_util.h"
33 #include "to_str.h"
34 #include "strtoi.h"
35 #ifdef _WIN32
36 #include "console_win32.h"
37 #endif
39 #define ASSERT(expr) assert(expr)
41 /* Runtime log level. */
42 #define ENV_VAR_LEVEL "WIRESHARK_LOG_LEVEL"
44 /* Log domains enabled/disabled. */
45 #define ENV_VAR_DOMAIN "WIRESHARK_LOG_DOMAIN"
47 /* Alias "domain" and "domains". */
48 #define ENV_VAR_DOMAIN_S "WIRESHARK_LOG_DOMAINS"
50 /* Log level that generates a trap and aborts. Can be "critical"
51 * or "warning". */
52 #define ENV_VAR_FATAL "WIRESHARK_LOG_FATAL"
54 /* Log domains that are fatal. */
55 #define ENV_VAR_FATAL_DOMAIN "WIRESHARK_LOG_FATAL_DOMAIN"
57 /* Alias "domain" and "domains". */
58 #define ENV_VAR_FATAL_DOMAIN_S "WIRESHARK_LOG_FATAL_DOMAINS"
60 /* Domains that will produce debug output, regardless of log level or
61 * domain filter. */
62 #define ENV_VAR_DEBUG "WIRESHARK_LOG_DEBUG"
64 /* Domains that will produce noisy output, regardless of log level or
65 * domain filter. */
66 #define ENV_VAR_NOISY "WIRESHARK_LOG_NOISY"
68 #define DEFAULT_LOG_LEVEL LOG_LEVEL_MESSAGE
70 #define DEFAULT_PROGNAME "PID"
72 #define DOMAIN_UNDEFED(domain) ((domain) == NULL || *(domain) == '\0')
73 #define DOMAIN_DEFINED(domain) (!DOMAIN_UNDEFED(domain))
76 * Note: I didn't measure it but I assume using a string array is faster than
77 * a GHashTable for small number N of domains.
79 typedef struct {
80 char **domainv;
81 bool positive; /* positive or negative match */
82 enum ws_log_level min_level; /* for level filters */
83 } log_filter_t;
86 /* If the module is not initialized by calling ws_log_init() all messages
87 * will be printed regardless of log level. This is a feature, not a bug. */
88 static enum ws_log_level current_log_level = LOG_LEVEL_NONE;
90 static bool stdout_color_enabled;
92 static bool stderr_color_enabled;
94 /* Use stdout for levels "info" and below, for backward compatibility
95 * with GLib. */
96 static bool stdout_logging_enabled;
98 static const char *registered_progname = DEFAULT_PROGNAME;
100 /* List of domains to filter. */
101 static log_filter_t *domain_filter;
103 /* List of domains to output debug level unconditionally. */
104 static log_filter_t *debug_filter;
106 /* List of domains to output noisy level unconditionally. */
107 static log_filter_t *noisy_filter;
109 /* List of domains that are fatal. */
110 static log_filter_t *fatal_filter;
112 static ws_log_writer_cb *registered_log_writer;
114 static void *registered_log_writer_data;
116 static ws_log_writer_free_data_cb *registered_log_writer_data_free;
118 static FILE *custom_log;
120 static enum ws_log_level fatal_log_level = LOG_LEVEL_ERROR;
122 #ifdef WS_DEBUG
123 static bool init_complete;
124 #endif
126 ws_log_console_open_pref ws_log_console_open = LOG_CONSOLE_OPEN_NEVER;
129 static void print_err(void (*vcmdarg_err)(const char *, va_list ap),
130 int exit_failure,
131 const char *fmt, ...) G_GNUC_PRINTF(3,4);
133 static void ws_log_cleanup(void);
136 const char *ws_log_level_to_string(enum ws_log_level level)
138 switch (level) {
139 case LOG_LEVEL_NONE:
140 return "(zero)";
141 case LOG_LEVEL_ECHO:
142 return "ECHO";
143 case LOG_LEVEL_ERROR:
144 return "ERROR";
145 case LOG_LEVEL_CRITICAL:
146 return "CRITICAL";
147 case LOG_LEVEL_WARNING:
148 return "WARNING";
149 case LOG_LEVEL_MESSAGE:
150 return "MESSAGE";
151 case LOG_LEVEL_INFO:
152 return "INFO";
153 case LOG_LEVEL_DEBUG:
154 return "DEBUG";
155 case LOG_LEVEL_NOISY:
156 return "NOISY";
157 default:
158 return "(BOGUS LOG LEVEL)";
163 static enum ws_log_level string_to_log_level(const char *str_level)
165 if (!str_level)
166 return LOG_LEVEL_NONE;
168 if (g_ascii_strcasecmp(str_level, "noisy") == 0)
169 return LOG_LEVEL_NOISY;
170 else if (g_ascii_strcasecmp(str_level, "debug") == 0)
171 return LOG_LEVEL_DEBUG;
172 else if (g_ascii_strcasecmp(str_level, "info") == 0)
173 return LOG_LEVEL_INFO;
174 else if (g_ascii_strcasecmp(str_level, "message") == 0)
175 return LOG_LEVEL_MESSAGE;
176 else if (g_ascii_strcasecmp(str_level, "warning") == 0)
177 return LOG_LEVEL_WARNING;
178 else if (g_ascii_strcasecmp(str_level, "critical") == 0)
179 return LOG_LEVEL_CRITICAL;
180 else if (g_ascii_strcasecmp(str_level, "error") == 0)
181 return LOG_LEVEL_ERROR;
182 else if (g_ascii_strcasecmp(str_level, "echo") == 0)
183 return LOG_LEVEL_ECHO;
184 else
185 return LOG_LEVEL_NONE;
189 WS_RETNONNULL
190 static inline const char *domain_to_string(const char *domain)
192 return DOMAIN_UNDEFED(domain) ? "(none)" : domain;
196 static inline bool filter_contains(log_filter_t *filter,
197 const char *domain)
199 if (filter == NULL || DOMAIN_UNDEFED(domain))
200 return false;
202 for (char **domv = filter->domainv; *domv != NULL; domv++) {
203 if (g_ascii_strcasecmp(*domv, domain) == 0) {
204 return true;
207 return false;
211 static inline bool level_filter_matches(log_filter_t *filter,
212 const char *domain,
213 enum ws_log_level level,
214 bool *active_ptr)
216 if (filter == NULL || DOMAIN_UNDEFED(domain))
217 return false;
219 if (!filter_contains(filter, domain))
220 return false;
222 if (filter->positive) {
223 if (active_ptr)
224 *active_ptr = level >= filter->min_level;
225 return true;
228 /* negative match */
229 if (level <= filter->min_level) {
230 if (active_ptr)
231 *active_ptr = false;
232 return true;
235 return false;
239 static inline void
240 get_timestamp(struct timespec *ts)
242 bool ok = false;
244 #if defined(HAVE_CLOCK_GETTIME)
245 ok = (clock_gettime(CLOCK_REALTIME, ts) == 0);
246 #elif defined(HAVE_TIMESPEC_GET)
247 ok = (timespec_get(ts, TIME_UTC) == TIME_UTC);
248 #endif
249 if (ok)
250 return;
252 /* Fall back on time(). */
253 ts->tv_sec = time(NULL);
254 ts->tv_nsec = -1;
258 static inline void fill_manifest(ws_log_manifest_t *mft)
260 struct timespec ts;
261 get_timestamp(&ts);
262 ws_localtime_r(&ts.tv_sec, &mft->tstamp_secs);
263 mft->nanosecs = ts.tv_nsec;
264 mft->pid = getpid();
268 static inline bool msg_is_active(const char *domain, enum ws_log_level level,
269 ws_log_manifest_t *mft)
271 bool is_active = ws_log_msg_is_active(domain, level);
272 if (is_active)
273 fill_manifest(mft);
274 return is_active;
278 bool ws_log_msg_is_active(const char *domain, enum ws_log_level level)
281 * Higher numerical levels have higher priority. Critical and above
282 * are always enabled.
284 if (level >= LOG_LEVEL_CRITICAL)
285 return true;
288 * Check if the level has been configured as fatal.
290 if (level >= fatal_log_level)
291 return true;
294 * Check if the domain has been configured as fatal.
296 if (DOMAIN_DEFINED(domain) && fatal_filter != NULL) {
297 if (filter_contains(fatal_filter, domain) && fatal_filter->positive) {
298 return true;
303 * The debug/noisy filter overrides the other parameters.
305 if (DOMAIN_DEFINED(domain)) {
306 bool active;
308 if (level_filter_matches(noisy_filter, domain, level, &active))
309 return active;
310 if (level_filter_matches(debug_filter, domain, level, &active))
311 return active;
315 * If the priority is lower than the current minimum drop the
316 * message.
318 if (level < current_log_level)
319 return false;
322 * If we don't have domain filtering enabled we are done.
324 if (domain_filter == NULL)
325 return true;
328 * We have a filter but we don't use it with the undefined domain,
329 * pretty much every permanent call to ws_log should be using a
330 * chosen domain.
332 if (DOMAIN_UNDEFED(domain))
333 return true;
335 /* Check if the domain filter matches. */
336 if (filter_contains(domain_filter, domain))
337 return domain_filter->positive;
339 /* We have a domain filter but it didn't match. */
340 return !domain_filter->positive;
344 enum ws_log_level ws_log_get_level(void)
346 return current_log_level;
350 enum ws_log_level ws_log_set_level(enum ws_log_level level)
352 if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST)
353 return LOG_LEVEL_NONE;
354 if (level > LOG_LEVEL_CRITICAL)
355 level = LOG_LEVEL_CRITICAL;
357 current_log_level = level;
358 return current_log_level;
362 enum ws_log_level ws_log_set_level_str(const char *str_level)
364 enum ws_log_level level;
366 level = string_to_log_level(str_level);
367 return ws_log_set_level(level);
371 static const char *opt_level = "--log-level";
372 static const char *opt_domain = "--log-domain";
373 /* Alias "domain" and "domains". */
374 static const char *opt_domain_s = "--log-domains";
375 static const char *opt_file = "--log-file";
376 static const char *opt_fatal = "--log-fatal";
377 static const char *opt_fatal_domain = "--log-fatal-domain";
378 /* Alias "domain" and "domains". */
379 static const char *opt_fatal_domain_s = "--log-fatal-domains";
380 static const char *opt_debug = "--log-debug";
381 static const char *opt_noisy = "--log-noisy";
384 static void print_err(void (*vcmdarg_err)(const char *, va_list ap),
385 int exit_failure,
386 const char *fmt, ...)
388 va_list ap;
390 va_start(ap, fmt);
391 if (vcmdarg_err)
392 vcmdarg_err(fmt, ap);
393 else
394 vfprintf(stderr, fmt, ap);
395 va_end(ap);
396 if (exit_failure != LOG_ARGS_NOEXIT)
397 exit(exit_failure);
402 * This tries to convert old log level preference to a wslog
403 * configuration. The string must start with "console.log.level:"
404 * It receives an argv for { '-o', 'console.log.level:nnn', ...} or
405 * { '-oconsole.log.level:nnn', ...}.
407 static void
408 parse_console_compat_option(char *argv[],
409 void (*vcmdarg_err)(const char *, va_list ap),
410 int exit_failure)
412 const char *mask_str;
413 uint32_t mask;
414 enum ws_log_level level;
416 ASSERT(argv != NULL);
418 if (argv[0] == NULL)
419 return;
421 if (strcmp(argv[0], "-o") == 0) {
422 if (argv[1] == NULL ||
423 !g_str_has_prefix(argv[1], "console.log.level:")) {
424 /* Not what we were looking for. */
425 return;
427 mask_str = argv[1] + strlen("console.log.level:");
429 else if (g_str_has_prefix(argv[0], "-oconsole.log.level:")) {
430 mask_str = argv[0] + strlen("-oconsole.log.level:");
432 else {
433 /* Not what we were looking for. */
434 return;
437 print_err(vcmdarg_err, LOG_ARGS_NOEXIT,
438 "Option 'console.log.level' is deprecated, consult '--help' "
439 "for diagnostic message options.");
441 if (*mask_str == '\0') {
442 print_err(vcmdarg_err, exit_failure,
443 "Missing value to 'console.log.level' option.");
444 return;
447 if (!ws_basestrtou32(mask_str, NULL, &mask, 10)) {
448 print_err(vcmdarg_err, exit_failure,
449 "%s is not a valid decimal number.", mask_str);
450 return;
454 * The lowest priority bit in the mask defines the level.
456 if (mask & G_LOG_LEVEL_DEBUG)
457 level = LOG_LEVEL_DEBUG;
458 else if (mask & G_LOG_LEVEL_INFO)
459 level = LOG_LEVEL_INFO;
460 else if (mask & G_LOG_LEVEL_MESSAGE)
461 level = LOG_LEVEL_MESSAGE;
462 else if (mask & G_LOG_LEVEL_WARNING)
463 level = LOG_LEVEL_WARNING;
464 else if (mask & G_LOG_LEVEL_CRITICAL)
465 level = LOG_LEVEL_CRITICAL;
466 else if (mask & G_LOG_LEVEL_ERROR)
467 level = LOG_LEVEL_ERROR;
468 else
469 level = LOG_LEVEL_NONE;
471 if (level == LOG_LEVEL_NONE) {
472 /* Some values (like zero) might not contain any meaningful bits.
473 * Throwing an error in that case seems appropriate. */
474 print_err(vcmdarg_err, exit_failure,
475 "Value %s is not a valid log mask.", mask_str);
476 return;
479 ws_log_set_level(level);
482 /* Match "arg_name=value" or "arg_name value" to opt_name. */
483 static bool optequal(const char *arg, const char *opt)
485 ASSERT(arg);
486 ASSERT(opt);
487 #define ARGEND(arg) (*(arg) == '\0' || *(arg) == ' ' || *(arg) == '=')
489 while (!ARGEND(arg) && *opt != '\0') {
490 if (*arg != *opt) {
491 return false;
493 arg += 1;
494 opt += 1;
496 if (ARGEND(arg) && *opt == '\0') {
497 return true;
499 return false;
502 int ws_log_parse_args(int *argc_ptr, char *argv[],
503 void (*vcmdarg_err)(const char *, va_list ap),
504 int exit_failure)
506 char **ptr = argv;
507 int count = *argc_ptr;
508 int ret = 0;
509 size_t optlen;
510 const char *option, *value;
511 int extra;
513 if (argc_ptr == NULL || argv == NULL)
514 return -1;
516 #ifdef WS_DEBUG
517 /* Assert ws_log_init() was called before ws_log_parse_args(). */
518 ASSERT(init_complete);
519 #endif
521 /* Configure from command line. */
523 while (*ptr != NULL) {
524 if (optequal(*ptr, opt_level)) {
525 option = opt_level;
526 optlen = strlen(opt_level);
528 else if (optequal(*ptr, opt_domain)) {
529 option = opt_domain;
530 optlen = strlen(opt_domain);
532 else if (optequal(*ptr, opt_domain_s)) {
533 option = opt_domain; /* Alias */
534 optlen = strlen(opt_domain_s);
536 else if (optequal(*ptr, opt_fatal_domain)) {
537 option = opt_fatal_domain;
538 optlen = strlen(opt_fatal_domain);
540 else if (optequal(*ptr, opt_fatal_domain_s)) {
541 option = opt_fatal_domain; /* Alias */
542 optlen = strlen(opt_fatal_domain_s);
544 else if (optequal(*ptr, opt_file)) {
545 option = opt_file;
546 optlen = strlen(opt_file);
548 else if (optequal(*ptr, opt_fatal)) {
549 option = opt_fatal;
550 optlen = strlen(opt_fatal);
552 else if (optequal(*ptr, opt_debug)) {
553 option = opt_debug;
554 optlen = strlen(opt_debug);
556 else if (optequal(*ptr, opt_noisy)) {
557 option = opt_noisy;
558 optlen = strlen(opt_noisy);
560 else {
561 /* Check is we have the old '-o console.log.level' flag,
562 * or '-oconsole.log.level', for backward compatibility.
563 * Then if we do ignore it after processing and let the
564 * preferences module handle it later. */
565 if (*(*ptr + 0) == '-' && *(*ptr + 1) == 'o') {
566 parse_console_compat_option(ptr, vcmdarg_err, exit_failure);
568 ptr += 1;
569 count -= 1;
570 continue;
573 value = *ptr + optlen;
574 /* Two possibilities:
575 * --<option> <value>
576 * or
577 * --<option>=<value>
579 if (value[0] == '\0') {
580 /* value is separated with blank space */
581 value = *(ptr + 1);
582 extra = 1;
584 if (value == NULL || !*value || *value == '-') {
585 /* If the option value after the blank starts with '-' assume
586 * it is another option. */
587 print_err(vcmdarg_err, exit_failure,
588 "Option \"%s\" requires a value.\n", *ptr);
589 option = NULL;
590 extra = 0;
591 ret += 1;
594 else if (value[0] == '=') {
595 /* value is after equals */
596 value += 1;
597 extra = 0;
599 else {
600 /* Option isn't known. */
601 ptr += 1;
602 count -= 1;
603 continue;
606 if (option == opt_level) {
607 if (ws_log_set_level_str(value) == LOG_LEVEL_NONE) {
608 print_err(vcmdarg_err, exit_failure,
609 "Invalid log level \"%s\".\n", value);
610 ret += 1;
613 else if (option == opt_domain) {
614 ws_log_set_domain_filter(value);
616 else if (option == opt_fatal_domain) {
617 ws_log_set_fatal_domain_filter(value);
619 else if (option == opt_file) {
620 if (value == NULL) {
621 print_err(vcmdarg_err, exit_failure,
622 "Option '%s' requires an argument.\n",
623 option);
624 ret += 1;
626 else {
627 FILE *fp = ws_fopen(value, "w");
628 if (fp == NULL) {
629 print_err(vcmdarg_err, exit_failure,
630 "Error opening file '%s' for writing: %s.\n",
631 value, g_strerror(errno));
632 ret += 1;
634 else {
635 ws_log_add_custom_file(fp);
639 else if (option == opt_fatal) {
640 if (ws_log_set_fatal_level_str(value) == LOG_LEVEL_NONE) {
641 print_err(vcmdarg_err, exit_failure,
642 "Fatal log level must be \"critical\" or "
643 "\"warning\", not \"%s\".\n", value);
644 ret += 1;
647 else if (option == opt_debug) {
648 ws_log_set_debug_filter(value);
650 else if (option == opt_noisy) {
651 ws_log_set_noisy_filter(value);
653 else {
654 /* Option value missing or invalid, do nothing. */
658 * We found a log option. We will remove it from
659 * the argv by moving up the other strings in the array. This is
660 * so that it doesn't generate an unrecognized option
661 * error further along in the initialization process.
663 /* Include the terminating NULL in the memmove. */
664 memmove(ptr, ptr + 1 + extra, (count - extra) * sizeof(*ptr));
665 /* No need to increment ptr here. */
666 count -= (1 + extra);
667 *argc_ptr -= (1 + extra);
670 return ret;
674 static void free_log_filter(log_filter_t **filter_ptr)
676 if (filter_ptr == NULL || *filter_ptr == NULL)
677 return;
678 g_strfreev((*filter_ptr)->domainv);
679 g_free(*filter_ptr);
680 *filter_ptr = NULL;
684 static void tokenize_filter_str(log_filter_t **filter_ptr,
685 const char *str_filter,
686 enum ws_log_level min_level)
688 const char *sep = ",;";
689 bool negated = false;
690 log_filter_t *filter;
692 ASSERT(filter_ptr);
693 ASSERT(*filter_ptr == NULL);
695 if (str_filter == NULL)
696 return;
698 if (str_filter[0] == '!') {
699 negated = true;
700 str_filter += 1;
702 if (*str_filter == '\0')
703 return;
705 filter = g_new(log_filter_t, 1);
706 filter->domainv = g_strsplit_set(str_filter, sep, -1);
707 filter->positive = !negated;
708 filter->min_level = min_level;
709 *filter_ptr = filter;
713 void ws_log_set_domain_filter(const char *str_filter)
715 free_log_filter(&domain_filter);
716 tokenize_filter_str(&domain_filter, str_filter, LOG_LEVEL_NONE);
720 void ws_log_set_fatal_domain_filter(const char *str_filter)
722 free_log_filter(&fatal_filter);
723 tokenize_filter_str(&fatal_filter, str_filter, LOG_LEVEL_NONE);
727 void ws_log_set_debug_filter(const char *str_filter)
729 free_log_filter(&debug_filter);
730 tokenize_filter_str(&debug_filter, str_filter, LOG_LEVEL_DEBUG);
734 void ws_log_set_noisy_filter(const char *str_filter)
736 free_log_filter(&noisy_filter);
737 tokenize_filter_str(&noisy_filter, str_filter, LOG_LEVEL_NOISY);
741 enum ws_log_level ws_log_set_fatal_level(enum ws_log_level level)
743 if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST)
744 return LOG_LEVEL_NONE;
745 if (level > LOG_LEVEL_ERROR)
746 level = LOG_LEVEL_ERROR;
747 if (level < LOG_LEVEL_WARNING)
748 level = LOG_LEVEL_WARNING;
750 fatal_log_level = level;
751 return fatal_log_level;
755 enum ws_log_level ws_log_set_fatal_level_str(const char *str_level)
757 enum ws_log_level level;
759 level = string_to_log_level(str_level);
760 return ws_log_set_fatal_level(level);
764 void ws_log_set_writer(ws_log_writer_cb *writer)
766 if (registered_log_writer_data_free)
767 registered_log_writer_data_free(registered_log_writer_data);
769 registered_log_writer = writer;
770 registered_log_writer_data = NULL;
771 registered_log_writer_data_free = NULL;
775 void ws_log_set_writer_with_data(ws_log_writer_cb *writer,
776 void *user_data,
777 ws_log_writer_free_data_cb *free_user_data)
779 if (registered_log_writer_data_free)
780 registered_log_writer_data_free(registered_log_writer_data);
782 registered_log_writer = writer;
783 registered_log_writer_data = user_data;
784 registered_log_writer_data_free = free_user_data;
788 static void glib_log_handler(const char *domain, GLogLevelFlags flags,
789 const char *message, void * user_data _U_)
791 enum ws_log_level level;
794 * The highest priority bit in the mask defines the level. We
795 * ignore the GLib fatal log level mask and use our own fatal
796 * log level setting instead.
799 if (flags & G_LOG_LEVEL_ERROR)
800 level = LOG_LEVEL_ERROR;
801 else if (flags & G_LOG_LEVEL_CRITICAL)
802 level = LOG_LEVEL_CRITICAL;
803 else if (flags & G_LOG_LEVEL_WARNING)
804 level = LOG_LEVEL_WARNING;
805 else if (flags & G_LOG_LEVEL_MESSAGE)
806 level = LOG_LEVEL_MESSAGE;
807 else if (flags & G_LOG_LEVEL_INFO)
808 level = LOG_LEVEL_INFO;
809 else if (flags & G_LOG_LEVEL_DEBUG)
810 level = LOG_LEVEL_DEBUG;
811 else
812 level = LOG_LEVEL_NONE; /* Should not happen. */
814 ws_log(domain, level, "%s", message);
818 #ifdef _WIN32
819 static void load_registry(void)
821 LONG lResult;
822 DWORD ptype;
823 DWORD data;
824 DWORD data_size = sizeof(DWORD);
826 lResult = RegGetValueA(HKEY_CURRENT_USER,
827 "Software\\Wireshark",
828 LOG_HKCU_CONSOLE_OPEN,
829 RRF_RT_REG_DWORD,
830 &ptype,
831 &data,
832 &data_size);
833 if (lResult != ERROR_SUCCESS || ptype != REG_DWORD) {
834 return;
837 ws_log_console_open = (ws_log_console_open_pref)data;
839 #endif
843 * We can't write to stderr in ws_log_init() because dumpcap uses stderr
844 * to communicate with the parent and it will block. We have to use
845 * vcmdarg_err to report errors.
847 void ws_log_init(const char *progname,
848 void (*vcmdarg_err)(const char *, va_list ap))
850 const char *env;
851 int fd;
853 if (progname != NULL) {
854 registered_progname = progname;
855 g_set_prgname(progname);
858 ws_tzset();
860 current_log_level = DEFAULT_LOG_LEVEL;
862 if ((fd = fileno(stdout)) >= 0)
863 stdout_color_enabled = g_log_writer_supports_color(fd);
864 if ((fd = fileno(stderr)) >= 0)
865 stderr_color_enabled = g_log_writer_supports_color(fd);
867 /* Set ourselves as the default log handler for all GLib domains. */
868 g_log_set_default_handler(glib_log_handler, NULL);
870 #ifdef _WIN32
871 load_registry();
873 /* if the user wants a console to be always there, well, we should open one for him */
874 if (ws_log_console_open == LOG_CONSOLE_OPEN_ALWAYS) {
875 create_console();
877 #endif
879 atexit(ws_log_cleanup);
881 /* Configure from environment. */
883 env = g_getenv(ENV_VAR_LEVEL);
884 if (env != NULL) {
885 if (ws_log_set_level_str(env) == LOG_LEVEL_NONE) {
886 print_err(vcmdarg_err, LOG_ARGS_NOEXIT,
887 "Ignoring invalid environment value %s=\"%s\"",
888 ENV_VAR_LEVEL, env);
892 env = g_getenv(ENV_VAR_FATAL);
893 if (env != NULL) {
894 if (ws_log_set_fatal_level_str(env) == LOG_LEVEL_NONE) {
895 print_err(vcmdarg_err, LOG_ARGS_NOEXIT,
896 "Ignoring invalid environment value %s=\"%s\"",
897 ENV_VAR_FATAL, env);
901 /* Alias "domain" and "domains". The plural form wins. */
902 if ((env = g_getenv(ENV_VAR_DOMAIN_S)) != NULL)
903 ws_log_set_domain_filter(env);
904 else if ((env = g_getenv(ENV_VAR_DOMAIN)) != NULL)
905 ws_log_set_domain_filter(env);
907 /* Alias "domain" and "domains". The plural form wins. */
908 if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN_S)) != NULL)
909 ws_log_set_fatal_domain_filter(env);
910 else if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN)) != NULL)
911 ws_log_set_fatal_domain_filter(env);
913 env = g_getenv(ENV_VAR_DEBUG);
914 if (env != NULL)
915 ws_log_set_debug_filter(env);
917 env = g_getenv(ENV_VAR_NOISY);
918 if (env != NULL)
919 ws_log_set_noisy_filter(env);
921 #ifdef WS_DEBUG
922 init_complete = true;
923 #endif
927 void ws_log_init_with_writer(const char *progname,
928 ws_log_writer_cb *writer,
929 void (*vcmdarg_err)(const char *, va_list ap))
931 registered_log_writer = writer;
932 ws_log_init(progname, vcmdarg_err);
936 void ws_log_init_with_writer_and_data(const char *progname,
937 ws_log_writer_cb *writer,
938 void *user_data,
939 ws_log_writer_free_data_cb *free_user_data,
940 void (*vcmdarg_err)(const char *, va_list ap))
942 registered_log_writer_data = user_data;
943 registered_log_writer_data_free = free_user_data;
944 ws_log_init_with_writer(progname, writer, vcmdarg_err);
948 #define MAGENTA "\033[35m"
949 #define BLUE "\033[34m"
950 #define CYAN "\033[36m"
951 #define GREEN "\033[32m"
952 #define YELLOW "\033[33m"
953 #define RED "\033[31m"
954 #define RESET "\033[0m"
956 static inline const char *level_color_on(bool enable, enum ws_log_level level)
958 if (!enable)
959 return "";
961 switch (level) {
962 case LOG_LEVEL_NOISY:
963 case LOG_LEVEL_DEBUG:
964 return GREEN;
965 case LOG_LEVEL_INFO:
966 case LOG_LEVEL_MESSAGE:
967 return CYAN;
968 case LOG_LEVEL_WARNING:
969 return YELLOW;
970 case LOG_LEVEL_CRITICAL:
971 return MAGENTA;
972 case LOG_LEVEL_ERROR:
973 return RED;
974 case LOG_LEVEL_ECHO:
975 return YELLOW;
976 default:
977 break;
979 return "";
982 static inline const char *color_off(bool enable)
984 return enable ? RESET : "";
987 #define NANOSECS_IN_MICROSEC 1000
990 * We must not call anything that might log a message
991 * in the log handler context (GLib might log a message if we register
992 * our own handler for the GLib domain).
994 static void log_write_do_work(FILE *fp, bool use_color,
995 struct tm *when, long nanosecs, intmax_t pid,
996 const char *domain, enum ws_log_level level,
997 const char *file, long line, const char *func,
998 const char *user_format, va_list user_ap)
1000 fputs(" **", fp);
1002 #ifdef WS_DEBUG
1003 if (!init_complete)
1004 fputs(" no init!", fp);
1005 #endif
1007 /* Process */
1008 fprintf(fp, " (%s:%"PRIdMAX")", registered_progname, pid);
1010 /* Timestamp */
1011 if (when != NULL) {
1012 fprintf(fp, " %02d:%02d:%02d",
1013 when->tm_hour, when->tm_min, when->tm_sec);
1014 if (nanosecs >= 0) {
1015 fprintf(fp, ".%06ld", nanosecs / NANOSECS_IN_MICROSEC);
1019 /* Domain/level */
1020 fprintf(fp, " [%s %s%s%s]", domain_to_string(domain),
1021 level_color_on(use_color, level),
1022 ws_log_level_to_string(level),
1023 color_off(use_color));
1025 /* File/line */
1026 if (file != NULL) {
1027 fprintf(fp, " %s", file);
1028 if (line >= 0) {
1029 fprintf(fp, ":%ld", line);
1033 /* Any formatting changes here need to be synced with ui/capture.c:capture_input_closed. */
1034 fputs(" --", fp);
1036 /* Function name */
1037 if (func != NULL)
1038 fprintf(fp, " %s():", func);
1040 /* User message */
1041 fputc(' ', fp);
1042 vfprintf(fp, user_format, user_ap);
1043 fputc('\n', fp);
1044 fflush(fp);
1048 static inline FILE *console_file(enum ws_log_level level)
1050 if (level <= LOG_LEVEL_INFO && stdout_logging_enabled)
1051 return stdout;
1052 return stderr;
1056 static inline bool console_color_enabled(enum ws_log_level level)
1058 if (level <= LOG_LEVEL_INFO && stdout_logging_enabled)
1059 return stdout_color_enabled;
1060 return stderr_color_enabled;
1064 static void log_write_fatal_msg(FILE *fp, intmax_t pid, const char *msg)
1066 /* Process */
1067 fprintf(fp, " ** (%s:%"PRIdMAX") %s", registered_progname, pid, msg);
1072 * We must not call anything that might log a message
1073 * in the log handler context (GLib might log a message if we register
1074 * our own handler for the GLib domain).
1076 static void log_write_dispatch(const char *domain, enum ws_log_level level,
1077 const char *file, long line, const char *func,
1078 ws_log_manifest_t *mft,
1079 const char *user_format, va_list user_ap)
1081 bool fatal_event = false;
1082 const char *fatal_msg = NULL;
1083 va_list user_ap_copy;
1085 if (level >= fatal_log_level && level != LOG_LEVEL_ECHO) {
1086 fatal_event = true;
1087 fatal_msg = "Aborting on fatal log level exception\n";
1089 else if (fatal_filter != NULL) {
1090 if (filter_contains(fatal_filter, domain) && fatal_filter->positive) {
1091 fatal_event = true;
1092 fatal_msg = "Aborting on fatal log domain exception\n";
1096 #ifdef _WIN32
1097 if (ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) {
1098 create_console();
1100 #endif /* _WIN32 */
1102 if (custom_log) {
1103 va_copy(user_ap_copy, user_ap);
1104 log_write_do_work(custom_log, false,
1105 &mft->tstamp_secs, mft->nanosecs, mft->pid,
1106 domain, level, file, line, func,
1107 user_format, user_ap_copy);
1108 va_end(user_ap_copy);
1109 if (fatal_msg) {
1110 log_write_fatal_msg(custom_log, mft->pid, fatal_msg);
1114 if (registered_log_writer) {
1115 registered_log_writer(domain, level, file, line, func, fatal_msg, mft,
1116 user_format, user_ap, registered_log_writer_data);
1118 else {
1119 log_write_do_work(console_file(level), console_color_enabled(level),
1120 &mft->tstamp_secs, mft->nanosecs, mft->pid,
1121 domain, level, file, line, func,
1122 user_format, user_ap);
1123 if (fatal_msg) {
1124 log_write_fatal_msg(console_file(level), mft->pid, fatal_msg);
1128 #ifdef _WIN32
1129 if (fatal_event && ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) {
1130 /* wait for a key press before the following error handler will terminate the program
1131 this way the user at least can read the error message */
1132 printf("\n\nPress any key to exit\n");
1133 _getch();
1135 #endif /* _WIN32 */
1137 if (fatal_event) {
1138 abort();
1143 void ws_logv(const char *domain, enum ws_log_level level,
1144 const char *format, va_list ap)
1146 ws_log_manifest_t mft;
1147 if (!msg_is_active(domain, level, &mft))
1148 return;
1150 log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap);
1154 void ws_logv_full(const char *domain, enum ws_log_level level,
1155 const char *file, long line, const char *func,
1156 const char *format, va_list ap)
1158 ws_log_manifest_t mft;
1159 if (!msg_is_active(domain, level, &mft))
1160 return;
1162 log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1166 void ws_log(const char *domain, enum ws_log_level level,
1167 const char *format, ...)
1169 ws_log_manifest_t mft;
1170 if (!msg_is_active(domain, level, &mft))
1171 return;
1173 va_list ap;
1175 va_start(ap, format);
1176 log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap);
1177 va_end(ap);
1181 void ws_log_full(const char *domain, enum ws_log_level level,
1182 const char *file, long line, const char *func,
1183 const char *format, ...)
1185 ws_log_manifest_t mft;
1186 if (!msg_is_active(domain, level, &mft))
1187 return;
1189 va_list ap;
1191 va_start(ap, format);
1192 log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1193 va_end(ap);
1197 void ws_log_fatal_full(const char *domain, enum ws_log_level level,
1198 const char *file, long line, const char *func,
1199 const char *format, ...)
1201 ws_log_manifest_t mft;
1202 va_list ap;
1204 fill_manifest(&mft);
1205 va_start(ap, format);
1206 log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1207 va_end(ap);
1208 abort();
1212 void ws_log_write_always_full(const char *domain, enum ws_log_level level,
1213 const char *file, long line, const char *func,
1214 const char *format, ...)
1216 ws_log_manifest_t mft;
1217 va_list ap;
1219 fill_manifest(&mft);
1220 va_start(ap, format);
1221 log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1222 va_end(ap);
1226 static void
1227 append_trailer(const char *src, size_t src_length, wmem_strbuf_t *display, wmem_strbuf_t *underline)
1229 gunichar ch;
1230 size_t hex_len;
1232 while (src_length > 0) {
1233 ch = g_utf8_get_char_validated(src, src_length);
1234 if (ch == (gunichar)-1 || ch == (gunichar)-2) {
1235 wmem_strbuf_append_hex(display, *src);
1236 wmem_strbuf_append_c_count(underline, '^', 4);
1237 src += 1;
1238 src_length -= 1;
1240 else {
1241 if (g_unichar_isprint(ch)) {
1242 wmem_strbuf_append_unichar(display, ch);
1243 wmem_strbuf_append_c_count(underline, ' ', 1);
1245 else {
1246 hex_len = wmem_strbuf_append_hex_unichar(display, ch);
1247 wmem_strbuf_append_c_count(underline, ' ', hex_len);
1249 const char *tmp = g_utf8_next_char(src);
1250 src_length -= tmp - src;
1251 src = tmp;
1257 static char *
1258 make_utf8_display(const char *src, size_t src_length, size_t good_length)
1260 wmem_strbuf_t *display;
1261 wmem_strbuf_t *underline;
1262 gunichar ch;
1263 size_t hex_len;
1265 display = wmem_strbuf_create(NULL);
1266 underline = wmem_strbuf_create(NULL);
1268 for (const char *s = src; s < src + good_length; s = g_utf8_next_char(s)) {
1269 ch = g_utf8_get_char(s);
1271 if (g_unichar_isprint(ch)) {
1272 wmem_strbuf_append_unichar(display, ch);
1273 wmem_strbuf_append_c(underline, ' ');
1275 else {
1276 hex_len = wmem_strbuf_append_hex_unichar(display, ch);
1277 wmem_strbuf_append_c_count(underline, ' ', hex_len);
1281 append_trailer(&src[good_length], src_length - good_length, display, underline);
1283 wmem_strbuf_append_c(display, '\n');
1284 wmem_strbuf_append(display, underline->str);
1285 wmem_strbuf_destroy(underline);
1287 return wmem_strbuf_finalize(display);
1291 void ws_log_utf8_full(const char *domain, enum ws_log_level level,
1292 const char *file, long line, const char *func,
1293 const char *string, ssize_t _length, const char *endptr)
1295 if (!ws_log_msg_is_active(domain, level))
1296 return;
1298 char *display;
1299 size_t length;
1300 size_t good_length;
1302 if (_length < 0)
1303 length = strlen(string);
1304 else
1305 length = _length;
1307 if (endptr == NULL || endptr < string) {
1308 /* Find the pointer to the first invalid byte. */
1309 if (g_utf8_validate(string, length, &endptr)) {
1310 /* Valid string - should not happen. */
1311 return;
1314 good_length = endptr - string;
1316 display = make_utf8_display(string, length, good_length);
1318 ws_log_write_always_full(domain, level, file, line, func,
1319 "Invalid UTF-8 at address %p offset %zu (length = %zu):\n%s",
1320 string, good_length, length, display);
1322 g_free(display);
1326 void ws_log_buffer_full(const char *domain, enum ws_log_level level,
1327 const char *file, long line, const char *func,
1328 const uint8_t *ptr, size_t size, size_t max_bytes_len,
1329 const char *msg)
1331 if (!ws_log_msg_is_active(domain, level))
1332 return;
1334 char *bufstr = bytes_to_str_maxlen(NULL, ptr, size, max_bytes_len);
1336 if (G_UNLIKELY(msg == NULL))
1337 ws_log_write_always_full(domain, level, file, line, func,
1338 "<buffer:%p>: %s (%zu bytes)",
1339 ptr, bufstr, size);
1340 else
1341 ws_log_write_always_full(domain, level, file, line, func,
1342 "%s: %s (%zu bytes)",
1343 msg, bufstr, size);
1344 wmem_free(NULL, bufstr);
1348 void ws_log_file_writer(FILE *fp, const char *domain, enum ws_log_level level,
1349 const char *file, long line, const char *func,
1350 ws_log_manifest_t *mft,
1351 const char *user_format, va_list user_ap)
1353 log_write_do_work(fp, false,
1354 &mft->tstamp_secs, mft->nanosecs, mft->pid,
1355 domain, level, file, line, func,
1356 user_format, user_ap);
1360 void ws_log_console_writer(const char *domain, enum ws_log_level level,
1361 const char *file, long line, const char *func,
1362 ws_log_manifest_t *mft,
1363 const char *user_format, va_list user_ap)
1365 log_write_do_work(console_file(level), console_color_enabled(level),
1366 &mft->tstamp_secs, mft->nanosecs, mft->pid,
1367 domain, level, file, line, func,
1368 user_format, user_ap);
1372 WS_DLL_PUBLIC
1373 void ws_log_console_writer_set_use_stdout(bool use_stdout)
1375 stdout_logging_enabled = use_stdout;
1379 static void ws_log_cleanup(void)
1381 if (registered_log_writer_data_free) {
1382 registered_log_writer_data_free(registered_log_writer_data);
1383 registered_log_writer_data = NULL;
1385 if (custom_log) {
1386 fclose(custom_log);
1387 custom_log = NULL;
1389 free_log_filter(&domain_filter);
1390 free_log_filter(&debug_filter);
1391 free_log_filter(&noisy_filter);
1392 free_log_filter(&fatal_filter);
1396 void ws_log_add_custom_file(FILE *fp)
1398 if (custom_log != NULL) {
1399 fclose(custom_log);
1401 custom_log = fp;
1405 #define USAGE_LEVEL \
1406 "sets the active log level (\"critical\", \"warning\", etc.)"
1408 #define USAGE_FATAL \
1409 "sets level to abort the program (\"critical\" or \"warning\")"
1411 #define USAGE_DOMAINS \
1412 "comma-separated list of the active log domains"
1414 #define USAGE_FATAL_DOMAINS \
1415 "list of domains that cause the program to abort"
1417 #define USAGE_DEBUG \
1418 "list of domains with \"debug\" level"
1420 #define USAGE_NOISY \
1421 "list of domains with \"noisy\" level"
1423 #define USAGE_FILE \
1424 "file to output messages to (in addition to stderr)"
1426 void ws_log_print_usage(FILE *fp)
1428 fprintf(fp, "Diagnostic output:\n");
1429 fprintf(fp, " --log-level <level> " USAGE_LEVEL "\n");
1430 fprintf(fp, " --log-fatal <level> " USAGE_FATAL "\n");
1431 fprintf(fp, " --log-domains <[!]list> " USAGE_DOMAINS "\n");
1432 fprintf(fp, " --log-fatal-domains <list>\n");
1433 fprintf(fp, " " USAGE_FATAL_DOMAINS "\n");
1434 fprintf(fp, " --log-debug <[!]list> " USAGE_DEBUG "\n");
1435 fprintf(fp, " --log-noisy <[!]list> " USAGE_NOISY "\n");
1436 fprintf(fp, " --log-file <path> " USAGE_FILE "\n");