/* * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 2021 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include "wslog.h" #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef _WIN32 #include #endif #include #include #include /* Runtime log level. */ #define ENV_VAR_LEVEL "WIRESHARK_LOG_LEVEL" /* Log domains enabled/disabled. */ #define ENV_VAR_DOMAINS "WIRESHARK_LOG_DOMAINS" /* Log level that generates a trap and aborts. Can be "critical" * or "warning". */ #define ENV_VAR_FATAL "WIRESHARK_LOG_FATAL" /* Domains that will produce debug output, regardless of log level or * domain filter. */ #define ENV_VAR_DEBUG "WIRESHARK_LOG_DEBUG" /* Domains that will produce noisy output, regardless of log level or * domain filter. */ #define ENV_VAR_NOISY "WIRESHARK_LOG_NOISY" #define DEFAULT_LOG_LEVEL LOG_LEVEL_MESSAGE #define DEFAULT_APPNAME "PID" #define DOMAIN_NOTSET(domain) ((domain) == NULL || *(domain) == '\0') static enum ws_log_level current_log_level = LOG_LEVEL_NONE; static gboolean color_enabled = FALSE; static const char *registered_appname = NULL; /* List of domains to filter. */ static GPtrArray *domain_filter = NULL; /* List of domains to output debug level unconditionally. */ static GPtrArray *debug_filter = NULL; /* List of domains to output noisy level unconditionally. */ static GPtrArray *noisy_filter = NULL; /* True if active domains should match, false if active domains should not * match. */ static gboolean domain_filter_positive = TRUE; /* True if debug filter enables debug log level, false if debug filter * disables debug log level. */ static gboolean debug_filter_positive = TRUE; /* True if noisy filter enables noisy log level, false if noisy filter * disables noisy log level. */ static gboolean noisy_filter_positive = TRUE; static ws_log_writer_cb *registered_log_writer = NULL; static void *registered_log_writer_data = NULL; static ws_log_writer_free_data_cb *registered_log_writer_data_free = NULL; static FILE *custom_log = NULL; static enum ws_log_level fatal_log_level = LOG_LEVEL_ERROR; static void ws_log_cleanup(void); const char *ws_log_level_to_string(enum ws_log_level level) { switch (level) { case LOG_LEVEL_NONE: return "(zero)"; case LOG_LEVEL_ERROR: return "ERROR"; case LOG_LEVEL_CRITICAL: return "CRITICAL"; case LOG_LEVEL_WARNING: return "WARNING"; case LOG_LEVEL_MESSAGE: return "MESSAGE"; case LOG_LEVEL_INFO: return "INFO"; case LOG_LEVEL_DEBUG: return "DEBUG"; case LOG_LEVEL_NOISY: return "NOISY"; default: return "(BOGUS LOG LEVEL)"; } } static enum ws_log_level string_to_log_level(const char *str_level) { if (!str_level) return LOG_LEVEL_NONE; if (g_ascii_strcasecmp(str_level, "noisy") == 0) return LOG_LEVEL_NOISY; else if (g_ascii_strcasecmp(str_level, "debug") == 0) return LOG_LEVEL_DEBUG; else if (g_ascii_strcasecmp(str_level, "info") == 0) return LOG_LEVEL_INFO; else if (g_ascii_strcasecmp(str_level, "message") == 0) return LOG_LEVEL_MESSAGE; else if (g_ascii_strcasecmp(str_level, "warning") == 0) return LOG_LEVEL_WARNING; else if (g_ascii_strcasecmp(str_level, "critical") == 0) return LOG_LEVEL_CRITICAL; else if (g_ascii_strcasecmp(str_level, "error") == 0) return LOG_LEVEL_ERROR; else return LOG_LEVEL_NONE; } WS_RETNONNULL static inline const char *domain_to_string(const char *domain) { return (domain == NULL) ? "(none)" : domain; } static inline gboolean log_level_is_active(enum ws_log_level level) { /* * Lower numerical levels have higher priority. Critical and above * are always enabled. */ if (level <= LOG_LEVEL_CRITICAL) return TRUE; return level <= current_log_level; } static inline gboolean filter_contains(GPtrArray *filter, const char *domain) { if (filter == NULL || DOMAIN_NOTSET(domain)) return FALSE; for (guint i = 0; i < filter->len; i++) { if (g_ascii_strcasecmp(filter->pdata[i], domain) == 0) { return TRUE; } } return FALSE; } static inline gboolean log_domain_is_active(const char *domain) { if (domain_filter == NULL) return TRUE; /* * We don't filter the undefined domain, pretty much every permanent * call to ws_log should be using a set domain. */ if (DOMAIN_NOTSET(domain)) return TRUE; if (filter_contains(domain_filter, domain)) return domain_filter_positive; return !domain_filter_positive; } #define ACTIVE 1 #define SILENT 0 #define CONTINUE -1 static inline int level_filter_matches(GPtrArray *ptr, const char *domain, enum ws_log_level level, enum ws_log_level max_level, gboolean positive) { if (filter_contains(ptr, domain) == FALSE) return CONTINUE; if (positive) return level <= max_level ? ACTIVE : SILENT; /* negative match */ return level >= max_level ? SILENT : CONTINUE; } #define DEBUG_FILTER_MATCHES(domain, level) \ level_filter_matches(debug_filter, domain, level, \ LOG_LEVEL_DEBUG, debug_filter_positive) #define NOISY_FILTER_MATCHES(domain, level) \ level_filter_matches(noisy_filter, domain, level, \ LOG_LEVEL_NOISY, noisy_filter_positive) gboolean ws_log_message_is_active(const char *domain, enum ws_log_level level) { int action; if ((action = NOISY_FILTER_MATCHES(domain, level)) != CONTINUE) return action; if ((action = DEBUG_FILTER_MATCHES(domain, level)) != CONTINUE) return action; return log_level_is_active(level) && log_domain_is_active(domain); } enum ws_log_level ws_log_get_level(void) { return current_log_level; } enum ws_log_level ws_log_set_level(enum ws_log_level log_level) { ws_assert(log_level > LOG_LEVEL_NONE && log_level < _LOG_LEVEL_LAST); current_log_level = log_level; return current_log_level; } enum ws_log_level ws_log_set_level_str(const char *str_level) { enum ws_log_level level; level = string_to_log_level(str_level); if (level == LOG_LEVEL_NONE) return LOG_LEVEL_NONE; current_log_level = level; return current_log_level; } static const char *opt_level = "--log-level"; static const char *opt_domains = "--log-domains"; static const char *opt_file = "--log-file"; static const char *opt_fatal = "--log-fatal"; static const char *opt_debug = "--log-debug"; static const char *opt_noisy = "--log-noisy"; int ws_log_parse_args(int *argc_ptr, char *argv[], void (*print_err)(const char *, ...)) { char **ptr = argv; int count = *argc_ptr; int ret = 0; size_t optlen; const char *option, *value; int prune_extra; while (*ptr != NULL) { if (g_str_has_prefix(*ptr, opt_level)) { option = opt_level; optlen = strlen(opt_level); } else if (g_str_has_prefix(*ptr, opt_domains)) { option = opt_domains; optlen = strlen(opt_domains); } else if (g_str_has_prefix(*ptr, opt_file)) { option = opt_file; optlen = strlen(opt_file); } else if (g_str_has_prefix(*ptr, opt_fatal)) { option = opt_fatal; optlen = strlen(opt_fatal); } else if (g_str_has_prefix(*ptr, opt_debug)) { option = opt_debug; optlen = strlen(opt_debug); } else if (g_str_has_prefix(*ptr, opt_noisy)) { option = opt_noisy; optlen = strlen(opt_noisy); } else { ptr += 1; count -= 1; continue; } value = *ptr + optlen; /* Two possibilities: * --