Искусство работы с логами (Часть 8): Самопереводящиеся записи об ошибках
Введение
На данном этапе нашего пути уже ни для кого не секрет, что ведение логов — это не просто запись событий. Это возможность точно уловить то, что ваш советник пытается вам сказать в самом центре вихря тиков, решений и неопределенностей, из которых состоит алгоритмическая торговля.
В процессе повседневного использования Logify я заметил одну вещь, которая меня беспокоила: обработка ошибок все еще оставалась поверхностной. Даже с надежной структурой форматирования, логи по-прежнему отображали лишь сырой код ошибки, без какого-либо намека на то, что он на самом деле означает. Что-то вроде:
logify.Error("Failed to send sell order", "Order Management", "Code: "+IntegerToString(GetLastError())); // Console: // 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | Code: 10016
Результат? Расплывчатое сообщение. Мы знаем, где произошла ошибка, но не знаем, почему. И кто хоть раз сталкивался с необходимостью искать десятки кодов MQL5 в документации? Раньше я сам довольно часто делал так: получив код ошибки, мне приходилось лезть в документацию, чтобы выяснить, что же на самом деле произошло. Именно это неудобство и натолкнуло меня на идею: а что, если Logify сможет интерпретировать ошибку за меня? Что, если вместо простого кода он будет выдавать мне его значение в ясной, контекстуализированной форме, готовой для записи в лог?
Так родилась функциональная эволюция: теперь можно передавать код ошибки напрямую в библиотеку, и она самостоятельно выполняет запрос, форматирование и отображение соответствующего описания автоматически. Тот же самый предыдущий пример, переделанный с этим улучшением, превращается в:
logify.Error("Failed to send sell order", GetLastError(), "Order Management"); // Console: // 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | 10016: Invalid stops in the request
Гораздо понятнее и полезнее, но я решил пойти дальше. Система, которая стремится к ясности, должна быть гибкой не только в коде, но и в языке. Именно поэтому на этом, восьмом этапе, Logify также получает многоязычную поддержку сообщений об ошибках с автоматическим переводом на 11 языков, включая английский, португальский, испанский, немецкий, французский, итальянский, русский, турецкий, китайский, японский и корейский. Теперь ваши логи говорят на языке вашей команды или вашего клиента без ручных настроек.
На этом этапе вы узнаете, как:
- Обогащать логи ошибок точными описаниями, взятыми непосредственно из документации MQL5.
- Отображать сообщения об ошибках на нескольких языках с динамическим выбором наиболее подходящего языка для каждого контекста.
- Настраивать форматирование в зависимости от уровня логирования, создавая различные шаблоны для ошибок, предупреждений и информационных сообщений на основе их степени критичности.
В конечном итоге Logify станет более интеллектуальным, доступным и гораздо более полезным в реальных условиях. Потому что в алгоритмической торговле важна каждая деталь, и каждая секунда, потраченная на расшифровку ошибки, — это секунда, украденная у следующего верного решения.
Создание способа обработки ошибок
Как и любой хорошей системе, нам нужен прочный фундамент, поэтому мы начнем с определения структуры для представления ошибок. Эта структура, которую мы назовем MqlError, будет использоваться во всей системе для хранения трех фундаментальных составляющих любой ошибки:
- Числовой код (code)
- Символическая константа
- Понятное описание (description)
Мы создаем эту структуру в файле <Include/Logify/Error/Error.mqh> file:
//+------------------------------------------------------------------+ //| Data structure for error handling | //+------------------------------------------------------------------+ struct MqlError { int code; // Cod of error string description; // Description of error string constant; // Type error MqlError::MqlError(void) { code = 0; description = ""; constant = ""; } }; //+------------------------------------------------------------------+
Эта структура проста, но достаточна для представления любой ошибки, возвращаемой платформой, будь то ошибка выполнения, ошибка совершения сделок или даже определенная пользователем ошибка. Теперь, когда у нас есть место для хранения данных, нам нужно заполнить эту структуру реальными ошибками.
MetaQuotes предоставляет сотни кодов ошибок, каждый со своей символьной константой и описанием на английском языке, но мы хотим пойти дальше. Мы хотим, чтобы библиотека говорила на немецком, испанском, французском, португальском, китайском... и чтобы логи автоматически адаптировались к настроенному языку.
Стратегия здесь заключается в создании файла .mqh для каждого языка, содержащего функцию, которая инициализирует массив всеми известными ошибками. Название файла следует стандарту ErrorMessages.xx.mqh, где xx обозначает аббревиатуру языка (например, en для английского, de для немецкого, pt для португальского).
Вот как выглядит файл на английском языке:
//+------------------------------------------------------------------+ //| ErrorMessages.en.mqh | //| joaopedrodev | //| https://www.mql5.com/en/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/en/users/joaopedrodev" //+------------------------------------------------------------------+ //| Import struct //+------------------------------------------------------------------+ #include "../Error.mqh" void InitializeErrorsEnglish(MqlError &errors[]) { //--- Free and resize ArrayFree(errors); ArrayResize(errors,274); //+------------------------------------------------------------------+ //| Unknown error | //+------------------------------------------------------------------+ errors[0].code = 0; errors[0].description = "No error found"; errors[0].constant = "ERROR_UNKNOWN"; //+------------------------------------------------------------------+ //| Server error | //+------------------------------------------------------------------+ errors[1].code = 10004; errors[1].description = "New quote"; errors[1].constant = "TRADE_RETCODE_REQUOTE"; //--- errors[2].code = 10006; errors[2].description = "Request rejected"; errors[2].constant = "TRADE_RETCODE_REJECT"; //--- // Remaining error codes... //--- errors[272].code = 5625; errors[272].description = "Parameter binding error, wrong index"; errors[272].constant = "ERR_DATABASE_RANGE"; //--- errors[273].code = 5626; errors[273].description = "Open file is not a database file"; errors[273].constant = "ERR_DATABASE_NOTADB"; } //+------------------------------------------------------------------+
Функция InitializeErrorsEnglish() получает массив MqlError и заполняет его всеми известными ошибками на английском языке. Мы повторяем этот шаблон для других языков, таких как немецкий, испанский, французский, итальянский, японский, корейский, португальский, русский, турецкий и китайский. Все эти файлы хранятся в папке <Include/Logify/Error/Languages>. В общей сложности у нас есть 11 языковых файлов и 274 записи в каждом файле. И да, это была кропотливая работа, но теперь вы можете наслаждаться ею простым подключением. Помните, что весь код прилагается в конце статьи.
Теперь, когда у нас есть все организованные данные, нам нужен интерфейс для их запроса. Здесь на сцену выходит класс CLogifyError. Этот класс будет отвечать за загрузку ошибок на нужном языке и возврат полной информации для любого запрошенного кода ошибки.
Интерфейс класса прост:
//+------------------------------------------------------------------+ //| class : CLogifyError | //| | //| [PROPERTY] | //| Name : LogifyError | //| Heritage : No heritage | //| Description : class to look up the error code and return details | //| of each error code. | //| | //+------------------------------------------------------------------+ class CLogifyError { private: ENUM_LANGUAGE m_language; MqlError m_errors[]; public: CLogifyError(void); ~CLogifyError(void); //--- Set/Get void SetLanguage(ENUM_LANGUAGE language); ENUM_LANGUAGE GetLanguage(void); //--- Get error MqlError Error(int code); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyError::CLogifyError() { InitializeErrorsEnglish(m_errors); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyError::~CLogifyError(void) { } //+------------------------------------------------------------------+
При создании экземпляра класс загружает ошибки на английском языке по умолчанию. Вы можете изменить язык в любое время с помощью SetLanguage(), и библиотека перезагрузит данные на соответствующем языке.
Метод Error(int code) — это сердце класса. Он просматривает массив ошибок в поисках введенного кода и возвращает соответствующую структуру MqlError. Если код не найден, функция возвращает общую ошибку в качестве запасного варианта. Кроме того, он автоматически определяет, находится ли код ошибки в диапазоне, зарезервированном для пользовательских ошибок (от ERR_USER_ERROR_FIRST до ERR_USER_ERROR_LAST), и возвращает ответ также и для них.
//+------------------------------------------------------------------+ //| Returns error information based on the error code received | //+------------------------------------------------------------------+ MqlError CLogifyError::Error(int code) { int size = ArraySize(m_errors); for(int i=0;i<size;i++) { if(m_errors[i].code == code) { //--- Return return(m_errors[i]); } } //--- User error if(code >= ERR_USER_ERROR_FIRST && code < ERR_USER_ERROR_LAST) { MqlError error; error.code = code; error.constant = "User error"; error.description = "ERR_USER_ERROR"; //--- Return return(m_errors[274]); } //--- Return return(m_errors[0]); } //+------------------------------------------------------------------+
Наконец, давайте добавим эту структуру ошибки в модель данных лога MqlLogifyModel, чтобы мы могли получать доступ к данным об ошибке внутри структуры данных записи.
#include "LogifyLevel.mqh" #include "Error/Error.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct MqlLogifyModel { string formated; // The log message formatted according to the specified format. string levelname; // Textual name of the log level (e.g., "DEBUG", "INFO") string msg; // Main content of the log message string args; // Additional arguments associated with the log message ulong timestamp; // Timestamp of the log event, represented in seconds since the start of the Unix epoch datetime date_time; // Date and time of the log event, in datetime format ENUM_LOG_LEVEL level; // Enumeration representing the severity level of the log string origin; // Source or context of the log message (e.g., class or module) string filename; // Name of the source file where the log message was generated string function; // Name of the function where the log was called ulong line; // Line number in the source file where the log was generated MqlError error; // Error data void MqlLogifyModel::Reset(void) { formated = ""; levelname = ""; msg = ""; args = ""; timestamp = 0; date_time = 0; level = LOG_LEVEL_DEBUG; origin = ""; filename = ""; function = ""; line = 0; } MqlLogifyModel::MqlLogifyModel(void) { this.Reset(); } MqlLogifyModel::MqlLogifyModel(string _formated,string _levelname,string _msg,string _args,ulong _timestamp,datetime _date_time,ENUM_LOG_LEVEL _level,string _origin,string _filename,string _function,ulong _line,MqlError &_error) { formated = _formated; levelname = _levelname; msg = _msg; args = _args; timestamp = _timestamp; date_time = _date_time; level = _level; origin = _origin; filename = _filename; function = _function; line = _line; error = _error; } }; //+------------------------------------------------------------------+
С этими изменениями структура нашей библиотеки выглядит следующим образом:

