
Искусство ведения логов (Часть 1): Основные понятия и первые шаги в MQL5
Введение
Добро пожаловать в новое приключение! Данная статья открывает специальный цикл, в котором мы будем пошагово создавать библиотеку для работы с журналами, предназначенную для тех, кто занимается разработкой на языке MQL5. Идея проста, но амбициозна: предложить надежный, гибкий и высококачественный инструмент, способный сделать процесс записи и анализа журналов в советниках (экспертах) более практичным, эффективным и полезным.
На сегодня у нас есть нативные журналы MetaTrader 5, которые выполняют задачу мониторинга основных показателей: запуск терминала, подключение к серверам, сведения об окружении. Но честно говоря, эти журналы не адаптированы к специфике разработки советников. Когда мы захотим понять определенное поведение советника в процессе исполнения, мы столкнемся с ограничениями. Недостаток точности, контроля и возможностей кастомизации дадут явно почувствовать разницу.
Именно поэтому данная серия статей предлагает сделать шаг вперед. Мы с нуля построим специализированную, полностью кастомизируемую систему ведения журналов. Представьте, что у вас будет полный контроль на тем, что записывать в журнал: отслеживание критических событий и ошибок, анализ результатов или даже хранение определенной информации для будущих исследований. И все это, разумеется, в организованном виде и с эффективностью, соответствующей требованиям такого сценария.
Но здесь мы поговорим не только о коде. Данная серия статей выходит за пределы клавиатуры. Мы изучим основы ведения журналов, ответим на вопрос "почему" прежде чем спрашивать "как", обсудим наилучшие практики проектирования и вместе построим что-то не только функциональное, но и элегантное, интуитивно-понятное. В конце концов, создание ПО — это не только решение задач, но и искусство.
Что такое журналы?
С функциональной точки зрения журналы (логи) — это хронологические записи событий, постоянно генерируемых системами и приложениями. Они похожи на свидетельства очевидца о каждом сделанном запросе, каждой появившейся ошибке и каждом принятом советником решении. Если разработчик хочет отследить, почему прервался поток исполнения, ему следует начать с изучения журналов. Без них интерпретация того, что происходит, будет похожа на исследование лабиринта в темноте.
По правде говоря, нас все больше окружает цифровая вселенная, в которой системы — это не просто инструменты, но важнейшие механизмы в мире, который дышит кодом. Подумайте о мгновенных сообщениях, финансовых транзакциях, занимающих долю секунды, или автоматизированном управлении промышленным предприятием. Надежность в этом мире — не роскошь, а необходимое условие. Но что произойдет, если что-то пойдет не так? С чего начинается поиск неисправности? Конечно, с журналов. Журналы — это наши глаза и уши внутри "черного ящика", который мы называем системами.
Представьте: советник во время автоматизированного согласования начинает выдавать сбои при отправке массовых запросов на сервер. Внезапно возникают отклонения запросов. Без структурированного журнала вам придется вслепую задаваться предположениями: может быть, сервер перегружен? Может быть, проблема в конфигурации советника? При наличии грамотно спроектированных журналов вы сможете не только найти нужный "стог сена", но и конкретную иголку в нем: ошибку аутентификации, таймаут или даже избыточный объем запросов.
Но в журналах не все идеально. Их использование без критериев может закончиться выстрелом себе в ногу: накапливаются ненужные данные, расходы на их хранение взлетают до небес, а при худшем сценарии — возникают утечки чувствительной информации. Более того, недостаточно просто иметь журналы: нужно знать как их правильно настраивать и точно интерпретировать. В противном случае то, что должно было стать картой, превратится в хаотичный шум и скорее создаст больше путаницы, чем решит проблему.
Чтобы во всех подробностях понять, что такое журналы, необходимо уяснить, что это не просто инструменты. Это молчаливые спутники, которые в нужный момент поведают вам о том, что происходит в системе. И здесь, как и в любом хорошем партнерстве, ценность в том, чтобы уметь слушать.
С более практической точки зрения журналы состоят из строк текста, документирующих определенные события в рамках системы. Они содержат такую информацию, как:
- дата и время события: чтобы отслеживать, когда произошло что-либо;
- тип события: ошибка, предупреждение, информация, отладка и другие;
- сообщение с описанием: объяснение того, что произошло;
- дополнительный контекст: технические сведения, такие как значения переменных на момент события, данные о графике, такие как таймфрейм или символ, и даже значение какого-либо параметра, которое было использовано.
Преимущества использования журналов
Ниже мы подробно рассмотрим основные преимущества, которые дает нам использование журналов, подчеркивая то, как они помогают оптимизировать операции и обеспечивают эффективность советников.
1. Отладка и устранение неполадок
Хорошо структурированные журналы меняют все. Они не только позволяют увидеть, что пошло не так, но также и узнать, почему и как это произошло. Ошибка, которая раньше казалась ускользающей тенью, становится чем-то, что вы можете отследить, понять и исправить. Это похоже на лупу, которая увеличивает каждую критически важную деталь, имевшую место на момент возникновения проблемы.
К примеру, представьте, что запрос завершается непредвиденным сбоем. Без журналов проблему можно списать на случайность, а решение будет состоять из догадок. Но с четко структурированными журналами этот сценарий изменится. Сообщение об ошибке выглядит как маячок, сопровождаемый ценными данными о соответствующем запросе: отправленные параметры, ответ сервера или даже непредвиденный таймаут. Такой контекст не только сообщит о причинах ошибки, но также и укажет путь к ее решению.
Практический пример журнальной записи об ошибке:
[2024-11-18 14:32:15] ERROR : Limit Buy Trade Request Failed - Invalid Price in Request [10015 | TRADE_RETCODE_INVALID_PRICE]
Данная запись сообщает, в чем конкретно заключалась ошибка (попытка отправить запрос на сервер), и указывает, что код ошибки был 10015, что означает ошибку неправильной цены в запросе. Теперь разработчик советника сможет точно узнать, в какой момент возникла ошибка, как в нашем примере она возникла при отправке лимитного ордера на покупку.
2. Аудит и проверка соответствия
Журналы играют важнейшую роль, когда дело касается аудита и проверки соответствия стандартам и политикам безопасности. В отраслях, работающих с чувствительными данными, например, в финансах, требование о ведении подробных записей является не только вопросом организации: это вопрос соблюдения законов и нормативно-правовых актов, регулирующих деятельность предприятия.
Они служат надежным оттиском, где документируется каждое нужное действие: кто получил доступ к информации, в какое время, и что было сделано. Это не только обеспечивает прозрачность среды, но также становится мощным инструментом для расследования инцидентов в системе безопасности или случаев применения сомнительных практик. Благодаря хорошо структурированным журналам, выявление нехарактерной активности перестает быть расплывчатой задачей, и становится непосредственным и эффективным процессом, укрепляющим доверие к системе и ее безопасность.
3. Мониторинг производительности
Использование журналов также играет важнейшую роль для мониторинга производительности систем. В производственной среде, где скорость и эффективность ответа имеют существенное значение, журналы позволяют вам отслеживать состояние вашего советника в режиме реального времени. Журналы производительности могут включать информацию о времени отклика на заказ, использовании ресурсов (таких как процессор, память и диск), а также о частоте возникновения ошибок. На основе этой информации могут быть приняты корректирующие меры, такие как оптимизация кода.
Пример записи в журнале производительности:
[2024-11-18 16:45:23] INFO - Server response received, EURUSD purchase executed successfully | Volume: 0.01 | Price: 1.01234 | Duration: 49 ms
4. Автоматизация и алерты
Автоматизацию можно выделить, как одно из величайших преимуществ журналов, особенно в интеграции с инструментами мониторинга и анализа. При надлежащей настройке журналы могут вызывать срабатывание автоматических алертов в случае обнаружения критических событий, обеспечивая, получение разработчиком мгновенных уведомлений о сбоях, ошибках или даже крупных убытках, сгенерированных советником.
Такие алерты могут быть не только в форме предупреждений: их можно отправлять по электронной почте, СМС или интегрировать в управленческие платформы, что позволит реагировать на них с быстротой и точностью. Такой уровень автоматизации не только защищает систему от проблем, которые могут быстро разрастись, но также дает разработчику возможность действовать на упреждение, минимизируя тем самым последствия и обеспечивая стабильность среды.
Пример журнальной записи с алертом:
[2024-11-18 19:15:50] FATAL - CPU usage exceeded 90%, immediate attention required.
Говоря кратко, использование журналов дает множество выгод помимо простой записи информации о том, что происходит в вашем советнике. Они являются мощным инструментом для отладки, мониторинга производительности, аудита безопасности и автоматизации алертов, что делает их неотъемлемым элементом для эффективного управления инфраструктурой советников.
Определение требований к библиотеке
Перед началом разработки необходимо прийти к четкому пониманию того, чего мы хотим достичь. Так мы избегаем переработок и гарантируем, что библиотека будет соответствовать реальным потребностям тех, кто будет ею пользоваться. Памятуя об этом, я перечислил основные функции, которые должна содержать библиотека для работы с журналами:
-
Singleton
Библиотека должна быть реализована в соответствии с паттерном проектирования Singleton ("одиночка"), чтобы гарантировать доступ всех экземпляров к одному и тому же объекту-журналу. Это обеспечивает единообразие управления журналами в различных частях кода и позволяет избежать дублирования ресурсов. В следующей статье я раскрою эту тему подробнее.
-
Хранение в базе данных
Я хочу, чтобы все журналы хранились в базе данных, чтобы была возможность обращаться к данным с запросами. Это важнейший функционал для анализа истории, аудитов и даже для определения паттернов поведения.
-
Несколько типов вывода
Библиотека должна обеспечивать гибкость в плане просмотра журналов различными способами, например, через:
- консоль
- терминал
- файлы
- базы данных
Такое разнообразие позволит получать доступ к логам в удобном формате под различные ситуации.
-
Уровни журналов
Нам следует обеспечить поддержку различных уровней журналов для классификации сообщений в соответствии со степенью их серьезности. Уровни бывают следующими:
- DEBUG: детализированные сообщения для целей отладки,
- INFO: общая информация об операции системы,
- ALERT: алерты для ситуаций, которые требуют внимания, но не являются критическими,
- ERROR: ошибки, которые затрагивают части системы, но позволяют продолжать работу непрерывно,
- FATAL: серьезные проблемы, которые прерывают процесс исполнения системы.
-
Пользовательский формат журналов
Важно обеспечить возможность кастомизации формата журналов. Пример представлен ниже:
([{timestamp}] {level} : {origin} {message})
Это обеспечивает гибкость, позволяющую адаптировать вывод к конкретным нуждам каждого проекта.
-
Ротация журналов
Во избежание неконтролируемого роста файлов журналов, в библиотеке должна быть реализована система ротации, сохраняющая журналы в отдельные файлы каждый день, или после достижения ими определенного размера.
-
Динамические столбцы данных
Важнейшим элементом является возможность хранения динамических метаданных в формате JSON. Такие данные могут включать информацию, относящуюся к советнику на момент внесения сообщения в журнал, что дополнит контекст журналов.
-
Автоматические уведомления
Библиотека также должна иметь возможность отправлять уведомления при определенных уровнях серьезности, например, FATAL. Должна быть реализована возможность отправлять такие алерты в виде:
- электронных писем
- SMS
- предупреждений в терминале
Это обеспечит незамедлительное информирование ответственных лиц о критических проблемах.
-
Измерение длины кода
Наконец, крайне важно добавить функционал для измерения длины фрагментов кода. Это позволит нам выявлять узкие места в производительности и оптимизировать процессы.
Вышеописанные требования станут фундаментом для разработки нашей библиотеки. По мере ее реализации мы будем рассматривать, как строятся указанные функциональные элементы, и как они интегрируются в проект. Такой структурированный подход не только поможет нам сохранить фокус, но и обеспечит надежность, гибкость и адаптируемость конечного продукта к нуждам разработчиков советников в среде MQL5.
Структурирование основы проекта
Теперь, когда мы определили требования, следующим шагом будет структурирование основы нашего проекта. Корректная организация файлов и папок крайне важна для того, чтобы код оставался модульным, простым для понимания и сопровождения. Учитывая вышесказанное, давайте приступим к созданию первоначальной структуры папок и файлов для нашей библиотеки журналов.
Первым шагом будет создание новой папки внутри папки Include, которая будет отвечать за хранение всех файлов, связанных с библиотекой журналов. Чтобы это сделать, просто кликните правой кнопкой мыши по папке Include во вкладке навигации, как показано на картинке, и выберите опцию "Новая папка":
Появится окно с опциями для нового файла. Выберите в нем опцию "Новый класс" и нажмите "Далее", после чего вы увидите такое окно:
Заполните параметры, указав имя класса CLogify. После этого я поменял имя автора и ссылку, но эти параметры не имеют значения для класса. Когда закончите, это будет выглядеть следующим образом:
//+------------------------------------------------------------------+ //| Logify.mqh | //| joaopedrodev | //| https://www.mql5.com/en/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/en/users/joaopedrodev" #property version "1.00" class CLogify { private: public: CLogify(); ~CLogify(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CLogify::CLogify() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CLogify::~CLogify() { } //+------------------------------------------------------------------+
Создайте еще два файла, следуя этой процедуре:
- LogifyLevel.mqh → Определяет перечисление с уровнями журналов, которые мы будем использовать.
- LogifyModel.mqh → Структура данных, которая будет хранить подробную информацию о каждом журнале.
В итоге мы получим следующую структуру файлов и папок:
|--- Logify |--- Logify.mqh |--- LogifyLevel.mqh |--- LogifyModel.mqh
Создав базовую структуру и первоначальные файлы, мы получим функциональный скелет библиотеки журналов.
Создание степеней серьезности
Здесь мы воспользуемся файлом LogifyLevel.mqh, который будет определять различные степени серьезности сообщений, которые могут встречаться в журнале, представленные в виде перечисления. Мы будем использовать следующий код для перечисления:
enum ENUM_LOG_LEVEL { LOG_LEVEL_DEBUG = 0, // Debug LOG_LEVEL_INFOR, // Infor LOG_LEVEL_ALERT, // Alert LOG_LEVEL_ERROR, // Error LOG_LEVEL_FATAL, // Fatal };
Пояснение
- Перечисление: каждое значение перечисления представляет собой степень серьезности записей в журнале, от LOG_LEVEL_DEBUG (наименьшая серьезность) до LOG_LEVEL_FATAL (наибольшая серьезность).
- Удобство пользования: данное перечисление будет использоваться для разбиения журнальных записей по разным категориям серьезности, что упростит фильтрацию или совершение определенных действий на основе степени серьезности сообщения.
Создание модели данных
Теперь давайте создадим структуру данных для хранения информации о журнале, которую будет обрабатывать библиотека. Данная структура будет храниться в файле LogifyModel.mqh и служить основой для хранения всех журнальных записей, созданных системой.
Ниже представлен код для определения структуры MqlLogifyModel, которая будет отвечать за хранение важнейших данных для каждой журнальной записи, таких как временная метка (дата и время события), источник (в результате чего была сгенерирована запись), сообщение записи и любые другие дополнительные метаданные.
//+------------------------------------------------------------------+ //| LogifyModel.mqh | //| joaopedrodev | //| https://www.mql5.com/en/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/en/users/joaopedrodev" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "LogifyLevel.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct MqlLogifyModel { ulong timestamp; // Date and time of the event ENUM_LOG_LEVEL level; // Severity level string origin; // Log source string message; // Log message string metadata; // Additional information in JSON or text MqlLogifyModel::MqlLogifyModel(void) { timestamp = 0; level = LOG_LEVEL_DEBUG; origin = ""; message = ""; metadata = ""; } MqlLogifyModel::MqlLogifyModel(ulong _timestamp,ENUM_LOG_LEVEL _level,string _origin,string _message,string _metadata) { timestamp = _timestamp; level = _level; origin = _origin; message = _message; metadata = _metadata; } }; //+------------------------------------------------------------------+
Пояснение к структуре данных
- timestamp: поле типа ulong, которое хранит дату и время события. Данное поле будет содержать временную метку журнала на момент создания журнальной записи.
- level: степень серьезности сообщения.
- origin: поле строкового типа, которое определяет происхождение журнальной записи. Оно может быть полезно для определения того, какая часть системы сгенерировала сообщение в журнале (например, имя модуля или функции).
- message: само сообщение журнальной записи, также строкового типа, которое описывает событие или действие, которые имели место в системе.
- metadata: дополнительное поле, хранящее вспомогательную информацию о журнальной записи. Может быть объектом JSON либо простой текстовой строкой и содержит дополнительные данные, относящиеся к событию. Будет полезно для хранения контекстуальной информации, такой как параметры исполнения или данные, специфические для системы.
Конструкторы
- Конструктор по умолчанию инициализирует все поля с пустыми или нулевыми значениями.
- Параметризованный конструктор позволяет вам создавать экземпляры MqlLogifyModel путем прямого заполнения полей определенными значениями, такими как временная метка, источник, сообщение и метаданные.
Реализация главного класса CLogify
Теперь мы приступим к реализации главного класса библиотеки журналов CLogify , который будет служить основой для управления журналами и их отображения. Этот класс включает в себя методы для различных уровней журналов, а также универсальный метод Append, которым будут пользоваться все остальные методы.
Класс будет определен в файле Logify.mqh и будет содержать следующие методы:
//+------------------------------------------------------------------+ //| class : CLogify | //| | //| [PROPERTY] | //| Name : Logify | //| Heritage : No heritage | //| Description : Core class for log management. | //| | //+------------------------------------------------------------------+ class CLogify { private: public: CLogify(); ~CLogify(); //--- Generic method for adding logs bool Append(ulong timestamp, ENUM_LOG_LEVEL level, string message, string origin = "", string metadata = ""); //--- Specific methods for each log level bool Debug(string message, string origin = "", string metadata = ""); bool Infor(string message, string origin = "", string metadata = ""); bool Alert(string message, string origin = "", string metadata = ""); bool Error(string message, string origin = "", string metadata = ""); bool Fatal(string message, string origin = "", string metadata = ""); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogify::CLogify() { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogify::~CLogify() { } //+------------------------------------------------------------------+Будут реализованы методы класса для отображения логов в консоли с помощью функции Print. В дальнейшем такие журнальные записи можно перенаправлять в другие каналы вывода, например, файлы, базы данных или графики.
//+------------------------------------------------------------------+ //| Generic method for adding logs | //+------------------------------------------------------------------+ bool CLogify::Append(ulong timestamp,ENUM_LOG_LEVEL level,string message,string origin="",string metadata="") { MqlLogifyModel model(timestamp,level,origin,message,metadata); string levelStr = ""; switch(level) { case LOGIFY_DEBUG: levelStr = "DEBUG"; break; case LOGIFY_INFO: levelStr = "INFO"; break; case LOGIFY_ALERT: levelStr = "ALERT"; break; case LOGIFY_ERROR: levelStr = "ERROR"; break; case LOGIFY_FATAL: levelStr = "FATAL"; break; } Print("[" + TimeToString(timestamp) + "] [" + levelStr + "] [" + origin + "] - " + message + " " + metadata); return(true); } //+------------------------------------------------------------------+ //| Debug level message | //+------------------------------------------------------------------+ bool CLogify::Debug(string message,string origin="",string metadata="") { return(this.Append(TimeCurrent(),LOG_LEVEL_DEBUG,message,origin,metadata)); } //+------------------------------------------------------------------+ //| Infor level message | //+------------------------------------------------------------------+ bool CLogify::Infor(string message,string origin="",string metadata="") { return(this.Append(TimeCurrent(),LOG_LEVEL_INFOR,message,origin,metadata)); } //+------------------------------------------------------------------+ //| Alert level message | //+------------------------------------------------------------------+ bool CLogify::Alert(string message,string origin="",string metadata="") { return(this.Append(TimeCurrent(),LOG_LEVEL_ALERT,message,origin,metadata)); } //+------------------------------------------------------------------+ //| Error level message | //+------------------------------------------------------------------+ bool CLogify::Error(string message,string origin="",string metadata="") { return(this.Append(TimeCurrent(),LOG_LEVEL_ERROR,message,origin,metadata)); } //+------------------------------------------------------------------+ //| Fatal level message | //+------------------------------------------------------------------+ bool CLogify::Fatal(string message,string origin="",string metadata="") { return(this.Append(TimeCurrent(),LOG_LEVEL_FATAL,message,origin,metadata)); } //+------------------------------------------------------------------+
Теперь, когда у нас есть основная структура и методы для отображения базовых журналов, следующим шагом будет создание тестов для обеспечения их корректной работы. Опять же, поддержку различных типов вывода, таких как файлы, базы данных и графики, мы добавим позднее.
Тесты
Для тестирования библиотеки Logify мы создадим специального советника. Во-первых, создайте внутри папки Expert новую папку под именем Logify, которая будет содержать все файлы тестов. Затем создайте файл LogifyTest.mq5 со следующей первоначальной структурой://+------------------------------------------------------------------+ //| LogifyTest.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Добавьте главный файл библиотеки и создайте экземпляр класса CLogify:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify logify;
В функцию OnInit добавьте вызовы логирования, чтобы проверить все доступные уровни:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14"); logify.Infor("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1"); logify.Alert("Stop Loss adjusted to breakeven level", "Risk Management", "Order ID: 12345678"); logify.Error("Failed to send sell order", "Order Management", "Reason: Insufficient balance"); logify.Fatal("Failed to initialize EA: Invalid settings", "Initialization", "Missing or incorrect parameters"); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
В процессе исполнения кода мы ожидаем получить следующие сообщения в терминале MetaTrader 5:
Заключение
Библиотека Logify была успешно протестирована и корректно отображает сообщения в журнале для всех доступных уровней. Данная базовая структура обеспечивает организованное управление журналами с возможностью расширения, что закладывает прочный фундамент для будущих улучшений, таких как интеграция с базами данных, файлами или графиками.
Благодаря модульной реализации и простым в использовании методам, библиотека Logify предлагает гибкость и прозрачность в управлении логами приложений на MQL5. Следующими шагами будут создание альтернативных методов вывода и добавление динамических настроек для кастомизации работы библиотеки. До встречи в следующей статье!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16447
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования