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
19 /* Because ws_assert() dependes on ws_error() we do not use it
20 * here and fall back on assert() instead. */
31 #include "file_util.h"
32 #include "time_util.h"
36 #include "console_win32.h"
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"
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
62 #define ENV_VAR_DEBUG "WIRESHARK_LOG_DEBUG"
64 /* Domains that will produce noisy output, regardless of log level or
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.
81 bool positive
; /* positive or negative match */
82 enum ws_log_level min_level
; /* for level filters */
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
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
;
123 static bool init_complete
;
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
),
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
)
143 case LOG_LEVEL_ERROR
:
145 case LOG_LEVEL_CRITICAL
:
147 case LOG_LEVEL_WARNING
:
149 case LOG_LEVEL_MESSAGE
:
153 case LOG_LEVEL_DEBUG
:
155 case LOG_LEVEL_NOISY
:
158 return "(BOGUS LOG LEVEL)";
163 static enum ws_log_level
string_to_log_level(const char *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
;
185 return LOG_LEVEL_NONE
;
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
,
199 if (filter
== NULL
|| DOMAIN_UNDEFED(domain
))
202 for (char **domv
= filter
->domainv
; *domv
!= NULL
; domv
++) {
203 if (g_ascii_strcasecmp(*domv
, domain
) == 0) {
211 static inline bool level_filter_matches(log_filter_t
*filter
,
213 enum ws_log_level level
,
216 if (filter
== NULL
|| DOMAIN_UNDEFED(domain
))
219 if (!filter_contains(filter
, domain
))
222 if (filter
->positive
) {
224 *active_ptr
= level
>= filter
->min_level
;
229 if (level
<= filter
->min_level
) {
240 get_timestamp(struct timespec
*ts
)
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
);
252 /* Fall back on time(). */
253 ts
->tv_sec
= time(NULL
);
258 static inline void fill_manifest(ws_log_manifest_t
*mft
)
262 ws_localtime_r(&ts
.tv_sec
, &mft
->tstamp_secs
);
263 mft
->nanosecs
= ts
.tv_nsec
;
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
);
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
)
288 * Check if the level has been configured as fatal.
290 if (level
>= fatal_log_level
)
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
) {
303 * The debug/noisy filter overrides the other parameters.
305 if (DOMAIN_DEFINED(domain
)) {
308 if (level_filter_matches(noisy_filter
, domain
, level
, &active
))
310 if (level_filter_matches(debug_filter
, domain
, level
, &active
))
315 * If the priority is lower than the current minimum drop the
318 if (level
< current_log_level
)
322 * If we don't have domain filtering enabled we are done.
324 if (domain_filter
== NULL
)
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
332 if (DOMAIN_UNDEFED(domain
))
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
),
386 const char *fmt
, ...)
392 vcmdarg_err(fmt
, ap
);
394 vfprintf(stderr
, fmt
, ap
);
396 if (exit_failure
!= LOG_ARGS_NOEXIT
)
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', ...}.
408 parse_console_compat_option(char *argv
[],
409 void (*vcmdarg_err
)(const char *, va_list ap
),
412 const char *mask_str
;
414 enum ws_log_level level
;
416 ASSERT(argv
!= NULL
);
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. */
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:");
433 /* Not what we were looking for. */
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.");
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
);
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
;
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
);
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
)
487 #define ARGEND(arg) (*(arg) == '\0' || *(arg) == ' ' || *(arg) == '=')
489 while (!ARGEND(arg
) && *opt
!= '\0') {
496 if (ARGEND(arg
) && *opt
== '\0') {
502 int ws_log_parse_args(int *argc_ptr
, char *argv
[],
503 void (*vcmdarg_err
)(const char *, va_list ap
),
507 int count
= *argc_ptr
;
510 const char *option
, *value
;
513 if (argc_ptr
== NULL
|| argv
== NULL
)
517 /* Assert ws_log_init() was called before ws_log_parse_args(). */
518 ASSERT(init_complete
);
521 /* Configure from command line. */
523 while (*ptr
!= NULL
) {
524 if (optequal(*ptr
, opt_level
)) {
526 optlen
= strlen(opt_level
);
528 else if (optequal(*ptr
, 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
)) {
546 optlen
= strlen(opt_file
);
548 else if (optequal(*ptr
, opt_fatal
)) {
550 optlen
= strlen(opt_fatal
);
552 else if (optequal(*ptr
, opt_debug
)) {
554 optlen
= strlen(opt_debug
);
556 else if (optequal(*ptr
, opt_noisy
)) {
558 optlen
= strlen(opt_noisy
);
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
);
573 value
= *ptr
+ optlen
;
574 /* Two possibilities:
579 if (value
[0] == '\0') {
580 /* value is separated with blank space */
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
);
594 else if (value
[0] == '=') {
595 /* value is after equals */
600 /* Option isn't known. */
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
);
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
) {
621 print_err(vcmdarg_err
, exit_failure
,
622 "Option '%s' requires an argument.\n",
627 FILE *fp
= ws_fopen(value
, "w");
629 print_err(vcmdarg_err
, exit_failure
,
630 "Error opening file '%s' for writing: %s.\n",
631 value
, g_strerror(errno
));
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
);
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
);
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
);
674 static void free_log_filter(log_filter_t
**filter_ptr
)
676 if (filter_ptr
== NULL
|| *filter_ptr
== NULL
)
678 g_strfreev((*filter_ptr
)->domainv
);
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
;
693 ASSERT(*filter_ptr
== NULL
);
695 if (str_filter
== NULL
)
698 if (str_filter
[0] == '!') {
702 if (*str_filter
== '\0')
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
,
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
;
812 level
= LOG_LEVEL_NONE
; /* Should not happen. */
814 ws_log(domain
, level
, "%s", message
);
819 static void load_registry(void)
824 DWORD data_size
= sizeof(DWORD
);
826 lResult
= RegGetValueA(HKEY_CURRENT_USER
,
827 "Software\\Wireshark",
828 LOG_HKCU_CONSOLE_OPEN
,
833 if (lResult
!= ERROR_SUCCESS
|| ptype
!= REG_DWORD
) {
837 ws_log_console_open
= (ws_log_console_open_pref
)data
;
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
))
853 if (progname
!= NULL
) {
854 registered_progname
= progname
;
855 g_set_prgname(progname
);
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
);
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
) {
879 atexit(ws_log_cleanup
);
881 /* Configure from environment. */
883 env
= g_getenv(ENV_VAR_LEVEL
);
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\"",
892 env
= g_getenv(ENV_VAR_FATAL
);
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\"",
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
);
915 ws_log_set_debug_filter(env
);
917 env
= g_getenv(ENV_VAR_NOISY
);
919 ws_log_set_noisy_filter(env
);
922 init_complete
= true;
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
,
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
)
962 case LOG_LEVEL_NOISY
:
963 case LOG_LEVEL_DEBUG
:
966 case LOG_LEVEL_MESSAGE
:
968 case LOG_LEVEL_WARNING
:
970 case LOG_LEVEL_CRITICAL
:
972 case LOG_LEVEL_ERROR
:
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
)
1004 fputs(" no init!", fp
);
1008 fprintf(fp
, " (%s:%"PRIdMAX
")", registered_progname
, pid
);
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
);
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
));
1027 fprintf(fp
, " %s", file
);
1029 fprintf(fp
, ":%ld", line
);
1033 /* Any formatting changes here need to be synced with ui/capture.c:capture_input_closed. */
1038 fprintf(fp
, " %s():", func
);
1042 vfprintf(fp
, user_format
, user_ap
);
1048 static inline FILE *console_file(enum ws_log_level level
)
1050 if (level
<= LOG_LEVEL_INFO
&& stdout_logging_enabled
)
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
)
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
) {
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
) {
1092 fatal_msg
= "Aborting on fatal log domain exception\n";
1097 if (ws_log_console_open
!= LOG_CONSOLE_OPEN_NEVER
) {
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
);
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
);
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
);
1124 log_write_fatal_msg(console_file(level
), mft
->pid
, fatal_msg
);
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");
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
))
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
))
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
))
1175 va_start(ap
, format
);
1176 log_write_dispatch(domain
, level
, NULL
, -1, NULL
, &mft
, format
, 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
))
1191 va_start(ap
, format
);
1192 log_write_dispatch(domain
, level
, file
, line
, func
, &mft
, format
, 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
;
1204 fill_manifest(&mft
);
1205 va_start(ap
, format
);
1206 log_write_dispatch(domain
, level
, file
, line
, func
, &mft
, format
, ap
);
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
;
1219 fill_manifest(&mft
);
1220 va_start(ap
, format
);
1221 log_write_dispatch(domain
, level
, file
, line
, func
, &mft
, format
, ap
);
1227 append_trailer(const char *src
, size_t src_length
, wmem_strbuf_t
*display
, wmem_strbuf_t
*underline
)
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);
1241 if (g_unichar_isprint(ch
)) {
1242 wmem_strbuf_append_unichar(display
, ch
);
1243 wmem_strbuf_append_c_count(underline
, ' ', 1);
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
;
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
;
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
, ' ');
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
))
1303 length
= strlen(string
);
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. */
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
);
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
,
1331 if (!ws_log_msg_is_active(domain
, level
))
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)",
1341 ws_log_write_always_full(domain
, level
, file
, line
, func
,
1342 "%s: %s (%zu bytes)",
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
);
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
;
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
) {
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");