// Copyright (C) 2007 EQ2EMulator Development Team - GPL v3 #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "types.hpp" #include "xml_parser.hpp" #include "../WorldServer/World.h" #include "../WorldServer/client.h" #include "../WorldServer/zoneserver.h" extern ZoneList zone_list; #define LOG_BUFFER_SIZE 4096 // Log output destinations #define LOG_LOGFILE 1 #define LOG_CONSOLE 2 #define LOG_CLIENT 4 // Console color definitions using ANSI escape codes #define FOREGROUND_WHITE 37 #define FOREGROUND_WHITE_BOLD 137 #define FOREGROUND_RED 31 #define FOREGROUND_RED_BOLD 131 #define FOREGROUND_GREEN 32 #define FOREGROUND_GREEN_BOLD 132 #define FOREGROUND_BLUE 34 #define FOREGROUND_BLUE_BOLD 134 #define FOREGROUND_YELLOW 33 #define FOREGROUND_YELLOW_BOLD 133 #define FOREGROUND_CYAN 36 #define FOREGROUND_CYAN_BOLD 136 #define FOREGROUND_MAGENTA 35 #define FOREGROUND_MAGENTA_BOLD 135 #define LOG_CATEGORY(category) LOG_ ##category , enum LogCategory { #include "log_types.hpp" NUMBER_OF_LOG_CATEGORIES }; #define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) category##__##type , enum LogType { #include "log_types.hpp" NUMBER_OF_LOG_TYPES }; #define LOG_CATEGORY(category) #category, const char *log_category_names[NUMBER_OF_LOG_CATEGORIES] = { #include "log_types.hpp" }; // Structure to hold log type configuration and status struct LogTypeStatus { int8_t level; // Minimum log level for this type int color; // Console color code bool enabled; // Whether this log type is active bool logfile; // Write to log file bool console; // Write to console bool client; // Send to connected clients LogCategory category; // Category this log type belongs to const char *name; // Internal name identifier const char *display_name; // Human-readable display name }; #define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) { level, color, enabled, logfile, console, client, LOG_ ##category, #category "__" #type, ( strlen(str)>0 ) ? str : #category "__" #type }, static LogTypeStatus real_log_type_info[NUMBER_OF_LOG_TYPES+1] = { #include "log_types.hpp" { 0, 0, false, false, false, false, NUMBER_OF_LOG_CATEGORIES, "BAD TYPE", "Bad Name" } /* dummy trailing record */ }; LogTypeStatus *log_type_info = real_log_type_info; // Logging system configuration constants #define LOG_CYCLE 100 // milliseconds between each batch of log writes #define LOGS_PER_CYCLE 50 // amount of logs to write per cycle #define LOG_DIR "logs" // directory for log files #define DATE_MAX 8 // maximum date string length #define LOG_NAME_MAX 32 // maximum log name length // Determine executable name for log file naming #if defined LOGIN #define EXE_NAME "login" #elif defined WORLD #define EXE_NAME "world" #elif defined PARSER #define EXE_NAME "parser" #elif defined PATCHER #define EXE_NAME "patcher" #else #define EXE_NAME "unknown" #endif // Structure for queued log entries in doubly-linked list struct logq_t { LogType log_type; // Type of log entry char date[DATE_MAX + 1]; // Timestamp string char name[LOG_NAME_MAX + 1]; // Log category name char *text; // Log message text struct logq_t *next; // Next entry in queue struct logq_t *prev; // Previous entry in queue }; // Global logging system state static logq_t head; // Head of log queue static logq_t tail; // Tail of log queue static std::atomic num_logqs{0}; // Number of queued logs static std::mutex log_mutex; // Mutex for log queue access static std::atomic looping{false}; // Thread loop control static std::atomic start_called{false}; // Initialization flag extern const char* log_category_names[NUMBER_OF_LOG_CATEGORIES]; extern LogTypeStatus* log_type_info; // Function declarations void LogStart(); void LogStop(); int8_t GetLoggerLevel(LogType type); void LogWrite(LogType type, int8_t log_level, const char *cat_text, const char *fmt, ...); bool LogParseConfigs(); #ifdef PARSER void ColorizeLog(int color, char *date, const char *display_name, const char *category, std::string buffer); #endif // Set console text color using ANSI escape codes static void SetConsoleColor(int color) { switch (color) { case FOREGROUND_WHITE: case FOREGROUND_WHITE_BOLD: case FOREGROUND_RED: case FOREGROUND_RED_BOLD: case FOREGROUND_GREEN: case FOREGROUND_GREEN_BOLD: case FOREGROUND_BLUE: case FOREGROUND_BLUE_BOLD: case FOREGROUND_YELLOW: case FOREGROUND_YELLOW_BOLD: case FOREGROUND_CYAN: case FOREGROUND_CYAN_BOLD: case FOREGROUND_MAGENTA: case FOREGROUND_MAGENTA_BOLD: printf("\033[%i;%i;40m", color > 100 ? 1 : 0, color > 100 ? color - 100 : color); break; default: printf("\033[0;37;40m"); break; } } // Open a log file for writing with date-based naming static FILE * OpenLogFile() { char file[FILENAME_MAX + 1]; struct stat st; struct tm *tm; time_t now; FILE *f; now = time(nullptr); tm = localtime(&now); // Create logs directory if it doesn't exist if (stat(LOG_DIR, &st) != 0) { if (mkdir(LOG_DIR, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { fprintf(stderr, "Unable to create directory '%s': %s\n", LOG_DIR, strerror(errno)); return stderr; } } // Generate filename with date and process ID #ifdef NO_PIDLOG snprintf(file, FILENAME_MAX, LOG_DIR"/%04i-%02i-%02i_eq2" EXE_NAME ".log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); #else snprintf(file, FILENAME_MAX, LOG_DIR"/%04i-%02i-%02i_eq2" EXE_NAME "_%04i.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, getpid()); #endif if ((f = fopen(file, "a")) == nullptr) { fprintf(stderr, "Could not open '%s' for writing: %s\n", file, strerror(errno)); return stderr; } return f; } // Write a batch of queued log entries to their destinations static void WriteQueuedLogs(int count) { logq_t pending_head, pending_tail, *logq, *tmp; int i = 0; FILE *f; // Initialize temporary list for processing pending_head.next = &pending_tail; pending_tail.prev = &pending_head; // Move logs from main queue to temporary list for processing { std::lock_guard lock(log_mutex); while (head.next != &tail) { // Remove from main list logq = head.next; logq->next->prev = &head; head.next = logq->next; // Add to temporary list tmp = pending_tail.prev; tmp->next = logq; logq->prev = tmp; logq->next = &pending_tail; pending_tail.prev = logq; --num_logqs; // Check count limit if (count > 0 && ++i == count) break; } } // Process logs from temporary list logq = pending_head.next; if (logq == &pending_tail) return; while (logq != &pending_tail) { // Write to console if enabled if (log_type_info[logq->log_type].console) { SetConsoleColor(FOREGROUND_WHITE_BOLD); printf("%s ", logq->date); SetConsoleColor(log_type_info[logq->log_type].color); printf("%s ", log_type_info[logq->log_type].display_name); SetConsoleColor(FOREGROUND_WHITE_BOLD); printf("%-10s: ", logq->name); SetConsoleColor(log_type_info[logq->log_type].color); printf("%s\n", logq->text); SetConsoleColor(-1); fflush(stdout); } // Write to log file if enabled if (log_type_info[logq->log_type].logfile) { f = OpenLogFile(); if (f != stderr || (f == stderr && !log_type_info[logq->log_type].console)) { fprintf(f, "%s %s %s: %s\n", logq->date, log_type_info[logq->log_type].display_name, logq->name, logq->text); fflush(f); if (f != stderr) fclose(f); } } #if defined WORLD // Send to subscribed clients if enabled if (log_type_info[logq->log_type].client) { // TODO: Implement client logging subscription system } #endif // Move to next log entry and clean up current tmp = logq; logq = logq->next; free(tmp->text); free(tmp); } } // Main logging thread function - processes queued logs continuously void LogLoop() { while (looping.load()) { WriteQueuedLogs(LOGS_PER_CYCLE); std::this_thread::sleep_for(std::chrono::milliseconds(LOG_CYCLE)); } } // Initialize the logging system and start the logging thread void LogStart() { if (start_called.load()) return; // Initialize doubly-linked list head.prev = nullptr; head.next = &tail; tail.prev = &head; tail.next = nullptr; looping.store(true); // Start logging thread std::thread log_thread(LogLoop); log_thread.detach(); start_called.store(true); } // Stop the logging system and flush remaining logs void LogStop() { looping.store(false); WriteQueuedLogs(-1); // Write all remaining logs start_called.store(false); } // Add a log entry to the queue for processing static void LogQueueAdd(LogType log_type, char *text, int len, const char *cat_text = nullptr) { logq_t *logq; struct tm *tm; time_t now; // Allocate log queue entry if ((logq = static_cast(calloc(1, sizeof(logq_t)))) == nullptr) { free(text); fprintf(stderr, "%s: %u: Unable to allocate %zu bytes\n", __FUNCTION__, __LINE__, sizeof(logq_t)); return; } // Allocate text buffer if ((logq->text = static_cast(calloc(len + 1, sizeof(char)))) == nullptr) { free(text); free(logq); fprintf(stderr, "%s: %u: Unable to allocate %i bytes\n", __FUNCTION__, __LINE__, len + 1); return; } // Set timestamp now = time(nullptr); tm = localtime(&now); // Initialize log entry logq->log_type = log_type; snprintf(logq->date, DATE_MAX + 1, "%02i:%02i:%02i", tm->tm_hour, tm->tm_min, tm->tm_sec); strncpy(logq->name, cat_text == nullptr || cat_text[0] == '\0' ? log_type_info[log_type].name : cat_text, LOG_NAME_MAX); strncpy(logq->text, text, len); free(text); // Ensure logging system is started if (!start_called.load()) LogStart(); // Add to queue { std::lock_guard lock(log_mutex); tail.prev->next = logq; logq->prev = tail.prev; logq->next = &tail; tail.prev = logq; ++num_logqs; } } // Get the minimum log level for a specific log type int8_t GetLoggerLevel(LogType type) { return log_type_info[type].level; } #ifndef PARSER // Main logging function - formats message and queues for output void LogWrite(LogType type, int8_t log_level, const char *cat_text, const char *fmt, ...) { int count, size = 64; char *buf; va_list ap; // Check if logging is enabled and level is appropriate if (!log_type_info[type].enabled || (log_level > 0 && log_type_info[type].level < log_level)) return; // Dynamically allocate buffer for formatted message while (true) { if ((buf = static_cast(malloc(size))) == nullptr) { fprintf(stderr, "%s: %i: Unable to allocate %i bytes\n", __FUNCTION__, __LINE__, size); return; } va_start(ap, fmt); count = vsnprintf(buf, size, fmt, ap); va_end(ap); if (count > -1 && count < size) break; free(buf); if (count > 1) size = count + 1; else size *= 2; } LogQueueAdd(type, buf, count, cat_text); } #else // Parser-specific logging function - writes directly without queueing void LogWrite(LogType type, int8_t log_level, const char *cat_text, const char *format, ...) { // Check if logging should proceed if (!format || !log_type_info[type].enabled || (log_level > 0 && log_type_info[type].level < log_level)) return; time_t clock; struct tm *tm; char buffer[LOG_BUFFER_SIZE], date[32]; va_list args; FILE *f; size_t cat_text_len = 0; memset(buffer, 0, sizeof(buffer)); memset(date, 0, sizeof(date)); // Format the log message va_start(args, format); vsnprintf(buffer, sizeof(buffer) - 1, format, args); va_end(args); // Generate timestamp time(&clock); tm = localtime(&clock); snprintf(date, sizeof(date)-1, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); cat_text_len = strlen(cat_text); // Write to log file if enabled if (log_type_info[type].logfile) { char exename[200] = ""; #ifdef LOGIN snprintf(exename, sizeof(exename), "login"); #elif defined WORLD snprintf(exename, sizeof(exename), "world"); #elif defined PARSER snprintf(exename, sizeof(exename), "parser"); #elif defined PATCHER snprintf(exename, sizeof(exename), "patcher"); #endif char filename[200], log_header[200] = ""; // Generate filename with or without PID #ifndef NO_PIDLOG snprintf(filename, sizeof(filename)-1, "logs/%04d-%02d-%02d_eq2%s_%04i.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, exename, getpid()); #else snprintf(filename, sizeof(filename)-1, "logs/%04d-%02d-%02d_eq2%s.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, exename); #endif // Check if new log file and create header f = fopen(filename, "r"); if (!f) snprintf(log_header, sizeof(log_header), "===[ New log '%s' started ]===\n\n", filename); else fclose(f); // Write log entry f = fopen(filename, "a"); if (f) { if (strlen(log_header) > 0) fprintf(f, "%s\n", log_header); fprintf(f, "%s %s %s: %s\n", date, log_type_info[type].display_name, cat_text, buffer); fclose(f); } } // Write to console if enabled if (log_type_info[type].console) { printf("%s %s %s: %s\n", date, log_type_info[type].display_name, cat_text_len == 0 ? log_type_info[type].name : cat_text, buffer); } } // Colorize console output for parser (simplified for Linux) void ColorizeLog(int color, char *date, const char *display_name, const char *category, std::string buffer) { printf("%s ", date); SetConsoleColor(color); printf("%s ", display_name); SetConsoleColor(FOREGROUND_WHITE_BOLD); printf("%s: ", category); SetConsoleColor(color); printf("%s\n", buffer.c_str()); SetConsoleColor(FOREGROUND_WHITE); } #endif // Find log type status by category and type name LogTypeStatus* GetLogTypeStatus(const char *category, const char *type) { char combined[256]; int i; memset(combined, 0, sizeof(combined)); snprintf(combined, sizeof(combined) - 1, "%s__%s", category, type); for (i = 0; i < NUMBER_OF_LOG_TYPES; i++) { if (strcasecmp(log_type_info[i].name, combined) == 0) return &log_type_info[i]; } return &log_type_info[NUMBER_OF_LOG_TYPES]; } // Process XML configuration for a log category void ProcessLogConfig(XMLNode node) { int i; const char *category, *type, *level, *color, *enabled, *logs; LogTypeStatus *lfs; XMLNode child; // Get category attribute category = node.getAttribute("Category"); if (!category) { LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing a Category"); return; } // Process each ConfigType child node for (i = 0; i < node.nChildNode("ConfigType"); i++) { child = node.getChildNode("ConfigType", i); type = child.getAttribute("Type"); if (!type) { LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing a Type"); continue; } // Get log type status and configuration attributes lfs = GetLogTypeStatus(category, type); level = child.getAttribute("Level"); enabled = child.getAttribute("Enabled"); color = child.getAttribute("Color"); logs = child.getAttribute("Logs"); // Validate logs attribute if (!logs) { LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing 'Logs' attribute to specify which log(s) to write to"); continue; } if (!IsNumber(logs)) { LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Attribute 'Logs' must be a number. See LogTypes.h for the valid types."); continue; } // Set enabled status if (enabled) { if (!strcasecmp("true", enabled) || !strcasecmp("on", enabled)) lfs->enabled = true; else if (!strcasecmp("false", enabled) || !strcasecmp("off", enabled)) lfs->enabled = false; else LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Log setting 'Enabled' has invalid value '%s'. " "'true'/'on' or 'false'/'off' are valid values", enabled); } // Set log level if (IsNumber(level)) lfs->level = atoi(level); else lfs->level = 0; // Set color if (color) { if (IsNumber(color)) lfs->color = atoi(color); else if (!strcasecmp("White", color)) lfs->color = FOREGROUND_WHITE; else if (!strcasecmp("Green", color)) lfs->color = FOREGROUND_GREEN; else if (!strcasecmp("Yellow", color)) lfs->color = FOREGROUND_YELLOW; else if (!strcasecmp("Red", color)) lfs->color = FOREGROUND_RED; else if (!strcasecmp("Blue", color)) lfs->color = FOREGROUND_BLUE; else if (!strcasecmp("Cyan", color)) lfs->color = FOREGROUND_CYAN; else if (!strcasecmp("Magenta", color)) lfs->color = FOREGROUND_MAGENTA; else if (!strcasecmp("WhiteBold", color)) lfs->color = FOREGROUND_WHITE_BOLD; else if (!strcasecmp("GreenBold", color)) lfs->color = FOREGROUND_GREEN_BOLD; else if (!strcasecmp("YellowBold", color)) lfs->color = FOREGROUND_YELLOW_BOLD; else if (!strcasecmp("RedBold", color)) lfs->color = FOREGROUND_RED_BOLD; else if (!strcasecmp("BlueBold", color)) lfs->color = FOREGROUND_BLUE_BOLD; else if (!strcasecmp("CyanBold", color)) lfs->color = FOREGROUND_CYAN_BOLD; else if (!strcasecmp("MagentaBold", color)) lfs->color = FOREGROUND_MAGENTA_BOLD; else LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Log setting 'Color' has invalid value '%s'", color); } // Set output destinations lfs->logfile = (atoi(logs) & LOG_LOGFILE); lfs->console = (atoi(logs) & LOG_CONSOLE); lfs->client = (atoi(logs) & LOG_CLIENT); } } // Parse log configuration from XML file bool LogParseConfigs() { XMLNode main_node; int i; main_node = XMLNode::openFileHelper("log_config.xml", "EQ2EmuLogConfigs"); if (main_node.isEmpty()) { LogWrite(MISC__WARNING, 0, "Misc", "Unable to parse the file 'log_config.xml' or it does not exist. Default values will be used"); return false; } // Process each LogConfig node for (i = 0; i < main_node.nChildNode("LogConfig"); i++) ProcessLogConfig(main_node.getChildNode("LogConfig", i)); return true; }