
Искусство ведения логов (Часть 3): Изучение обработчиков для сохранения логов
Введение
В первой статье данного цикла, Искусство ведения логов (Часть 1): Основные понятия и первые шаги в MQL5, мы начали с создания собственной библиотеки логов для разработки советника. В указанной статье мы определили основные мотивы для создания такого важнейшего инструмента: преодолеть ограничения, присущие нативным логам MetaTrader 5, и привнести надежное, кастомизируемое и производительное решение во вселенную MQL5.
Напомним основные пункты, которые были рассмотрены: мы заложили основу для нашей библиотеки, утвердив следующие базовые требования:
- Надежная структура с применением паттерна Singleton, обеспечивающая согласованность между фрагментами кода.
- Продвинутый режим хранения для сохранения логов в базах данных, обеспечивая отслеживаемую историю для глубинного анализа и аудитов.
- Гибкость вывода, позволяющая сохранять или отображать логи с удобством, будь то в консоли, в файлах, в терминале или в базе данных.
- Классификация по уровням логирования, отличающая информационные сообщения от критических алертов и ошибок.
- Кастомизация формата вывода для удовлетворения уникальных потребностей каждого разработчика или проекта.
Благодаря этому прочному фундаменту стало ясно, что система логирования, которую мы разрабатываем, будет представлять собой гораздо больше чем простой логгер событий: это будет стратегический инструмент для понимания, мониторинга и оптимизации поведения советников в реальном времени.
Теперь в этой третьей статье мы сделаем важный шаг: поймем концепцию обработчиков. Если программы форматирования структурируют данные, то обработчики отвечают за то, куда будут отправляться логи. Они выступают в качестве распределителей сообщений, направляя их в соответствующие места, будь то файлы, консоли, базы данных или даже системы уведомлений. В этой статье мы поймем логику, лежащую в основе обработчиков, создадим практические примеры их применения в различных сценариях и изучим их интеграцию с программами форматирования. К концу статьи вы получите все необходимые инструменты для создания высоко настраиваемых и эффективных потоков логов. Приступим?
Что такое обработчики?
Обработчики — это ключевые компоненты, определяющие, куда направляются логи. Их можно представить как диспетчеров сообщений, которые получают информацию от регистратора и направляют ее в нужное место назначения, будь то консоль, файл, электронная почта или даже удаленный сервер.
Представьте, что вы управляете фабрикой. Продукты (логи) необходимо транспортировать в разные места назначения: некоторые отправляются на склад, некоторые — в отдел отгрузки, а некоторые хранятся в качестве исторических записей. Диспетчер определяет маршрут каждого сообщения, и эту роль выполняют обработчики.
Каждый обработчик может иметь индивидуальные настройки, такие как уровни серьезности (например, отправлять только сообщения об ошибках), форматы вывода (например, включать временные метки или нет) и пункты назначения.
Эти компоненты обеспечивают разделение и интеллектуальную маршрутизацию логов, что особенно важно для средних и крупных приложений. Обработчики обеспечивают такие функции, как помощь разработчикам в отладке ошибок в режиме реального времени в консоли, хранение подробных логов для будущего анализа, отправка критических уведомлений по электронной почте, когда происходит что-то срочное, или пересылка информации о мониторинге на центральный сервер. Все это можно сделать одновременно, без необходимости сложных настроек.
Как работают обработчики
Чтобы понять, как обработчики работают на практике, давайте рассмотрим пример из библиотеки. На приведенной ниже схеме показан основной процесс регистрации сообщений:
Сейчас основной функцией класса CLogify является метод Append, который отвечает за прием данных логов, включая уровень серьезности, сообщение, источник и время записи в лог. С помощью этих данных метод Append создает переменную типа MqlLogifyModel, которая отправляется в консоль терминала через нативную функцию Print.
Этот процесс работает, но имеет ограничения: все логи могут отображаться только в консоли, и нет гибкости для обработки или хранения этих сообщений в другом месте.
С добавлением обработчиков процесс значительно улучшился. Взглянем на новую диаграмму:
В новом потоке:
- Метод Append продолжает получать информацию из лога (серьезность, сообщение, источник, время и т.д.).
- Он создает ту же переменную MqlLogifyModel для хранения данных.
- Вместо отправки непосредственно в консоль, лог теперь передается в список обработчиков, представленный массивом Handlers.
Каждый обработчик может обрабатывать данные независимо, что позволяет направлять сообщения по нескольким адресам.
Вот три основных примера обработчиков, которые можно использовать в этой системе:
- HandlerTerminal выводит логи непосредственно в терминале MetaTrader 5, что полезно для диагностики и отладки в режиме реального времени.
- HandlerFile записывает логи в файлы формата .txt или .log, идеально подходит для хранения истории исполнений или создания подробных отчетов для будущего анализа.
- HandlerDatabase сохраняет логи в базе данных, такой как SQLite, что позволяет выполнять расширенные запросы данных, включая анализ трендов или более сложные аудиты.
Приведу практический пример полезности обработчиков. Представьте, что вы разработали торгового робота, которому нужна эффективная система логов для мониторинга его работы. Вы можете настроить обработчики следующим образом:
- Сохранять в файл только сообщения DEBUG и INFO, чтобы записывать все выполненные операции, включая входы и выходы из рынка.
- Выводить в терминал только сообщения WARN и ERROR, что позволит отслеживать проблемы в режиме реального времени.
- Записывать критические ошибки в базу данных, чтобы инженеры или аналитики могли позже получить и проанализировать соответствующую информацию.
Такая структура обеспечивает более надежную, структурированную и эффективную систему логирования. С помощью обработчиков вы получаете полный контроль над тем, как и где обрабатываются и хранятся логи.
Реализация базового класса обработчиков
Давайте реализуем базовый класс CLogifyHandler , который будет общей базой (родительским классом) для всех обработчиков, создаваемых позже. Начнем с создания папки Handlers в корневом каталоге библиотеки. Внутри этой новой папки создайте файл LogifyHandler. В созданном файле будет определен класс CLogifyHandler, который изначально будет иметь только базовый конструктор и деструктор. Исходный код будет выглядеть следующим образом::
//+------------------------------------------------------------------+ //| LogifyHandler.mqh | //| joaopedrodev | //| https://www.mql5.com/en/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/en/users/joaopedrodev" //+------------------------------------------------------------------+ //| class : CLogifyHandler | //| | //| [PROPERTY] | //| Name : CLogifyHandler | //| Heritage : No heritage | //| Description : Base class for all log handlers. | //| | //+------------------------------------------------------------------+ class CLogifyHandler { public: CLogifyHandler(void); ~CLogifyHandler(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyHandler::CLogifyHandler(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyHandler::~CLogifyHandler(void) { } //+------------------------------------------------------------------+
На данном этапе класс CLogifyHandler служит шаблоном, который будет расширяться для включения дополнительных функций.
Теперь добавим необходимые импорты, начиная с файла LogifyModel.mqh, который определяет модель логирования, которая будет использоваться обработчиками.
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "../LogifyModel.mqh"
Этот файл содержит определение класса или структуры MqlLogifyModel, которая используется для инкапсуляции данных для каждого сообщения, таких как уровень серьезности, сообщение и источник.
Следующим шагом является добавление двух защищенных атрибутов к классу:
- m_name хранит имя обработчика, которое может быть полезно для идентификации во время отладки или отчетности;
- m_level определяет уровень серьезности, который будет обрабатывать обработчик (например, DEBUG, INFO, ERROR).
Кроме того, мы создадим публичные методы для установки и извлечения этих значений.
class CLogifyHandler { protected: string m_name; ENUM_LOG_LEVEL m_level; public: //--- Set/Get void SetLevel(ENUM_LOG_LEVEL level); string GetName(void); ENUM_LOG_LEVEL GetLevel(void); }; //+------------------------------------------------------------------+ //| Set level | //+------------------------------------------------------------------+ void CLogifyHandler::SetLevel(ENUM_LOG_LEVEL level) { m_level = level; } //+------------------------------------------------------------------+ //| Get name | //+------------------------------------------------------------------+ string CLogifyHandler::GetName(void) { return(m_name); } //+------------------------------------------------------------------+ //| Get level | //+------------------------------------------------------------------+ ENUM_LOG_LEVEL CLogifyHandler::GetLevel(void) { return(m_level); } //+------------------------------------------------------------------+
Атрибут m_name может быть установлен только в производных классах через их конструкторы, что обеспечивает безопасность и инкапсуляцию. Поэтому метода SetName нет.
Существует три основных метода, которые должны реализовывать все обработчики:
- Emit(MqlLogifyModel &data) обрабатывает лог и отправляет его в указанное место назначения (файл, консоль, база данных и т.д.).
- Метод Flush() завершает или очищает все незавершенные операции.
- Метод Close() закрывает обработчик и освобождает связанные с ним ресурсы.
На данный момент эти функции определены как виртуальные с пустой реализацией по умолчанию. Это позволяет каждому дочернему классу настраивать поведение по мере необходимости.
class CLogifyHandler { public: //--- Handler methods virtual void Emit(MqlLogifyModel &data); // Processes a log message and sends it to the specified destination virtual void Flush(void); // Clears or completes any pending operations virtual void Close(void); // Closes the handler and releases any resources }; //+------------------------------------------------------------------+ //| Processes a log message and sends it to the specified destination| //+------------------------------------------------------------------+ void CLogifyHandler::Emit(MqlLogifyModel &data) { } //+------------------------------------------------------------------+ //| Clears or completes any pending operations | //+------------------------------------------------------------------+ void CLogifyHandler::Flush(void) { } //+------------------------------------------------------------------+ //| Closes the handler and releases any resources | //+------------------------------------------------------------------+ void CLogifyHandler::Close(void) { } //+------------------------------------------------------------------+
На данном этапе методы ничего не делают, так как реализация конкретного поведения каждого обработчика зависит от дочерних классов (таких как HandlerFile, HandlerDatabase и т.д.).
Реализация обработчиков
После реализации базового класса CLogifyHandler мы можем приступить к созданию специализированных обработчиков, наследуемых от него. Такой подход соответствует основным принципам объектно-ориентированного программирования, таким как наследование и полиморфизм, обеспечивая модульность и гибкость кода. Каждый специализированный обработчик будет отвечать за обработку логов определенным образом, используя общую структуру базового класса, но реализуя свою собственную логику для методов Emit, Flush и Close.
Прежде чем реализовывать обработчики, давайте лучше структурируем наш проект. Создайте три новых файла в папке Handlers: LogifyHandlerConsole.mqh, LogifyHandlerDatabase.mqh и LogifyHandlerFile.mqh. Окончательная структура будет выглядеть следующим образом:
В файле LogifyHandlerConsole.mqh мы создадим класс CLogifyHandlerConsole, который наследует атрибуты и методы базового класса CLogifyHandler. Одно из первых изменений будет касаться значения переменной m_name, установленного на "console" в конструкторе класса. Это поможет четко идентифицировать обработчик во время выполнения. Вот начальное определение класса:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "LogifyHandler.mqh" //+------------------------------------------------------------------+ //| class : CLogifyHandlerConsole | //| | //| [PROPERTY] | //| Name : CLogifyHandlerConsole | //| Heritage : CLogifyHandler | //| Description : Log handler, inserts data into terminal window. | //| | //+------------------------------------------------------------------+ class CLogifyHandlerConsole : public CLogifyHandler { public: CLogifyHandlerConsole(void); ~CLogifyHandlerConsole(void); virtual void Emit(MqlLogifyModel &data); // Processes a log message and sends it to the specified destination virtual void Flush(void); // Clears or completes any pending operations virtual void Close(void); // Closes the handler and releases any resources }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyHandlerConsole::CLogifyHandlerConsole(void) { m_name = "console"; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyHandlerConsole::~CLogifyHandlerConsole(void) { } //+------------------------------------------------------------------+
Функция Emit отвечает в первую очередь за обработку логов и их отправку в соответствующее место назначения. Для консоли это означает вывод отформатированного сообщения в терминале MetaTrader. Вот реализация этого метода:
//+------------------------------------------------------------------+ //| Processes a log message and sends it to the specified destination| //+------------------------------------------------------------------+ void CLogifyHandlerConsole::Emit(MqlLogifyModel &data) { if(data.level >= this.GetLevel()) { Print("Console handler: ",data.formated); } } //+------------------------------------------------------------------+
Обратите внимание, что перед выводом сообщения проверяется, соответствует ли уровень лога (data.level) уровню, настроенному в обработчике. Это гарантирует, что будут отображаться только важные или релевантные сообщения.
Следуя тому же принципу, что и для обработчика консоли, мы можем создать другие специализированные обработчики, например, для баз данных и файлов. Необходимо изменить файлы LogifyHandlerDatabase.mqh и LogifyHandlerFile.mqh. Хотя эти обработчики могут иметь одинаковую базовую логику, их конкретные реализации Emit могут различаться.
Этот обработчик предназначен для хранения логов в базе данных, хотя на данный момент мы будем отображать сообщение в консоли в демонстрационных целях. Код для функции Emit будет следующим:
//+------------------------------------------------------------------+ //| Processes a log message and sends it to the specified destination| //+------------------------------------------------------------------+ void CLogifyHandlerDatabase::Emit(MqlLogifyModel &data) { if(data.level >= this.GetLevel()) { Print("Database handler: ",data.formated); } } //+------------------------------------------------------------------+
Обработчик LogifyHandlerFile будет использоваться для записи логов в определенный файл. Вот начальная реализация для Emit:
//+------------------------------------------------------------------+ //| Processes a log message and sends it to the specified destination| //+------------------------------------------------------------------+ void CLogifyHandlerFile::Emit(MqlLogifyModel &data) { if(data.level >= this.GetLevel()) { Print("File handler: ",data.formated); } } //+------------------------------------------------------------------+
Хотя мы определили методы Flush и Close в базовом классе, не все обработчики должны их сразу реализовывать.
- Метод Flush полезен в более сложных обработчиках, например, при записи в файл или потоковой передаче данных в реальном времени.
- Метод Close нужен для освобождения ресурсов, например, подключения к базе данных, или закрытия потоков записи.
Для консольного обработчика эти методы остаются пустыми, так как дополнительных операций не требуется. Помните, что я показываю только фрагменты кода, полная версия кода приведена в конце статьи.
Добавление обработчиков в класс CLogify
Теперь, когда мы реализовали обработчики и они работают изолированно, пришло время интегрировать их в основной класс библиотеки, называемый CLogify. Для этого мы начинаем с импорта базового класса CLogifyHandler , который содержит необходимую структуру для всех обработчиков:
#include "Handlers/LogifyHandler.mqh" #include "Handlers/LogifyHandlerConsole.mqh" #include "Handlers/LogifyHandlerDatabase.mqh" #include "Handlers/LogifyHandlerFile.mqh"
В реализации класса CLogify мы добавим частный атрибут для хранения обработчиков, которые будут использоваться. Я выбрал массив указателей типа CLogifyHandler, поскольку обработчики будут управляться динамически.
Кроме того, класс будет иметь специальные методы для управления обработчиками:
- AddHandler добавляет новый обработчик в массив.
- HasHandler проверяет, существует ли в списке определенный обработчик, используя его имя в качестве критерия.
- GetHandler получает обработчик по имени или по индексу в массиве.
- SizeHandlers возвращает общее количество обработчиков в списке.
Вот обновленный код для класса CLogify, теперь с этими методами:
class CLogify { private: CLogifyHandler *m_handlers[]; public: //--- Handler void AddHandler(CLogifyHandler *handler); bool HasHandler(string name); CLogifyHandler *GetHandler(string name); CLogifyHandler *GetHandler(int index); int SizeHandlers(void); }; //+------------------------------------------------------------------+ //| Add handler to handlers array | //+------------------------------------------------------------------+ void CLogify::AddHandler(CLogifyHandler *handler) { int size = ArraySize(m_handlers); ArrayResize(m_handlers,size+1); m_handlers[size] = GetPointer(handler); } //+------------------------------------------------------------------+ //| Checks if handler is already in the array by name | //+------------------------------------------------------------------+ bool CLogify::HasHandler(string name) { int size = ArraySize(m_handlers); for(int i=0;i<size;i++) { if(m_handlers[i].GetName() == name) { return(true); } } return(false); } //+------------------------------------------------------------------+ //| Get handler by name | //+------------------------------------------------------------------+ CLogifyHandler *CLogify::GetHandler(string name) { int size = ArraySize(m_handlers); for(int i=0;i<size;i++) { if(m_handlers[i].GetName() == name) { return(m_handlers[i]); } } return(NULL); } //+------------------------------------------------------------------+ //| Get handler by index | //+------------------------------------------------------------------+ CLogifyHandler *CLogify::GetHandler(int index) { return(m_handlers[index]); } //+------------------------------------------------------------------+ //| Gets the total size of the handlers array | //+------------------------------------------------------------------+ int CLogify::SizeHandlers(void) { return(ArraySize(m_handlers)); } //+------------------------------------------------------------------+
После подготовки методов управления обработчиками давайте настроим работу функции Append так, чтобы она использовала обработчики при обработке логов.
Теперь метод Append будет проходить по всем доступным обработчикам в массиве и вызывать метод Emit каждого из них, чтобы лог отправлялся в соответствующее место назначения (например, консоль, база данных и т.д.).
Вот обновленный код для метода Append:
//+------------------------------------------------------------------+ //| 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) { //--- If the formatter is not configured, the log will not be recorded. if(m_formatter == NULL) { return(false); } //--- Textual name of the log level string levelStr = ""; switch(level) { case LOG_LEVEL_DEBUG: levelStr = "DEBUG"; break; case LOG_LEVEL_INFOR: levelStr = "INFOR"; 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); data.formated = m_formatter.FormatLog(data); //--- Call handlers int size = this.SizeHandlers(); for(int i=0;i<size;i++) { m_handlers[i].Emit(data); } return(true); } //+------------------------------------------------------------------+
Некоторые замечания:
- Отформатированный лог хранится в data.formatted , что гарантирует доступность полной информации для всех обработчиков.
- Каждый обработчик обрабатывает лог независимо, вызывая свой метод Emit.
Последней настройкой, которую я сделаю, будет удаление указателей массива в деструкторе класса:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogify::~CLogify() { //--- Delete formatter if(m_formatter != NULL) { delete m_formatter; } //--- Delete handlers int size_handlers = ArraySize(m_handlers); for(int i=0;i<size_handlers;i++) { delete m_handlers[i]; } } //+------------------------------------------------------------------+
Тестирование обработчиков
В этом примере мы будем использовать ранее упомянутый тестовый файл LogifyTest.mq5. Наша цель — продемонстрировать, как настроить и использовать два обработчика логов, каждый с разным уровнем логирования, и как эти логи будут записываться на основе настроенных фильтров.
Сначала мы создадим два обработчика, которые будут отвечать за запись логов в разных местах и на разных уровнях:
- Консоль – это будет экземпляр CLogifyHandlerConsole , он будет настроен на запись сообщений уровня DEBUG, то есть всех сообщений, от самых подробных до критических ошибок.
- Файл – это будет экземпляр CLogifyHandlerFile , он будет настроен на захват только сообщений уровня INFO, отфильтровывая сообщения DEBUG.
Вот код для настройки этих обработчиков:
int OnInit() { //--- Console CLogifyHandler *handler_console = new CLogifyHandlerConsole(); handler_console.SetLevel(LOG_LEVEL_DEBUG); //--- File CLogifyHandler *handler_file = new CLogifyHandlerFile(); handler_file.SetLevel(LOG_LEVEL_INFOR); return(INIT_SUCCEEDED); }
После настройки обработчиков следующим шагом будет их добавление в наш базовый класс CLogify , который отвечает за управление всеми логами приложения.
Кроме того, мы добавим программу форматирования, чтобы определить, как будут отображаться логи. Программа форматирования в этом примере будет форматировать логи по шаблону: "час:минута:секунда, [уровень логирования], сообщение".
После настройки обработчиков и программы форматирования мы выведем три сообщения с разными уровнями. Ниже приведен полный код для этого шага:
int OnInit() { //--- Console CLogifyHandler *handler_console = new CLogifyHandlerConsole(); handler_console.SetLevel(LOG_LEVEL_DEBUG); //--- File CLogifyHandler *handler_file = new CLogifyHandlerFile(); handler_file.SetLevel(LOG_LEVEL_INFOR); //--- Config logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","{date_time} [{levelname}] {msg}")); logify.AddHandler(handler_console); logify.AddHandler(handler_file); //--- Logs logify.Debug("Debug Message"); logify.Infor("Information Message"); logify.Error("Error Message"); return(INIT_SUCCEEDED); }
При запуске вышеуказанного кода в консоли будет отображен следующий результат:
Console handler: 03:20:05 [DEBUG] Debug Message Console handler: 03:20:05 [INFOR] Information Message File handler: 03:20:05 [INFOR] Information Message Console handler: 03:20:05 [ERROR] Error Message File handler: 03:20:05 [ERROR] Error Message
Интерпретация результатов:
- Обработчик консоли (handler_console) захватил все сообщения, от DEBUG до ERROR. Поэтому в консоли было записано три записи, по одной для каждого выданного лога.
- Обработчик файлов (handler_file) был настроен на запись только сообщений уровня INFO или выше. Таким образом, он игнорировал сообщения уровня DEBUG и записывал только сообщения INFO и ERROR, всего две записи в файле лога.
Заключение
В этой статье мы сделали важный шаг в создании нашей библиотеки логирования MQL5. Мы разобрали концепцию обработчиков, поняв их важную роль в качестве "проводников" логов к различным пунктам назначения. Мы увидели, как они работают вместе с программами форматирования, образуя целостную и модульную систему обработки логов.
На практике мы создали базовую структуру обработчиков, определив абстрактный класс, который будет служить основой для всех будущих реализаций. Мы также разработали три начальных обработчика: Console, Database и File, отвечающих за направление логов в консоль, базу данных и файлы соответственно. Хотя на данный момент все они используют функцию Print() для симуляции, эта прочная основа позволяет в дальнейшем расширять и специализировать каждый класс под конкретные задачи.
В тестах мы проверили интеграцию обработчиков с нашей библиотекой и показали, как их можно гибко добавлять и использовать. Этот процесс показал потенциал обработчиков как модульных компонентов, которые можно адаптировать под различные задачи в области ведения логов.
Весь код, использованный в этой статье, приведен ниже. Ниже приведена таблица с описанием каждого файла в библиотеке:
Имя файла | Описание |
---|---|
Experts/Logify/LogiftTest.mq5 | Файл, в котором мы тестируем функции библиотеки, содержащий практический пример |
Include/Logify/Formatter/LogifyFormatter.mqh | Класс, отвечающий за форматирование сообщений, заменяющий заполнители конкретными значениями. |
Include/Logify/Handlers/LogifyHandler.mqh | Базовый класс для управления обработчиками логов, включая настройку уровня и отправку логов |
Include/Logify/Handlers/LogifyHandlerConsole.mqh | Обработчик логов, который отправляет отформатированные логи непосредственно в консоль терминала в MetaTrader. |
Include/Logify/Handlers/LogifyHandlerDatabase.mqh | Обработчик логов, который отправляет отформатированные логи в базу данных (в настоящее время он содержит только распечатку, но в скором времени мы будем сохранять их в реальной базе данных sqlite) |
Include/Logify/Handlers/LogifyHandlerFile.mqh | Обработчик логов, который отправляет отформатированные логи в файл (в настоящее время он содержит только распечатку, но в скором времени мы будем сохранять их в реальный файл) |
Include/Logify/Logify.mqh | Основной класс для управления логами, интегрирующий уровни, модели и форматирование. |
Include/Logify/LogifyLevel.mqh | Файл, который определяет уровни логирования библиотеки Logify, позволяя осуществлять детальный контроль. |
Include/Logify/LogifyModel.mqh | Структура, которая моделирует логи, включая такие аспекты, как уровень, сообщение, метка времени и контекст. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16866
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





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