| //+------------------------------------------------------------------+
|
| //| EALogger.mqh |
|
| //| Copyright 2025, Your Company Name |
|
| //| https://www.yoursite.com |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2025, Your Company Name"
|
| #property link "https://www.yoursite.com"
|
| #property version "1.00"
|
| #property strict
|
|
|
| //+------------------------------------------------------------------+
|
| //| Log levels |
|
| //+------------------------------------------------------------------+
|
| enum ENUM_LOG_LEVEL
|
| {
|
| LOG_LEVEL_ERROR = 0, // Error level
|
| LOG_LEVEL_WARN, // Warning level
|
| LOG_LEVEL_INFO, // Information level
|
| LOG_LEVEL_DEBUG, // Debug level
|
| LOG_LEVEL_TRACE // Trace level
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Logger class |
|
| //+------------------------------------------------------------------+
|
| class CEALogger
|
| {
|
| private:
|
| string m_filename; // Log file name
|
| int m_fileHandle; // File handle
|
| ENUM_LOG_LEVEL m_logLevel; // Current log level
|
| bool m_consoleOutput; // Enable console output
|
| bool m_fileOutput; // Enable file output
|
|
|
| // Log level names
|
| static string m_levelNames[];
|
|
|
| // Format log message
|
| string FormatMessage(const string message, const ENUM_LOG_LEVEL level);
|
|
|
| public:
|
| CEALogger() : m_fileHandle(INVALID_HANDLE), m_logLevel(LOG_LEVEL_INFO),
|
| m_consoleOutput(true), m_fileOutput(true) {}
|
| ~CEALogger() { Deinitialize(); }
|
|
|
| // Initialization
|
| bool Initialize(const string filename,
|
| const ENUM_LOG_LEVEL level = LOG_LEVEL_INFO,
|
| const bool consoleOutput = true,
|
| const bool fileOutput = true);
|
| void Deinitialize();
|
|
|
| // Logging methods
|
| void Log(const string message, const ENUM_LOG_LEVEL level = LOG_LEVEL_INFO);
|
| void Error(const string message) { Log(message, LOG_LEVEL_ERROR); }
|
| void Warning(const string message) { Log(message, LOG_LEVEL_WARN); }
|
| void Info(const string message) { Log(message, LOG_LEVEL_INFO); }
|
| void Debug(const string message) { Log(message, LOG_LEVEL_DEBUG); }
|
| void Trace(const string message) { Log(message, LOG_LEVEL_TRACE); }
|
|
|
| // Getters/Setters
|
| ENUM_LOG_LEVEL GetLogLevel() const { return m_logLevel; }
|
| void SetLogLevel(const ENUM_LOG_LEVEL level) { m_logLevel = level; }
|
|
|
| // Helper methods
|
| static string GetLastErrorText(const int errorCode = 0);
|
| };
|
|
|
| // Initialize static members
|
| string CEALogger::m_levelNames[] = {"ERROR", "WARN ", "INFO ", "DEBUG", "TRACE"};
|
|
|
| //+------------------------------------------------------------------+
|
| //| Initialize logger |
|
| //+------------------------------------------------------------------+
|
| bool CEALger::Initialize(const string filename,
|
| const ENUM_LOG_LEVEL level,
|
| const bool consoleOutput,
|
| const bool fileOutput)
|
| {
|
| // Close previous file if open
|
| if (m_fileHandle != INVALID_HANDLE)
|
| FileClose(m_fileHandle);
|
|
|
| m_filename = filename;
|
| m_logLevel = level;
|
| m_consoleOutput = consoleOutput;
|
| m_fileOutput = fileOutput;
|
|
|
| // Open log file if file output is enabled
|
| if (m_fileOutput)
|
| {
|
| m_fileHandle = FileOpen(m_filename, FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE);
|
| if (m_fileHandle == INVALID_HANDLE)
|
| {
|
| // Try to create the directory if it doesn't exist
|
| string path = "";
|
| int pos = StringFind(m_filename, "\\");
|
| if (pos > 0)
|
| {
|
| path = StringSubstr(m_filename, 0, pos);
|
| if (!FolderCreate(path, 0))
|
| {
|
| Print("Failed to create log directory: ", path);
|
| return false;
|
| }
|
|
|
| // Try opening the file again
|
| m_fileHandle = FileOpen(m_filename, FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE);
|
| if (m_fileHandle == INVALID_HANDLE)
|
| {
|
| Print("Failed to open log file: ", m_filename, ", error: ", GetLastError());
|
| return false;
|
| }
|
| }
|
| }
|
|
|
| // Write log header
|
| string header = StringFormat("=== Log started: %s ===\n", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS));
|
| FileWriteString(m_fileHandle, header);
|
| FileFlush(m_fileHandle);
|
| }
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Deinitialize logger |
|
| //+------------------------------------------------------------------+
|
| void CEALogger::Deinitialize()
|
| {
|
| if (m_fileHandle != INVALID_HANDLE)
|
| {
|
| // Write log footer
|
| string footer = StringFormat("\n=== Log ended: %s ===\n\n", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS));
|
| FileWriteString(m_fileHandle, footer);
|
|
|
| // Close the file
|
| FileClose(m_fileHandle);
|
| m_fileHandle = INVALID_HANDLE;
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Format log message |
|
| //+------------------------------------------------------------------+
|
| string CEALogger::FormatMessage(const string message, const ENUM_LOG_LEVEL level)
|
| {
|
| if (level < 0 || level >= ArraySize(m_levelNames))
|
| return "";
|
|
|
| datetime currentTime = TimeCurrent();
|
| string timeStr = TimeToString(currentTime, TIME_DATE|TIME_SECONDS);
|
| string levelStr = m_levelNames[level];
|
|
|
| return StringFormat("%s [%s] %s", timeStr, levelStr, message);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Log a message |
|
| //+------------------------------------------------------------------+
|
| void CEALogger::Log(const string message, const ENUM_LOG_LEVEL level)
|
| {
|
| // Check log level
|
| if (level > m_logLevel)
|
| return;
|
|
|
| // Format the message
|
| string formattedMsg = FormatMessage(message, level);
|
| if (formattedMsg == "")
|
| return;
|
|
|
| // Output to console
|
| if (m_consoleOutput)
|
| {
|
| switch (level)
|
| {
|
| case LOG_LEVEL_ERROR:
|
| Print("ERROR: ", message);
|
| break;
|
| case LOG_LEVEL_WARN:
|
| Print("WARN: ", message);
|
| break;
|
| case LOG_LEVEL_INFO:
|
| Print("INFO: ", message);
|
| break;
|
| case LOG_LEVEL_DEBUG:
|
| Print("DEBUG: ", message);
|
| break;
|
| case LOG_LEVEL_TRACE:
|
| Print("TRACE: ", message);
|
| break;
|
| }
|
| }
|
|
|
| // Output to file
|
| if (m_fileOutput && m_fileHandle != INVALID_HANDLE)
|
| {
|
| FileWriteString(m_fileHandle, formattedMsg + "\n");
|
| FileFlush(m_fileHandle);
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Get last error text |
|
| //+------------------------------------------------------------------+
|
| string CEALogger::GetLastErrorText(const int errorCode = 0)
|
| {
|
| int code = (errorCode == 0) ? GetLastError() : errorCode;
|
|
|
| switch (code)
|
| {
|
| // Common errors
|
| case 0: return "No error";
|
| case 1: return "No error returned, but the result is unknown";
|
| case 2: return "Common error";
|
| case 3: return "Invalid trade parameters";
|
| case 4: return "Trade server is busy";
|
| case 5: return "Old version of the client terminal";
|
| case 6: return "No connection with trade server";
|
| case 7: return "Not enough rights";
|
| case 8: return "Too frequent requests";
|
| case 9: return "Malfunctional trade operation";
|
| case 64: return "Account disabled";
|
| case 65: return "Invalid account";
|
| case 128: return "Trade timeout";
|
| case 129: return "Invalid price";
|
| case 130: return "Invalid stops";
|
| case 131: return "Invalid trade volume";
|
| case 132: return "Market is closed";
|
| case 133: return "Trade is disabled";
|
| case 134: return "Not enough money";
|
| case 135: return "Price changed";
|
| case 136: return "Off quotes";
|
| case 137: return "Broker is busy";
|
| case 138: return "Requote";
|
| case 139: return "Order is locked";
|
| case 140: return "Long positions only allowed";
|
| case 141: return "Too many requests";
|
| case 145: return "Modification denied because order is too close to market";
|
| case 146: return "Trade context is busy";
|
| case 147: return "Expirations are denied by broker";
|
| case 148: return "Too many open and pending orders";
|
| case 149: return "Hedging is prohibited";
|
| case 150: return "Prohibited by FIFO rules";
|
|
|
| // Custom errors
|
| case 4000: return "No error (the operation completed successfully)";
|
| case 4001: return "Wrong function pointer";
|
| case 4002: return "Array index is out of range";
|
| case 4003: return "No memory for function call stack";
|
| case 4004: return "Recursive stack overflow";
|
| case 4005: return "Not enough stack for parameter";
|
| case 4006: return "No memory for parameter string";
|
| case 4007: return "No memory for temp string";
|
| case 4008: return "Not initialized string";
|
| case 4009: return "Invalid string parameter";
|
| case 4010: return "Invalid string position";
|
| case 4011: return "String parameter too long";
|
| case 4012: return "Invalid array";
|
| case 4013: return "Array resize error";
|
| case 4014: return "String resize error";
|
| case 4015: return "Not initialized array";
|
| case 4016: return "Invalid array size";
|
| case 4017: return "Abnormal program termination";
|
| case 4018: return "Unhandled exception";
|
| case 4019: return "Invalid pointer";
|
| case 4020: return "Invalid variable type";
|
| case 4021: return "Function is not allowed in testing mode";
|
| case 4022: return "Function is not confirmed";
|
| case 4050: return "Invalid function parameters count";
|
| case 4051: return "Invalid function parameter value";
|
| case 4052: return "String function internal error";
|
| case 4053: return "Some array error";
|
| case 4054: return "Incorrect series array using";
|
| case 4055: return "Custom indicator error";
|
| case 4056: return "Arrays are incompatible";
|
| case 4057: return "Global variables processing error";
|
| case 4058: return "Global variable not found";
|
| case 4059: return "Function is not allowed in testing mode";
|
| case 4060: return "Function is not confirmed";
|
| case 4061: return "Send mail error";
|
| case 4062: return "String parameter expected";
|
| case 4063: return "Integer parameter expected";
|
| case 4064: return "Double parameter expected";
|
| case 4065: return "Array as parameter expected";
|
| case 4066: return "Requested history not found";
|
| case 4067: return "Some error in trade operation";
|
| case 4099: return "End of file";
|
| case 4100: return "Some file error";
|
| case 4101: return "Wrong file name";
|
| case 4102: return "Too many opened files";
|
| case 4103: return "Cannot open file";
|
| case 4104: return "Incompatible access to a file";
|
| case 4105: return "No order selected";
|
| case 4106: return "Unknown symbol";
|
| case 4107: return "Invalid price";
|
| case 4108: return "Invalid ticket";
|
| case 4109: return "Trade is not allowed";
|
| case 4110: return "Longs are not allowed";
|
| case 4111: return "Shorts are not allowed";
|
| case 4200: return "Object already exists";
|
| case 4201: return "Unknown object property";
|
| case 4202: return "Object does not exist";
|
| case 4203: return "Unknown object type";
|
| case 4204: return "No object name";
|
| case 4205: return "Object coordinates error";
|
| case 4206: return "No specified subwindow";
|
| case 4207: return "Some error in object function";
|
| default: return "Unknown error";
|
| }
|
| }
|