Интеграция ошибок в основной класс
У нас уже есть надежная структура для представления ошибок, многоязычные файлы, содержащие их описания, и класс, предназначенный для получения этих сообщений в зависимости от языка. Но пока все это находится пока не интегрировано, не связанное с ядром библиотеки — классом CLogify.
Пришло время интегрировать систему ошибок в ядро Logify, чтобы каждая создаваемая запись журнала содержала, при необходимости, четкое многоязычное объяснение того, что пошло не так. Для этого мы выполним три шага: создадим экземпляр класса ошибок, настроим метод для добавления записей (Append) и, наконец, настроим форматтер для распознавания новых плейсхолдеров {err_code}, {err_constant} и {err_description}.
1. Добавление интеллекта ошибок в сердце Logify
Класс CLogify отвечает за связь форматирования, обработки и отправки лога, поэтому первый логический шаг — предоставить ему прямой доступ к нашей системе ошибок. Для этого достаточно импортировать файл LogifyError.mqh и создать экземпляр класса CLogifyError в качестве закрытого члена CLogify. Это гарантирует, что он всегда будет доступен внутри, не требуя от пользователя библиотеки беспокоиться об этом. Это небольшое дополнение уже открывает важные возможности. Теперь, когда бы ни произошла ошибка, мы можем быстро получить ее код, описание и константу.
Поскольку языком по умолчанию для CLogifyError является английский, было бы расточительно не позволить пользователю выбрать наиболее подходящий язык. Вот почему мы добавили два простых метода. Вот как выглядит код в итоге:
#include "LogifyModel.mqh" #include "Handlers/LogifyHandler.mqh" #include "Handlers/LogifyHandlerComment.mqh" #include "Handlers/LogifyHandlerConsole.mqh" #include "Handlers/LogifyHandlerDatabase.mqh" #include "Handlers/LogifyHandlerFile.mqh" #include "Error/LogifyError.mqh" //+------------------------------------------------------------------+ //| class : CLogify | //| | //| [PROPERTY] | //| Name : Logify | //| Heritage : No heritage | //| Description : Core class for log management. | //| | //+------------------------------------------------------------------+ class CLogify { private: CLogifyError m_error; public: CLogify(); ~CLogify(); //--- Language void SetLanguage(ENUM_LANGUAGE language); ENUM_LANGUAGE GetLanguage(void); }; //+------------------------------------------------------------------+
2. Обогащение метода Append кодом ошибки
До сих пор метод Append() получал основное сообщение, дополнительные аргументы, источник, имя файла, строку, функцию... но не сам код ошибки. Если мы хотим, чтобы лог содержал контекст о сбоях, нам нужно разрешить передачу этого кода, хотя бы опционально. Именно поэтому мы добавили параметр code_error в конец сигнатуры метода:
bool Append(ENUM_LOG_LEVEL level, string msg, string origin = "", string args = "", string filename = "", string function = "", int line = 0, int code_error = 0);
Благодаря этому вызов Append() не меняется для тех, кто не хочет передавать ошибку, но для тех, кто хочет, достаточно просто добавить ее в конце. Такой подход сохраняет обратную совместимость с существующим кодом.
Теперь, имея на руках код ошибки, мы используем метод Error() класса CLogifyError, чтобы получить соответствующую структуру MqlError:
//+------------------------------------------------------------------+ //| Generic method for adding logs | //+------------------------------------------------------------------+ bool CLogify::Append(ENUM_LOG_LEVEL level,string msg, string origin = "", string args = "",string filename="",string function="",int line=0,int code_error=0) { //--- Ensures that there is at least one handler this.EnsureDefaultHandler(); //--- Textual name of the log level string levelStr = ""; switch(level) { case LOG_LEVEL_DEBUG: levelStr = "DEBUG"; break; case LOG_LEVEL_INFO : levelStr = "INFO"; break; case LOG_LEVEL_ALERT: levelStr = "ALERT"; break; case LOG_LEVEL_ERROR: levelStr = "ERROR"; break; case LOG_LEVEL_FATAL: levelStr = "FATAL"; break; } //--- Creating a log template with detailed information datetime time_current = TimeCurrent(); MqlLogifyModel data("",levelStr,msg,args,time_current,time_current,level,origin,filename,function,line,m_error.Error(code_error)); //--- Call handlers int size = this.SizeHandlers(); for(int i=0;i<size;i++) { data.formated = m_handlers[i].GetFormatter().Format(data); m_handlers[i].Emit(data); } return(true); } //+------------------------------------------------------------------+
Теперь объект MqlLogifyModel несет в себе не только информацию лога, но и суть ошибки, которая его вызвала. Теперь осталось добавить перегруженную версию метода для методов Error() и Fatal(), не удаляя существующие, так как это может привести к ошибкам — пользователю библиотеки не всегда нужно добавлять код ошибки. Вот как выглядит код:
class CLogify { public: //--- Specific methods for each log level bool Debug(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Info(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Alert(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Error(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0); bool Fatal(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0); }; bool CLogify::Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_ERROR,msg,origin,args,filename,function,line,code_error)); } bool CLogify::Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_FATAL,msg,origin,args,filename,function,line,code_error)); }
3. Форматирование сообщения с использованием плейсхолдеров ошибки
До сих пор ошибка загружалась, но все еще не отображалась в отформатированном логе. Для этого нам нужно расширить систему плейсхолдеров CLogifyFormatter, включив в нее свойства ошибки. Логика проста, но изящна: мы показываем данные об ошибке, только если уровень лога ERROR или выше. Информационные или отладочные логи не нуждаются в такой детализации — мы стремимся к ясности, а не к засорению.
//+------------------------------------------------------------------+ //| Format logs | //+------------------------------------------------------------------+ string CLogifyFormatter::FormatLog(MqlLogifyModel &data) { string formated = m_log_format; StringReplace(formated,"{levelname}",data.levelname); StringReplace(formated,"{msg}",data.msg); StringReplace(formated,"{args}",data.args); StringReplace(formated,"{timestamp}",IntegerToString(data.timestamp)); StringReplace(formated,"{date_time}",this.FormatDate(data.date_time)); StringReplace(formated,"{level}",IntegerToString(data.level)); StringReplace(formated,"{origin}",data.origin); StringReplace(formated,"{filename}",data.filename); StringReplace(formated,"{function}",data.function); StringReplace(formated,"{line}",IntegerToString(data.line)); if(data.level >= LOG_LEVEL_ERROR) { StringReplace(formated,"{err_code}",IntegerToString(data.error.code)); StringReplace(formated,"{err_constant}",data.error.constant); StringReplace(formated,"{err_description}",data.error.description); } else { StringReplace(formated,"{err_code}",""); StringReplace(formated,"{err_constant}",""); StringReplace(formated,"{err_description}",""); } return(formated); } //+------------------------------------------------------------------+
Итак, мы создали три новых хука для настройки:
- {err_code} : отображает точный код ошибки (например, 10006 )
- {err_constant} : соответствующая константа (например, TRADE_RETCODE_REJECT).
- {err_description} : понятное описание (например, Request rejected )
Представим, что мы определили глобальный формат вот так:
"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})" По идее, это должно давать что-то вроде:
2025.06.09 14:22:15 [ERROR] Failed to send order (10016 TRADE_RETCODE_REJECT: Request rejected)
И действительно, для уровней логирования ERROR и FATAL это работает безупречно. Но что произойдет, если мы используем тот же формат для логов DEBUG, INFO или ALERT? Результат будет примерно таким:
2025.06.09 14:22:15 [INFO] Operation completed successfully ( : )
Или даже хуже: в зависимости от форматирования это может привести к визуальному смещению в записях логов. Несмотря на все наши старания очистить плейсхолдеры ошибок для младших уровней, проблема остается: формат был создан в расчете на то, что {err_code}, {err_constant} и {err_description} будут иметь значения. Когда их нет, лог выглядит... странно и неполноценно. Мы сталкиваемся с нарушением семантики: одна и та же маска форматирования применяется к сообщениям, имеющим совершенно разный контекст.
Решение заключается не в попытке скорректировать данные. Оно в том, чтобы воспринимать каждый уровень логирования таким, какой он есть: как отдельный тип сообщения со своими собственными потребностями и форматами.
Поэтому вместо CLogifyFormatter с универсальным форматом мы создаем отдельный форматтер для каждого уровня (DEBUG, INFO, ALERT, ERROR, FATAL). Это позволяет настроить, например:
- DEBUG: показывать функцию, файл и строку
- INFO: быть минималистичным — только что произошло
- ALERT: добавлять контекст, например, источник и аргументы
- ERROR: включать данные об ошибке
- FATAL: быть максимально полным
Реализация нескольких форматов
Сначала мы изменяем способ хранения формата: вместо простой строки мы превращаем его в массив с пятью возможными вариантами (по одному для каждого уровня логирования).
| Старый код | Новый код |
|---|---|
class CLogifyFormatter { private: //--- Stores the formats string m_date_format; string m_log_format; }; | class CLogifyFormatter { private: //--- Stores the formats string m_date_format; string m_format[5]; }; |
Мы заменили m_log_format на массив строк, по одной позиции для каждого уровня логирования. Теперь у нас есть пять различных форматов, каждый из которых можно настроить в соответствии с типом отправляемого сообщения.
С разделением форматов по уровням нам нужен удобный способ их настройки. Для этого мы создали два метода SetFormat: один применяет одинаковый формат ко всем уровням, а другой позволяет индивидуально настроить конкретный уровень.
class CLogifyFormatter { public: void SetFormat(string format); void SetFormat(ENUM_LOG_LEVEL level, string format); }; //+------------------------------------------------------------------+ //| Sets the format for all levels | //+------------------------------------------------------------------+ void CLogifyFormatter::SetFormat(string format) { m_format[LOG_LEVEL_DEBUG] = format; m_format[LOG_LEVEL_INFO] = format; m_format[LOG_LEVEL_ALERT] = format; m_format[LOG_LEVEL_ERROR] = format; m_format[LOG_LEVEL_FATAL] = format; } //+------------------------------------------------------------------+ //| Sets the format to a specific level | //+------------------------------------------------------------------+ void CLogifyFormatter::SetFormat(ENUM_LOG_LEVEL level, string format) { m_format[level] = format; } //+------------------------------------------------------------------+
Такая реализация не только удобна, но и позволяет нам быть одновременно универсальными и точными. Для тех, кому нужна простота, достаточно одного вызова SetFormat(...). Для тех, кто стремится к точности, можно просто вызвать SetFormat(level, format) для каждого нужного уровня.
После разделения форматов метод форматирования теперь должен отражать это разделение. Раньше мы использовали общую переменную m_log_format для получения маски сообщения. Теперь же мы ищем формат непосредственно в массиве, используя в качестве индекса уровень логирования.
| Старый код | Новый код |
|---|---|
string CLogifyFormatter::FormatLog(MqlLogifyModel &data) { string formated = m_log_format; ... } | string CLogifyFormatter::Format(MqlLogifyModel &data) { string formated = m_format[data.level]; ... } |
Это решает проблему чистым, расширяемым и предсказуемым способом. Никаких лишних проверок, никаких обходных путей для подавления пустых заполнителей. Каждый уровень точно знает, что он должен содержать.
Тестирование
Мы подошли к той части, которая оживляет все, что мы создали: к практическому тестированию и наблюдению за тем, как система ведет себя в различных сценариях и на разных уровнях логирования. В том же файле, который мы использовали для тестирования в прошлой статье, LogifyTest.mqh, мы подготовили следующие элементы:
- Визуальный обработчик через Comment() с рамкой, заголовком и ограничением на количество строк.
- Консольный обработчик через Print() для записи сообщений во внутренний лог платформы.
- Один форматтер, общий для обоих обработчиков.
- Четыре лог-сообщения разных уровней: два DEBUG, одно INFO и одно ERROR с кодом ошибки.
//+------------------------------------------------------------------+ //| Import CLogify | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify logify; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { //--- Handler config MqlLogifyHandleCommentConfig m_config; m_config.size = 5; // Max log lines m_config.frame_style = LOG_FRAME_STYLE_SINGLE; // Frame style m_config.direction = LOG_DIRECTION_UP; // Log direction (up) m_config.title = "Expert name"; // Log panel title CLogifyFormatter *formatter = new CLogifyFormatter("{date_time} [{levelname}]: {msg}"); formatter.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}]: {msg} [{err_constant} | {err_code} | {err_description}]"); //--- Create and configure handler CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment(); handler_comment.SetConfig(m_config); handler_comment.SetLevel(LOG_LEVEL_DEBUG); // Min log level handler_comment.SetFormatter(formatter); CLogifyHandlerConsole *handler_console = new CLogifyHandlerConsole(); handler_console.SetLevel(LOG_LEVEL_DEBUG); // Min log level handler_console.SetFormatter(formatter); //--- Add handler to Logify logify.AddHandler(handler_comment); logify.AddHandler(handler_console); //--- Test logs logify.Debug("Initializing Expert Advisor...", "Init", ""); logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14"); logify.Info("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1"); logify.Error("Failed to send sell order", 10016,"Order Management"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Когда вы запустите этот код, консоль MetaTrader отобразит следующие сообщения, отформатированные именно так, как мы и ожидали. Визуальный результат наглядно показывает, что младшие уровни (такие как DEBUG и INFO) следуют более лаконичному формату, в то время как ERROR задействует более детальный формат с данными об ошибке.
09/06/2025 14:22:31 [DEBUG]: Initializing Expert Advisor... 09/06/2025 14:22:31 [DEBUG]: RSI indicator value calculated: 72.56 09/06/2025 14:22:31 [INFO]: Buy order sent successfully 09/06/2025 14:22:31 [ERROR]: Failed to send sell order [TRADE_DISABLED | 10016 | Trade operations not allowed]
Обратите внимание, как последний лог содержит дополнительный блок в квадратных скобках с символьной константой, числовым кодом и понятным пользователю описанием ошибки. И все это стало возможным без добавления лишней условной логики в код эксперта. Каждый уровень логирования несет свой собственный формат, и нужные плейсхолдеры появляются только там, где они имеют смысл.
Заключение
В этой статье мы внесли улучшения в библиотеку Logify. В первой статье мы начали с структурирования модели данных, затем создали гибкие обработчики для различных каналов вывода. А сегодня мы добавили многоязычную поддержку сообщений об ошибках, позволяющую каждому логу предоставлять разработчику четкий технический контекст независимо от языка.
Мы продвинулись вперед с динамическим форматтером, способным обрабатывать пользовательские плейсхолдеры и автоматически адаптироваться к уровню логирования. Когда мы поняли, что универсальные форматы вызывают несогласованность, мы развили архитектуру, чтобы принимать конкретный формат для каждого уровня. И наконец, мы подтвердили все практическим тестом, который показал Logify в работе в реальном времени — чистым, предсказуемым и расширяемым способом.
Если вы дошли до этого момента, вы узнали, как:
- Проектировать масштабируемую и слабосвязанную структуру логирования;
- Интегрировать многоязычные сообщения об ошибках на основе кодов MetaTrader;
- Работать с заполнителями и форматированием в зависимости от уровня логирования;
- Создавать многоразовые обработчики с собственными конфигурациями;
Logify, как любой хороший инструмент, может продолжать развиваться. Если будут реализованы новые функции, я расскажу о них в будущей статье. А до тех пор — используйте, адаптируйте и улучшайте.
| Название файла | Описание |
|---|---|
| Experts/Logify/LogifyTest.mq5 | Файл, в котором мы тестируем возможности библиотеки, содержащий практический пример |
| Include/Logify/Error/Languages/ErrorMessages.XX.mqh | Подсчет количества сообщений об ошибках на каждом языке, где X обозначает аббревиатуру языка |
| Include/Logify/Error/Error.mqh | Структура данных для хранения ошибок |
| Include/Logify/Error/LogifyError.mqh | Класс для получения детальной информации об ошибке |
| Include/Logify/Formatter/LogifyFormatter.mqh | Класс, отвечающий за форматирование записей журнала, замену заполнителей конкретными значениями |
| Include/Logify/Handlers/LogifyHandler.mqh | Базовый класс для управления обработчиками журнала, включая установку уровня и отправку журналов |
| Include/Logify/Handlers/LogifyHandlerComment.mqh | Обработчик журнала, который отправляет форматированные журналы непосредственно в комментарий на графике терминала в MetaTrader |
| Include/Logify/Handlers/LogifyHandlerConsole.mqh | Обработчик журнала, который отправляет форматированные журналы непосредственно в консоль терминала в MetaTrader |
| Include/Logify/Handlers/LogifyHandlerDatabase.mqh | Обработчик журнала, который отправляет форматированные журналы в базу данных (В настоящее время он содержит только вывод на печать, но скоро мы будем сохранять его в реальную базу данных sqlite) |
| Include/Logify/Handlers/LogifyHandlerFile.mqh | Обработчик журнала, который отправляет форматированные журналы в файл |
| Include/Logify/Utils/IntervalWatcher.mqh | Проверяет, прошел ли заданный интервал времени, позволяя создавать процедуры внутри библиотеки |
| Include/Logify/Logify.mqh | Основной класс для управления журналами, объединяющий уровни, модели и форматирование |
| Include/Logify/LogifyLevel.mqh | Файл, определяющий уровни журналирования библиотеки Logify, что позволяет осуществлять детальный контроль |
| Include/Logify/LogifyModel.mqh | Структура, которая моделирует записи журнала, включая такие детали, как уровень, сообщение, временная метка и контекст |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18467
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Торговые инструменты на языке MQL5 (Часть 9): Мастер первого запуска для советников с прокручиваемым руководством
Переосмысливаем классические стратегии (Часть 13): Обновление стратегии по пересечению скользящих (Часть 2)
Знакомство с языком MQL5 (Часть 38): Освоение API и функции WebRequest в языке MQL5 (XII)
Нейросети в трейдинге: Единая архитектура взаимодействия рыночных признаков и торгового контекста (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Спасибо, я рад помочь. Любые предложения по улучшению, пожалуйста, свяжитесь со мной!
Я скомпилировал библиотеку регистрации Logify, и после MT5 build 5100 появилось несколько ошибок компиляции, связанных с типами в CLogifyHandlerDatabase::Query. Я полагаю, что вы уже должны были решить эту проблему.
Спасибо за предложения, я учту их в будущих статьях.
Небольшая идея по поводу языков.
Возможно, расположить оригинальные английские сообщения таким образом, чтобы их можно было легко скопировать и вставить в переводчик (deepl.com или translate.google.com) и поместить результат обратно в программу. Таким образом, каждый может легко настроить программу на свой язык.
Deeple распознает 36 языков, Google - около 130.