// Copyright (C) 2007 EQ2EMulator Development Team - GPL v3 License #pragma once #include #include #include #include #include #include #include #include // Debug level configuration - controls verbosity of logging output #ifndef EQDEBUG #define EQDEBUG 1 #endif #ifndef ThrowError void CatchSignal(int); #if defined(CATCH_CRASH) || defined(_EQDEBUG) #define ThrowError(errstr) { std::cout << "Fatal error: " << errstr << " (" << __FILE__ << ", line " << __LINE__ << ")" << std::endl; LogWrite(WORLD__ERROR, 0, "Debug", "Thrown Error: %s (%s:%i)", errstr, __FILE__, __LINE__); throw errstr; } #else #define ThrowError(errstr) { std::cout << "Fatal error: " << errstr << " (" << __FILE__ << ", line " << __LINE__ << ")" << std::endl; LogWrite(WORLD__ERROR, 0, "Debug", "Thrown Error: %s (%s:%i)", errstr, __FILE__, __LINE__); CatchSignal(0); } #endif #endif /** * Main logging system for EQ2Emulator * Provides file and console logging with multiple log levels and categories */ class EQEMuLog { public: // Log category enumeration - defines different types of log messages enum LogIDs { Status = 0, // Server status messages (must stay first) Normal, // General operational messages Error, // Error conditions and failures Debug, // Debug information for development Quest, // Quest system specific messages Commands, // Player command execution logs MaxLogID // Total number of log categories }; // Callback function types for custom log handling using msgCallbackBuf = std::function; using msgCallbackFmt = std::function; /** * Constructor - initializes logging system with default settings * Sets up file pointers, log status flags, and callback handlers */ EQEMuLog() { for (int i = 0; i < MaxLogID; i++) { fp[i] = nullptr; #if EQDEBUG >= 2 pLogStatus[i] = 1 | 2; // Enable file and console output for debug builds #else pLogStatus[i] = 0; // Disable logging for release builds #endif logCallbackFmt[i] = nullptr; logCallbackBuf[i] = nullptr; } #if EQDEBUG < 2 // Configure specific log levels for release builds pLogStatus[Status] = 3; // File + console pLogStatus[Error] = 3; // File + console pLogStatus[Debug] = 3; // File + console pLogStatus[Quest] = 2; // Console only pLogStatus[Commands] = 2; // Console only #endif } /** * Destructor - closes all open log files * Ensures proper cleanup of file handles */ ~EQEMuLog() { for (int i = 0; i < MaxLogID; i++) { if (fp[i]) { fp[i]->close(); delete fp[i]; } } } /** * Sets callback function for all log categories (formatted version) * @param proc Callback function to handle formatted log messages */ void SetAllCallbacks(msgCallbackFmt proc) { for (int r = Status; r < MaxLogID; r++) { SetCallback(static_cast(r), proc); } } /** * Sets callback function for all log categories (buffer version) * @param proc Callback function to handle raw buffer log messages */ void SetAllCallbacks(msgCallbackBuf proc) { for (int r = Status; r < MaxLogID; r++) { SetCallback(static_cast(r), proc); } } /** * Sets callback function for specific log category (formatted version) * @param id Log category to set callback for * @param proc Callback function to handle formatted messages */ void SetCallback(LogIDs id, msgCallbackFmt proc) { if (id >= MaxLogID) { return; } logCallbackFmt[id] = proc; } /** * Sets callback function for specific log category (buffer version) * @param id Log category to set callback for * @param proc Callback function to handle buffer messages */ void SetCallback(LogIDs id, msgCallbackBuf proc) { if (id >= MaxLogID) { return; } logCallbackBuf[id] = proc; } /** * Writes raw buffer data to log with timestamp * @param id Log category to write to * @param buf Raw data buffer to write * @param size Size of each element in buffer * @param count Number of elements to write * @return True if write was successful */ bool writebuf(LogIDs id, const char *buf, int8_t size, int32_t count) { if (id >= MaxLogID) { return false; } bool dofile = false; if (pLogStatus[id] & 1) { dofile = open(id); } if (!(dofile || pLogStatus[id] & 2)) { return false; } std::lock_guard lock(MLog[id]); auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); auto tm = *std::localtime(&time_t); if (dofile) { *fp[id] << std::format("{:04d} [{:04d}{:02d}{:02d} {:02d}:{:02d}:{:02d}] ", getpid(), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); fp[id]->write(buf, size * count); *fp[id] << "\n"; fp[id]->flush(); } if (logCallbackBuf[id]) { logCallbackBuf[id](id, buf, size, count); } if (pLogStatus[id] & 2) { auto& stream = (pLogStatus[id] & 8) ? std::cerr : std::cout; stream << "[" << LogNames[id] << "] "; stream.write(buf, size * count); stream << "\n"; } return true; } /** * Writes formatted message to log with timestamp * @param id Log category to write to * @param fmt Printf-style format string * @param ... Variable arguments for format string * @return True if write was successful */ template bool write(LogIDs id, const std::string& fmt, Args&&... args) { if (id >= MaxLogID) { return false; } bool dofile = false; if (pLogStatus[id] & 1) { dofile = open(id); } if (!(dofile || pLogStatus[id] & 2)) { return false; } std::lock_guard lock(MLog[id]); auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); auto tm = *std::localtime(&time_t); std::string buffer; if constexpr (sizeof...(args) > 0) { buffer = std::vformat(fmt, std::make_format_args(args...)); } else { buffer = fmt; } if (dofile) { *fp[id] << std::format("[{:04d}{:02d}{:02d} {:02d}:{:02d}:{:02d}] {}\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, buffer); fp[id]->flush(); } if (pLogStatus[id] & 2) { auto& stream = (pLogStatus[id] & 8) ? std::cerr : std::cout; stream << std::format("[{:04d}{:02d}{:02d} {:02d}:{:02d}:{:02d}] [{}] {}\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, LogNames[id], buffer); } return true; } /** * Dumps binary data in hexadecimal format with ASCII representation * @param id Log category to write to * @param data Binary data to dump * @param size Number of bytes to dump * @param cols Number of columns per row (default 16) * @param skip Number of bytes to skip at beginning (default 0) * @return True if dump was successful */ bool Dump(LogIDs id, const int8_t* data, int32_t size, int32_t cols = 16, int32_t skip = 0) { if (size == 0) { return true; } if (id >= MaxLogID) { return false; } bool dofile = false; if (pLogStatus[id] & 1) { dofile = open(id); } if (!(dofile || pLogStatus[id] & 2)) { return false; } std::lock_guard lock(MLog[id]); write(id, "Dumping Packet: {}", size); // Output data in hexadecimal format with ASCII representation std::string ascii(cols, '\0'); int j = 0; for (int32_t i = skip; i < size; i++) { if ((i - skip) % cols == 0) { if (i != skip) { writeNTS(id, dofile, " | {}\n", ascii); } writeNTS(id, dofile, "{:4}: ", i - skip); ascii.assign(cols, '\0'); j = 0; } else if ((i - skip) % (cols / 2) == 0) { writeNTS(id, dofile, "- "); } writeNTS(id, dofile, "{:02X} ", static_cast(data[i])); if (data[i] >= 32 && data[i] < 127) { ascii[j++] = data[i]; } else { ascii[j++] = '.'; } } // Pad final line if necessary int32_t k = ((size - skip) - 1) % cols; if (k < 8) { writeNTS(id, dofile, " "); } for (int32_t h = k + 1; h < cols; h++) { writeNTS(id, dofile, " "); } writeNTS(id, dofile, " | {}\n", ascii.substr(0, j)); if (dofile) { fp[id]->flush(); } return true; } private: /** * Opens log file for specified category * @param id Log category to open file for * @return True if file opened successfully */ bool open(LogIDs id) { if (id >= MaxLogID) { return false; } std::lock_guard lock(MOpen); if (pLogStatus[id] & 4) { // File error state return false; } if (fp[id]) { // Already open return true; } std::string exename; #if defined(WORLD) exename = "_world"; #elif defined(ZONE) exename = "_zone"; #endif std::string filename = std::format("{}{}_{:04d}.log", FileNames[id], exename, getpid()); fp[id] = new std::ofstream(filename, std::ios::app); if (!fp[id]->is_open()) { std::cerr << "Failed to open log file: " << filename << std::endl; pLogStatus[id] |= 4; // Set error state delete fp[id]; fp[id] = nullptr; return false; } *fp[id] << "---------------------------------------------\n"; return true; } /** * Internal write function without timestamp or locking (Not Thread Safe) * @param id Log category to write to * @param dofile Whether to write to file * @param fmt Format string * @param args Format arguments */ template void writeNTS(LogIDs id, bool dofile, const std::string& fmt, Args&&... args) { std::string buffer; if constexpr (sizeof...(args) > 0) { buffer = std::vformat(fmt, std::make_format_args(args...)); } else { buffer = fmt; } if (dofile) { *fp[id] << buffer; } if (pLogStatus[id] & 2) { auto& stream = (pLogStatus[id] & 8) ? std::cerr : std::cout; stream << buffer; } } // File management std::mutex MOpen; // Mutex for file opening operations std::mutex MLog[MaxLogID]; // Per-category logging mutexes std::ofstream* fp[MaxLogID]; // File pointers for each log category // Log status flags (bitwise) // 1 = output to file, 2 = output to stdout, 4 = file error, 8 = use stderr int8_t pLogStatus[MaxLogID]; // Callback function pointers for custom log handling msgCallbackFmt logCallbackFmt[MaxLogID]; // Formatted message callbacks msgCallbackBuf logCallbackBuf[MaxLogID]; // Buffer message callbacks // Static configuration arrays static constexpr const char* FileNames[MaxLogID] = { "logs/eq2emu", "logs/eq2emu", "logs/eq2emu_error", "logs/eq2emu_debug", "logs/eq2emu_quest", "logs/eq2emu_commands" }; static constexpr const char* LogNames[MaxLogID] = { "Status", "Normal", "Error", "Debug", "Quest", "Command" }; }; #ifdef _EQDEBUG /** * Performance monitoring utility for debug builds * Measures execution time between construction and destruction */ class PerformanceMonitor { public: /** * Constructor - starts timing measurement * @param ip Pointer to variable to store elapsed time */ PerformanceMonitor(int64_t* ip) : p(ip), start_time(std::chrono::high_resolution_clock::now()) { } /** * Destructor - calculates and stores elapsed time */ ~PerformanceMonitor() { auto end_time = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end_time - start_time); *p += duration.count(); } private: std::chrono::high_resolution_clock::time_point start_time; // Start time measurement int64_t* p; // Pointer to elapsed time storage }; #endif