You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
12 KiB
401 lines
12 KiB
/** |
|
* Debug module |
|
*/ |
|
|
|
#include "debug_config.h" |
|
#include "platform_compat.h" |
|
#include "mem.h" |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <errno.h> |
|
#include <ctype.h> |
|
#include <stdarg.h> |
|
#include <stdio.h> |
|
#include <time.h> |
|
|
|
void log_dump(const char* prefix, const uint8_t* data, size_t len) { |
|
char hex_buf[513] = {'\0'}; |
|
size_t hex_len = 0; |
|
size_t show_len = (len > 128) ? 128 : len; |
|
|
|
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) { |
|
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]); |
|
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes |
|
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " "); |
|
} |
|
} |
|
|
|
if (len > 128) { |
|
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "..."); |
|
} |
|
|
|
// Single-line debug output with packet info |
|
DEBUG_INFO(DEBUG_CATEGORY_DUMP, "%s: len=%zu hex=%s", prefix, len, hex_buf); |
|
|
|
// Additional debug info for first few bytes |
|
// if (len >= 2) { |
|
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x", |
|
// prefix, data[0], data[1], data[len-2], data[len-1]); |
|
// } |
|
} |
|
|
|
|
|
ip_str_t ip_to_str(const void *addr, int family) { |
|
ip_str_t result; |
|
result.str[0] = '\0'; |
|
|
|
if (!addr) { |
|
return result; |
|
} |
|
|
|
if (family == AF_INET) { |
|
inet_ntop(AF_INET, addr, result.str, 16); |
|
} else if (family == AF_INET6) { |
|
inet_ntop(AF_INET6, addr, result.str, 48); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
/* Get category by name */ |
|
static debug_category_t get_category_by_name(const char* name) { |
|
struct { |
|
const char* n; |
|
debug_category_t c; |
|
} map[] = { |
|
{"none", DEBUG_CATEGORY_NONE}, |
|
{"uasync", DEBUG_CATEGORY_UASYNC}, |
|
{"ll_queue", DEBUG_CATEGORY_LL_QUEUE}, |
|
{"connection", DEBUG_CATEGORY_CONNECTION}, |
|
{"etcp", DEBUG_CATEGORY_ETCP}, |
|
{"crypto", DEBUG_CATEGORY_CRYPTO}, |
|
{"memory", DEBUG_CATEGORY_MEMORY}, |
|
{"timing", DEBUG_CATEGORY_TIMING}, |
|
{"config", DEBUG_CATEGORY_CONFIG}, |
|
{"tun", DEBUG_CATEGORY_TUN}, |
|
{"routing", DEBUG_CATEGORY_ROUTING}, |
|
{"timers", DEBUG_CATEGORY_TIMERS}, |
|
{"normalizer", DEBUG_CATEGORY_NORMALIZER}, |
|
{"bgp", DEBUG_CATEGORY_BGP}, |
|
{"socket", DEBUG_CATEGORY_SOCKET}, |
|
{"control", DEBUG_CATEGORY_CONTROL}, |
|
{"dump", DEBUG_CATEGORY_DUMP}, |
|
{"all", DEBUG_CATEGORY_ALL}, |
|
{NULL, 0} |
|
}; |
|
|
|
for (int i = 0; map[i].n; i++) { |
|
if (strcasecmp(map[i].n, name) == 0) { |
|
return map[i].c; |
|
} |
|
} |
|
return DEBUG_CATEGORY_NONE; |
|
} |
|
|
|
/* Global debug configuration */ |
|
debug_config_t g_debug_config; |
|
|
|
/* Initialize debug system with default settings */ |
|
void debug_config_init(void) { |
|
g_debug_config.level = DEBUG_LEVEL_ERROR; // Global level: errors only by default |
|
g_debug_config.categories = DEBUG_CATEGORY_ALL; |
|
for (int i = 0; i < DEBUG_CATEGORY_COUNT; i++) { |
|
g_debug_config.category_levels[i] = DEBUG_LEVEL_NONE; // 0 = disabled per category |
|
} |
|
g_debug_config.timestamp_enabled = 1; |
|
g_debug_config.function_name_enabled = 1; |
|
g_debug_config.file_line_enabled = 1; |
|
g_debug_config.output_file = NULL; |
|
|
|
// Dual output defaults |
|
g_debug_config.file_output = NULL; // No file by default |
|
g_debug_config.file_level = DEBUG_LEVEL_TRACE; // File gets everything |
|
g_debug_config.console_level = DEBUG_LEVEL_INFO; // Console gets INFO+ |
|
g_debug_config.console_enabled = 1; // Console enabled |
|
} |
|
|
|
/* Set debug level */ |
|
void debug_set_level(debug_level_t level) { |
|
g_debug_config.level = level; |
|
g_debug_config.console_level = level; |
|
} |
|
|
|
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */ |
|
void debug_set_category_level(debug_category_t category, debug_level_t level) { |
|
if (category == DEBUG_CATEGORY_NONE) return; |
|
int idx = 0; |
|
while (!(category & (1ULL << idx)) && idx < DEBUG_CATEGORY_COUNT) { |
|
idx++; |
|
} |
|
if (idx < DEBUG_CATEGORY_COUNT) { |
|
g_debug_config.category_levels[idx] = level; |
|
} |
|
} |
|
|
|
/* Enable/disable specific categories */ |
|
void debug_enable_category(debug_category_t category) { |
|
g_debug_config.categories |= category; |
|
} |
|
|
|
void debug_disable_category(debug_category_t category) { |
|
g_debug_config.categories &= ~category; |
|
} |
|
|
|
void debug_set_categories(debug_category_t categories) { |
|
g_debug_config.categories = categories; |
|
} |
|
|
|
/* Set masks directly */ |
|
void debug_set_masks(debug_category_t categories, debug_level_t level) { |
|
g_debug_config.categories = categories; |
|
g_debug_config.level = level; |
|
} |
|
|
|
void debug_enable_function_name(int enable) { |
|
g_debug_config.function_name_enabled = enable; |
|
} |
|
|
|
void debug_enable_file_line(int enable) { |
|
g_debug_config.file_line_enabled = enable; |
|
} |
|
|
|
void debug_set_output_file(const char* file_path) { |
|
if (g_debug_config.file_output) return;// уже установлен, кто первый того и тапки |
|
FILE* new_file = fopen(file_path, "a"); |
|
if (new_file) { |
|
g_debug_config.file_output = new_file; |
|
g_debug_config.output_file = file_path; |
|
} |
|
} |
|
|
|
/* Set console log level */ |
|
void debug_set_console_level(debug_level_t level) { |
|
g_debug_config.console_level = level; |
|
} |
|
|
|
/* Set file log level */ |
|
void debug_set_file_level(debug_level_t level) { |
|
g_debug_config.file_level = level; |
|
} |
|
|
|
/* Enable/disable console output */ |
|
void debug_enable_console(int enable) { |
|
g_debug_config.console_enabled = enable; |
|
} |
|
|
|
/* Enable file output with optional truncation */ |
|
void debug_enable_file_output(const char* file_path, int truncate) { |
|
if (!file_path) return; |
|
|
|
// Close existing file if open |
|
if (g_debug_config.file_output) { |
|
fclose(g_debug_config.file_output); |
|
g_debug_config.file_output = NULL; |
|
} |
|
|
|
// Open new file |
|
const char* mode = truncate ? "w" : "a"; |
|
FILE* new_file = fopen(file_path, mode); |
|
if (new_file) { |
|
g_debug_config.file_output = new_file; |
|
g_debug_config.output_file = file_path; |
|
g_debug_config.console_enabled = 0; |
|
} |
|
} |
|
|
|
/* Disable file output */ |
|
void debug_disable_file_output(void) { |
|
if (g_debug_config.file_output) { |
|
fclose(g_debug_config.file_output); |
|
g_debug_config.file_output = NULL; |
|
g_debug_config.output_file = NULL; |
|
} |
|
} |
|
|
|
/* Check if debug output should be shown for given level and category */ |
|
int debug_should_output(debug_level_t level, debug_category_t category) { |
|
|
|
/* Check if category is enabled */ |
|
if (!(g_debug_config.categories & category)) { |
|
return 0; |
|
} |
|
|
|
/* Determine effective level: max(global, category) */ |
|
int idx = 0; |
|
while (!(category & (1ULL << idx)) && idx < DEBUG_CATEGORY_COUNT) { |
|
idx++; |
|
} |
|
debug_level_t cat_level = (idx < DEBUG_CATEGORY_COUNT) ? g_debug_config.category_levels[idx] : DEBUG_LEVEL_NONE; |
|
debug_level_t effective = (cat_level > g_debug_config.level) ? cat_level : g_debug_config.level; |
|
|
|
/* Check if level is sufficient */ |
|
if (level > effective) { |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
/* Get current debug level */ |
|
debug_level_t debug_get_level(void) { |
|
return g_debug_config.level; |
|
} |
|
|
|
/* Get level name */ |
|
static const char* get_level_name(debug_level_t level) { |
|
switch (level) { |
|
case DEBUG_LEVEL_ERROR: return "ERROR"; |
|
case DEBUG_LEVEL_WARN: return "WARN"; |
|
case DEBUG_LEVEL_INFO: return "INFO"; |
|
case DEBUG_LEVEL_DEBUG: return "DEBUG"; |
|
case DEBUG_LEVEL_TRACE: return "TRACE"; |
|
default: return "UNKNOWN"; |
|
} |
|
} |
|
|
|
/* Format and output debug message */ |
|
void debug_output(debug_level_t level, debug_category_t category, |
|
const char* function, const char* file, int line, |
|
const char* format, ...) { |
|
|
|
#define BUFFER_SIZE 4096 |
|
char buffer[BUFFER_SIZE]; |
|
int offset = 0; |
|
size_t remaining = BUFFER_SIZE; |
|
|
|
va_list args; |
|
va_start(args, format); |
|
|
|
/* Add timestamp with microseconds: hh:mm:ss-xxx.yyy */ |
|
struct timeval tv; |
|
gettimeofday(&tv, NULL); |
|
time_t tv_sec = (time_t)tv.tv_sec; |
|
struct tm* tm_info = localtime(&tv_sec); |
|
char time_str[32]; |
|
strftime(time_str, sizeof(time_str), "%H:%M:%S", tm_info); |
|
offset += snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000); |
|
remaining = BUFFER_SIZE - offset; |
|
|
|
/* Add level */ |
|
const char* level_name = get_level_name(level); |
|
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name); |
|
remaining = BUFFER_SIZE - offset; |
|
|
|
/* Add category */ |
|
offset += snprintf(buffer + offset, remaining, "[%llu] ", (unsigned long long)category); |
|
remaining = BUFFER_SIZE - offset; |
|
|
|
/* Add file:line if enabled */ |
|
if (g_debug_config.file_line_enabled && file) { |
|
offset += snprintf(buffer + offset, remaining, "(%s:%d) ", file, line); |
|
remaining = BUFFER_SIZE - offset; |
|
} |
|
|
|
/* Add function name if enabled */ |
|
if (g_debug_config.function_name_enabled && function) { |
|
offset += snprintf(buffer + offset, remaining, "%s() ", function); |
|
remaining = BUFFER_SIZE - offset; |
|
} |
|
|
|
/* Add the actual message */ |
|
offset += vsnprintf(buffer + offset, remaining, format, args); |
|
remaining = BUFFER_SIZE - offset; |
|
|
|
/* Add newline */ |
|
if (remaining > 1) { |
|
buffer[offset] = '\n'; |
|
buffer[offset + 1] = '\0'; |
|
} else { |
|
buffer[BUFFER_SIZE - 2] = '\n'; |
|
buffer[BUFFER_SIZE - 1] = '\0'; |
|
} |
|
|
|
va_end(args); |
|
|
|
/* Output to console if enabled and level is sufficient */ |
|
if (g_debug_config.console_enabled && level <= g_debug_config.console_level) { |
|
fprintf(stdout, "%s", buffer); |
|
fflush(stdout); |
|
} |
|
|
|
/* Output to file if open and level is sufficient */ |
|
if (g_debug_config.file_output && level <= g_debug_config.file_level) { |
|
fprintf(g_debug_config.file_output, "%s", buffer); |
|
fflush(g_debug_config.file_output); |
|
} |
|
#undef BUFFER_SIZE |
|
} |
|
|
|
/* Parse debug configuration from string */ |
|
int debug_parse_config(const char* config_string) { |
|
if (!config_string || !*config_string) { |
|
return 0; |
|
} |
|
|
|
char* str = strdup(config_string); |
|
if (!str) { |
|
return -1; |
|
} |
|
|
|
debug_category_t categories = 0; |
|
debug_level_t level = DEBUG_LEVEL_NONE; |
|
|
|
char* token = strtok(str, ";"); |
|
while (token) { |
|
/* Trim leading spaces */ |
|
while (isspace(*token)) { |
|
token++; |
|
} |
|
|
|
char* eq = strchr(token, '='); |
|
if (!eq) { |
|
u_free(str); |
|
return -1; |
|
} |
|
|
|
*eq = '\0'; |
|
char* key = token; |
|
char* value = eq + 1; |
|
|
|
/* Trim leading spaces in value */ |
|
while (isspace(*value)) { |
|
value++; |
|
} |
|
|
|
if (strcasecmp(key, "modules") == 0) { |
|
char* mod_token = strtok(value, ","); |
|
while (mod_token) { |
|
/* Trim leading spaces */ |
|
while (isspace(*mod_token)) { |
|
mod_token++; |
|
} |
|
|
|
debug_category_t cat = get_category_by_name(mod_token); |
|
if (cat == DEBUG_CATEGORY_NONE) { |
|
u_free(str); |
|
return -1; |
|
} |
|
categories |= cat; |
|
mod_token = strtok(NULL, ","); |
|
} |
|
} else if (strcasecmp(key, "level") == 0) { |
|
int lev_num = atoi(value); |
|
if (lev_num < DEBUG_LEVEL_NONE || lev_num > DEBUG_LEVEL_TRACE) { |
|
u_free(str); |
|
return -1; |
|
} |
|
level = (debug_level_t)lev_num; |
|
} else { |
|
u_free(str); |
|
return -1; |
|
} |
|
|
|
token = strtok(NULL, ";"); |
|
} |
|
|
|
debug_set_masks(categories, level); |
|
|
|
u_free(str); |
|
return 0; |
|
}
|
|
|