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.
435 lines
13 KiB
435 lines
13 KiB
/** |
|
* Debug configuration implementation |
|
* Runtime debug configuration system for flexible debug output control |
|
*/ |
|
|
|
#include "debug_config.h" |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <errno.h> |
|
#include <ctype.h> |
|
#include <stdarg.h> |
|
#include <stdio.h> |
|
#include <time.h> |
|
#include <strings.h> |
|
|
|
/* Rate limiting */ |
|
static size_t output_count = 0; |
|
static time_t last_output_time = 0; |
|
|
|
/* ANSI color codes */ |
|
static const char* color_red = "\033[31m"; |
|
static const char* color_yellow = "\033[33m"; |
|
static const char* color_green = "\033[32m"; |
|
static const char* color_blue = "\033[34m"; |
|
static const char* color_magenta = "\033[35m"; |
|
static const char* color_cyan = "\033[36m"; |
|
static const char* color_reset = "\033[0m"; |
|
|
|
/* 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}, |
|
{"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; |
|
} |
|
|
|
/* Get level by name */ |
|
static debug_level_t get_level_by_name(const char* name) { |
|
struct { |
|
const char* n; |
|
debug_level_t l; |
|
} map[] = { |
|
{"none", DEBUG_LEVEL_NONE}, |
|
{"error", DEBUG_LEVEL_ERROR}, |
|
{"warn", DEBUG_LEVEL_WARN}, |
|
{"info", DEBUG_LEVEL_INFO}, |
|
{"debug", DEBUG_LEVEL_DEBUG}, |
|
{"trace", DEBUG_LEVEL_TRACE}, |
|
{NULL, 0} |
|
}; |
|
|
|
for (int i = 0; map[i].n; i++) { |
|
if (strcasecmp(map[i].n, name) == 0) { |
|
return map[i].l; |
|
} |
|
} |
|
return DEBUG_LEVEL_NONE; |
|
} |
|
|
|
/* Global debug configuration */ |
|
debug_config_t g_debug_config; |
|
FILE* debug_output_file = NULL; |
|
|
|
/* Forward declaration for global_config structure */ |
|
struct global_config { |
|
char log_file[256]; |
|
char debug_level[32]; |
|
uint32_t debug_categories; |
|
int enable_timestamp; |
|
int enable_function_names; |
|
int enable_file_lines; |
|
int enable_colors; |
|
}; |
|
|
|
/* Apply debug settings from configuration if not overridden by CLI */ |
|
void debug_apply_config(const struct global_config *config, int cli_override_level, int cli_override_categories) { |
|
if (!config) return; |
|
|
|
// Apply log file if specified and not overridden |
|
if (config->log_file[0] != '\0') { |
|
debug_set_output_file(config->log_file); |
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set log file to: %s", config->log_file); |
|
} |
|
|
|
// Apply debug level from config if not overridden by CLI |
|
if (!cli_override_level && config->debug_level[0] != '\0') { |
|
debug_level_t level = DEBUG_LEVEL_INFO; // default |
|
if (strcasecmp(config->debug_level, "error") == 0) level = DEBUG_LEVEL_ERROR; |
|
else if (strcasecmp(config->debug_level, "warn") == 0) level = DEBUG_LEVEL_WARN; |
|
else if (strcasecmp(config->debug_level, "info") == 0) level = DEBUG_LEVEL_INFO; |
|
else if (strcasecmp(config->debug_level, "debug") == 0) level = DEBUG_LEVEL_DEBUG; |
|
else if (strcasecmp(config->debug_level, "trace") == 0) level = DEBUG_LEVEL_TRACE; |
|
|
|
debug_set_level(level); |
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug level from config: %s", config->debug_level); |
|
} |
|
|
|
// Apply debug categories from config if not overridden by CLI |
|
if (!cli_override_categories && config->debug_categories != 0) { |
|
debug_set_categories(config->debug_categories); |
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug categories from config: 0x%X", config->debug_categories); |
|
} |
|
|
|
// Apply output options from config |
|
debug_enable_timestamp(config->enable_timestamp); |
|
debug_enable_function_name(config->enable_function_names); |
|
debug_enable_file_line(config->enable_file_lines); |
|
debug_enable_color(config->enable_colors); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied debug configuration from file"); |
|
} |
|
|
|
/* Initialize debug system with default settings */ |
|
void debug_config_init(void) { |
|
g_debug_config.default_level = DEBUG_LEVEL_ERROR; |
|
g_debug_config.categories = DEBUG_CATEGORY_ALL; |
|
memset(g_debug_config.category_levels, 0, sizeof(g_debug_config.category_levels)); |
|
g_debug_config.timestamp_enabled = 1; |
|
g_debug_config.function_name_enabled = 1; |
|
g_debug_config.file_line_enabled = 1; |
|
g_debug_config.color_enabled = 1; |
|
g_debug_config.max_output_per_second = 0; |
|
g_debug_config.output_file = NULL; |
|
|
|
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { |
|
fclose(debug_output_file); |
|
} |
|
debug_output_file = stdout; |
|
|
|
output_count = 0; |
|
last_output_time = 0; |
|
} |
|
|
|
/* Set debug level */ |
|
void debug_set_level(debug_level_t level) { |
|
g_debug_config.default_level = 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(uint32_t categories) { |
|
g_debug_config.categories = categories; |
|
} |
|
|
|
/* Configure output options */ |
|
void debug_enable_timestamp(int enable) { |
|
g_debug_config.timestamp_enabled = enable; |
|
} |
|
|
|
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_enable_color(int enable) { |
|
g_debug_config.color_enabled = enable; |
|
} |
|
|
|
void debug_set_rate_limit(size_t max_per_second) { |
|
g_debug_config.max_output_per_second = max_per_second; |
|
} |
|
|
|
void debug_set_output_file(const char* file_path) { |
|
if (file_path == NULL) { |
|
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { |
|
fclose(debug_output_file); |
|
} |
|
debug_output_file = stdout; |
|
g_debug_config.output_file = NULL; |
|
} else { |
|
FILE* new_file = fopen(file_path, "a"); |
|
if (new_file) { |
|
if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { |
|
fclose(debug_output_file); |
|
} |
|
debug_output_file = new_file; |
|
g_debug_config.output_file = file_path; |
|
} |
|
} |
|
} |
|
|
|
/* 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; |
|
} |
|
|
|
/* Check if level is sufficient */ |
|
debug_level_t eff_level = debug_get_effective_level(category); |
|
if (level > eff_level) { |
|
return 0; |
|
} |
|
|
|
/* Rate limiting check */ |
|
if (g_debug_config.max_output_per_second > 0) { |
|
time_t current_time = time(NULL); |
|
if (current_time != last_output_time) { |
|
output_count = 0; |
|
last_output_time = current_time; |
|
} |
|
|
|
if (output_count >= g_debug_config.max_output_per_second) { |
|
return 0; |
|
} |
|
output_count++; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
/* Get current debug level for a category */ |
|
debug_level_t debug_get_effective_level(debug_category_t category) { |
|
if (category == DEBUG_CATEGORY_ALL || category == DEBUG_CATEGORY_NONE) { |
|
return g_debug_config.default_level; |
|
} |
|
|
|
/* Assume single category bit */ |
|
uint32_t cat = category; |
|
if (__builtin_popcount(cat) != 1) { |
|
return DEBUG_LEVEL_NONE; /* Not supported for multiple */ |
|
} |
|
|
|
int index = __builtin_ctz(cat); |
|
if (index >= NUM_DEBUG_CATEGORIES) { |
|
return DEBUG_LEVEL_NONE; |
|
} |
|
|
|
debug_level_t lev = g_debug_config.category_levels[index]; |
|
return (lev == DEBUG_LEVEL_NONE) ? g_debug_config.default_level : lev; |
|
} |
|
|
|
/* Get color for debug level */ |
|
static const char* get_level_color(debug_level_t level) { |
|
if (!g_debug_config.color_enabled) { |
|
return ""; |
|
} |
|
|
|
switch (level) { |
|
case DEBUG_LEVEL_ERROR: return color_red; |
|
case DEBUG_LEVEL_WARN: return color_yellow; |
|
case DEBUG_LEVEL_INFO: return color_green; |
|
case DEBUG_LEVEL_DEBUG: return color_blue; |
|
case DEBUG_LEVEL_TRACE: return color_magenta; |
|
default: return ""; |
|
} |
|
} |
|
|
|
/* 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); |
|
|
|
FILE* output = debug_output_file ? debug_output_file : stdout; |
|
|
|
/* Add timestamp if enabled */ |
|
if (g_debug_config.timestamp_enabled) { |
|
time_t now = time(NULL); |
|
struct tm* tm_info = localtime(&now); |
|
char time_str[32]; |
|
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); |
|
offset += snprintf(buffer + offset, remaining, "[%s] ", time_str); |
|
remaining = BUFFER_SIZE - offset; |
|
} |
|
|
|
/* Add level and color */ |
|
const char* color = get_level_color(level); |
|
const char* level_name = get_level_name(level); |
|
if (g_debug_config.color_enabled) { |
|
offset += snprintf(buffer + offset, remaining, "%s[%s]%s ", color, level_name, color_reset); |
|
} else { |
|
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name); |
|
} |
|
remaining = BUFFER_SIZE - offset; |
|
|
|
/* Add category */ |
|
offset += snprintf(buffer + offset, remaining, "[%d] ", category); |
|
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 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 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 the entire line at once */ |
|
fprintf(output, "%s", buffer); |
|
fflush(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; |
|
} |
|
|
|
char* token = strtok(str, ","); |
|
while (token) { |
|
/* Trim leading spaces */ |
|
while (isspace(*token)) { |
|
token++; |
|
} |
|
|
|
char* colon = strchr(token, ':'); |
|
if (!colon) { |
|
free(str); |
|
return -1; |
|
} |
|
|
|
*colon = '\0'; |
|
char* cat_name = token; |
|
char* level_name = colon + 1; |
|
|
|
/* Trim leading spaces in level_name */ |
|
while (isspace(*level_name)) { |
|
level_name++; |
|
} |
|
|
|
/* Find category */ |
|
debug_category_t cat = get_category_by_name(cat_name); |
|
if (cat == DEBUG_CATEGORY_NONE) { |
|
free(str); |
|
return -1; |
|
} |
|
|
|
/* Find level */ |
|
debug_level_t lev = get_level_by_name(level_name); |
|
if (lev == DEBUG_LEVEL_NONE) { |
|
free(str); |
|
return -1; |
|
} |
|
|
|
if (cat == DEBUG_CATEGORY_ALL) { |
|
g_debug_config.default_level = lev; |
|
} else { |
|
g_debug_config.categories |= cat; |
|
int index = __builtin_ctz(cat); |
|
if (index < NUM_DEBUG_CATEGORIES) { |
|
g_debug_config.category_levels[index] = lev; |
|
} |
|
} |
|
|
|
token = strtok(NULL, ","); |
|
} |
|
|
|
free(str); |
|
return 0; |
|
} |
|
|
|
/* Apply debug settings from configuration string values */ |
|
void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories, |
|
int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors, |
|
int cli_override_level, int cli_override_categories) { |
|
// This function would be implemented to apply individual config values |
|
// For now, it's a placeholder that could be expanded based on specific needs |
|
} |