Поиск ошибок и ведение лога

21 сентября 2010, 15:03
Дмитрий Александрович
6
5 341

Введение

Здравствуйте, уважаемые читатели!
В данной статье мы рассмотрим способы поиска ошибок в экспертах/скриптах/индикаторах и методы ведения логов, а также я предложу вам маленькую программу для просмотра логов - LogMon.

Программирование неотделимо от поиска ошибок, после написания нового блока кода его необходимо проверить на работоспособность, а так же на отсутствие логических ошибок. Найти ошибку в программе можно тремя разными способами:

  1. Оценка конечного результата.
  2. Пошаговая отладка.
  3. Запись логических шагов в лог.

Рассмотрим каждый способ.

1. Оценка конечного результата

При данном способе мы анализируем итог работы программы или части ее кода. Для примера берем простой код, который содержит очевидную ошибку, но это только для наглядности:

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<9;i++)
     {
      intArray[i]=i;
     }
   Alert(intArray[9]);

  }

Компилируем и запускаем, на экране будет выведено "0". Анализируем результат - мы ожидали число "9", отсюда делаем вывод, что наша программа работает не так, как нужно. Данный способ поиска ошибок является общим и не позволяет найти места ошибки. Для поиска ошибки воспользуемся отладкой и рассмотрим второй способ поиска ошибок.

2. Пошаговая отладка

Данный способ позволяет найти место нарушения логики работы программы. В MetaEditor ставим точку остановки внутри цикла for, начинаем отладку и добавляем просмотр переменной i:

отладка

Дальше нажимаем на "продолжение выполнения" до тех пор, пока не рассмотрим весь процесс работы программы. Мы видим то, что при достижении переменной "i" значения "8" происходит выход из цикла, отсюда делаем вывод, что ошибка в строчке:

for(int i=0;i<9;i++)

а именно  при сравнении значения i и числа 9. Исправляем часть строки "i<9" на "i<10" или "i<=9", проверяем результат. Получается число 9 - именно то, что мы ожидали. Используя отладку, мы узнали, как ведет программа во время выполнения, и смогли исправить ошибку. Но теперь о минусах данного способа:

  1. Интуитивно не понятно где ошибка.
  2. Необходимо добавлять переменные в список просмотра и после каждого шага просматривать их.
  3. Данный способ не сможет выявить ошибок во время выполнения готовой программы, например эксперта торгующего на реальном или демосчете.

Наконец, рассмотрим третий способ выявления ошибок.

3. Запись логических шагов в лог

Применяя данный способ, мы записываем значительные шаги нашей программы, например: инициализацию, проведение сделки, расчет индикатора и т.п. Модернизируем наш скрипт одной строчкой кода, а именно, мы будем выводить значение переменной i на каждой итерации цикла:

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<9;i++)
     {
      intArray[i]=i;
      Alert(i);
     }
   Alert(intArray[9]);

  }

Запускаем, и видим вывод лога - числа "0 1 2 3 4 5 6 7 8 0". Делаем умозаключения, почему так может быть, и исправляем скрипт, так же как и в прошлый раз.

Теперь о плюсах и минусах данного способа поиска ошибок:

  1. + Нет необходимости пошагово выполнять программу, что существенно экономит время.
  2. + Часто с первого взгляда видно, где кроется ошибка.
  3. + Можно вести лог во время работы программы в рабочей обстановке.
  4. + Можно сохранить лог для последующего анализа и сравнения (Например, при записи в файл, об этом ниже).
  5. - Немного увеличивается количество кода из-за добавления операторов, которые записывают данные в лог.
  6. - Увеличивается время выполнения программы (важно, в основном, для оптимизации).

Подведем итог:

Первый способ поиска ошибок не позволяет найти место ошибки, его мы применяем в первую очередь за его скорость. Второй способ - пошаговая отладка, позволяет найти точное место ошибки, но требуется немало времени на отладку, а также, если мы "проскочим" нужный участок кода, то придется все начинать сначала.

И наконец, третий способ - Запись логических шагов в лог, позволяет нам произвести быстрый анализ работы программы и сохранить результат. Записывая в лог события ваших экспертов/индикаторов/скриптов, вы сможете отыскать ошибку, если произойдет сбой, и вам не придется искать нужные условия, при которых возникает ошибка, часами отлаживая программы. Далее мы детально рассмотрим способы ведения лога, сравним их. А также я предложу вам способ, который является наиболее удобным и быстрым.

Когда необходимо вести лог

Вот некоторые причины для ведения лога:
  1. Ошибочное поведение программы;
  2. Слишком долгое выполнение программы (оптимизация);
  3. Контроль выполнения (вывод уведомлений об открытии/закрытии позиций, выполненных действиях и т.п.);
  4. Изучение MQL5, например, вывод массивов;
  5. Проверка эксперта перед чемпионатом и т.п.

Способы ведения лога

Способов записи сообщений в лог очень много, но некоторые используются повсеместно, а некоторые необходимы для особых случаев. Например, отправка лога на email или icq не всегда нужна.

Приведу список самых распространенных способов, используемых при программировании на MQL5:

  1. Использование функции Comment()
  2. Использование функции Alert()
  3. Использование функции Print()
  4. Запись лога в файл функцией FileWrite()

Приведу примеры кода с каждым способом и опишу особенности каждого способа. Тестовый код не будет содержать какой-либо цели, чтобы не уходить от сути.

Использование функции Comment()

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Comment("Переменная i: ",i);
      Sleep(5000);
     }
   Alert(intArray[9]);
  }

Итак, в левом верхнем мы видим текущее состояние переменной "i":

comment()

Таким образом мы можем следить за текущим состоянием запущенной программы. Теперь о плюсах и минусах:

  1. + Сразу видно значение.
  2. - Ограничение выводимой информации.
  3. - Нельзя выделить определенное сообщение.
  4. - Не видно работы на всем протяжении, только текущее состояние.
  5. - Сравнительно медленный способ.
  6. - Плохо подходит для постоянного мониторинга работы, т.к. необходимо следить за показаниями.

Функцией Comment() удобно выводить текущее состояние эксперта, например, "Проведены 2 сделки" или "buy GBRUSD lot: 0.7".

Использование функции Alert()

Данная функция выводит сообщения в отдельном окне, сопровождая звуковым уведомлением, пример кода:

void OnStart()
  {
//---
   Alert("Старт скрипта");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Alert("Переменная i: ",i);
      Sleep(1000);
     }
   Alert(intArray[9]);
   Alert("Остановка скрипта");
  }


Итог выполнения кода:

alert

И вот мы уже на седьмом небе от счастья, все сразу видно, да еще и со звуком. Но теперь о плюсах и минусах:

  1. + Записываются все сообщения последовательно.
  2. + Уведомление звуком.
  3. + Записывается все в файл "Путь_к_терминалу\MQL5\Logs\дата.txt".
  4. - Все сообщения от всех скриптов/экспертов/индикаторов записываются в один лог.
  5. - Не работает в тестере.
  6. - При частом вызове может вызвать зависание терминале на продолжительное время (например, при вызове на каждом тике или выводе массива в цикле).
  7. - Нельзя группировать сообщения.
  8. - Неудобно копаться в файле лога.
  9. - Нельзя сохранить сообщения в отличное место от стандартной папки.

Шестой пункт очень критичен при реальной торговле, особенно при пипсовке или при модификации Stop Loss. Минусов довольно много, можно найти и другие, но, думаю, и этого хватит.

Использование функции Print()

Данная функция записывает сообщения лога в специальное окно с названием "Эксперты", вот код:

void OnStart()
  {
//---
   Print("Старт скрипта");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Print("Переменная i: ",i);
     }
   Print(intArray[9]);
   Print("Остановка скрипта");
  }


print



Как видим способ вызова такой же, как у функции Alert(), но теперь все сообщения у нас пишутся без уведомлений в окошко "эксперты" и в файл "Путь_к_терминалу\MQL5\Logs\дата.txt", рассмотрим плюсы и минусы данного способа:

  1. + Записываются все сообщения последовательно.
  2. + Записывается все в файл "Путь_к_терминалу\MQL5\Logs\дата.txt".
  3. + Подходит для постоянного логирования работы программы.
  4. - Все сообщения от всех скриптов/экспертов/индикаторов записываются в один лог.
  5. - Нельзя группировать сообщения.
  6. - Неудобно копаться в файле лога.
  7. - Нельзя сохранить сообщения в отличное место от стандартной папки.

Данным способом, скорее всего, пользуется большинство программистов на MQL5, он довольно быстрый и хорошо подходит при большом количестве записей в лог.

Запись лога в файл

Рассмотрим последний способ ведения лога - запись сообщений в файлы. Данный способ значительно сложнее всех предыдущих, но при соответствующей подготовке обеспечивает хорошую скорость записи и удобный просмотр лога, а также возможность уведомления. Приведу простейший код для записи лога в файл:

void OnStart()
  {
//--- Открываем файл лога
   int fileHandle=FileOpen("log.txt",FILE_WRITE|FILE_TXT|FILE_SHARE_READ|FILE_UNICODE); 
   FileWrite(fileHandle,"Старт скрипта");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      FileWrite(fileHandle,"Переменная i: ",i);
      // Sleep(1000);
     }
   FileWrite(fileHandle,intArray[9]);
   FileWrite(fileHandle,"Остановка скрипта");
   FileClose(fileHandle); // закрываем файл лога
  }

Запускаем, затем заходим в папку  "Путь_к_терминалу\MQL5\Files" и открываем файл "log.txt" в блокноте. Вот содержимое файла:

log to file

Как видим, вывод последовательный, нет лишних сообщений - только то, что мы записали в файл. Рассмотрим плюсы и минусы:

  1. + Быстро работает.
  2. + Записывается только то, что мы хотим.
  3. + Можно записывать сообщения от разных программ в разные файлы, что исключит переплетение логов.
  4. - Нет уведомления о новых сообщениях в логе.
  5. - Нельзя выделить определенное сообщение/категорию сообщений.
  6. - Долго открывать лог, необходимо зайти в папку и открыть файл.

Подведем итог:

Все описанные выше способы имеют свои недостатки, но некоторые недостатки можно исправить самим. Первые три способа ведения лога не отличаются гибкостью, мы почти не можем повлиять на их поведение. А вот последний способ, Запись лога в файл - самый гибкий, мы сами определяем, как и когда будут записываться сообщения. Если вам нужно вывести на экран одно число, то, конечно, удобнее пользоваться первыми тремя способами, но если у вас серьезная программа с большим количеством кода, то обойтись без записи лога в файл трудно.


Новый подход к ведению лога


Теперь я расскажу и покажу, как можно усовершенствовать вывод лога в файл и предоставлю вам удобный инструмент для просмотра логов. Это написанная мною программа на С++ для Windows, которую я назвал LogMon.

Начнем с написания класса, который будет отвечать за формирования лога, а именно:

  1. Хранить в себе местоположения файла, в который записывается лог и другие настройки ведения лога.
  2. Создавать файлы лога в зависимости от заданного имени и даты/времени.
  3. Преобразовывать переданные параметры в строку лога.
  4. Добавлять время к сообщению лога.
  5. Добавлять цвет сообщения.
  6. Добавлять категорию сообщения.
  7. Кэшировать сообщения и записывать их раз в n-секунд или каждые n-сообщений.

Т.к. MQL5 объектно-ориентированный язык и не уступает в скорости C++, то мы будем писать класс именно на MQL5. Приступим к написанию класса.


Реализация класса для записи лога в файл

Наш класс мы расположим в отдельном включаемом файле с расширением mqh. Вот примерная структура класса.

CLogger

Теперь сам код класса с подробными комментариями:

//+------------------------------------------------------------------+
//|                                                      Clogger.mqh |
//|                                                             ProF |
//|                                                          http:// |
//+------------------------------------------------------------------+
#property copyright "ProF"
#property link      "http://"

// Максимальный размер кэша (штук)
#define MAX_CACHE_SIZE   10000
// максимальный размер файла в мегабайтах
#define MAX_FILE_SIZEMB 10
//+------------------------------------------------------------------+
//|   Логгер                                                         |
//+------------------------------------------------------------------+
class CLogger
  {
private:
   string            project,file;             // имя проекта и файла лога
   string            logCache[MAX_CACHE_SIZE]; // максимальный размер кэша
   int               sizeCache;                // счетчик кэша
   int               cacheTimeLimit;           // время кэширования
   datetime          cacheTime;                // время последнего сброса кэша в файл
   int               handleFile;               // хэндл файла лога
   string            defCategory;              // категория по умолчанию
   void              writeLog(string log_msg); // запись сообщения в лог или файл и сброс кэша
public:
   void              CLogger(void){cacheTimeLimit=0; cacheTime=0; sizeCache=0;};    // конструктор
   void             ~CLogger(void){};                                               // деструктор
   void              SetSetting(string project,string file_name,
                                string default_category="",int cache_time_limit=0); // задание настроек
   void              init();                   // инициализация, открытие файла для записи
   void              deinit();                 // деинициализация, закрытие файла
   void              write(string msg,string category="");                                         // формирование сообщения
   void              write(string msg,string category,color colorOfMsg,string file="",int line=0); // формирование сообщения
   void              write(string msg,string category,uchar red,uchar green,uchar blue,
                           string file="",int line=0);                                             // формирование сообщения
   void              flush(void);              // сброс кэша в файл

  };
//+------------------------------------------------------------------+
//|  Задание настроек                                                |
//+------------------------------------------------------------------+
void CLogger::SetSetting(string project_name,string file_name,
                        string default_category="",int cache_time_limit=0)
  {
   project=project_name;             // имя проекта
   file=file_name;                   // имя файла
   cacheTimeLimit=cache_time_limit;  // время кэширования
   if(default_category=="")          // установка категории по умолчанию
     {  defCategory="notice";   }
     else
     {defCategory = default_category;}
  }
//+------------------------------------------------------------------+
//|  Инициализация                                                   |
//+------------------------------------------------------------------+
void CLogger::init(void)
  {
   string path;
   MqlDateTime date;
   int i=0;
   TimeToStruct(TimeCurrent(),date);                            // получение текущего времени
   StringConcatenate(path,"log\\log_",project,"\\log_",file,"_",
                     date.year,date.mon,date.day);              // формирование пути и имени файла
   handleFile=FileOpen(path+".txt",FILE_WRITE|FILE_READ|
                       FILE_UNICODE|FILE_TXT|FILE_SHARE_READ);  // открытие или создание файла
   while(FileSize(handleFile)>(MAX_FILE_SIZEMB*1000000))        // проверка размера файла
     {
      //открываем или создаем новый файл лога
      i++;
      FileClose(handleFile);
      handleFile=FileOpen(path+"_"+(string)i+".txt",
                          FILE_WRITE|FILE_READ|FILE_UNICODE|FILE_TXT|FILE_SHARE_READ);
     }
   FileSeek(handleFile,0,SEEK_END);                             // устанавливаем указатель в конец файла
  }
//+------------------------------------------------------------------+
//|   Деинициализация                                                |
//+------------------------------------------------------------------+
void CLogger::deinit(void)
  {
   FileClose(handleFile); // закрываем файл
  }
//+------------------------------------------------------------------+
//|   Запись сообщения в файл или кэш                                |
//+------------------------------------------------------------------+
void CLogger::writeLog(string log_msg)
  {
   if(cacheTimeLimit!=0)  // проверяем разрешен ли кэш
     {
      if((sizeCache<MAX_CACHE_SIZE-1 && TimeCurrent()-cacheTime<cacheTimeLimit)
         || sizeCache==0) // проверяем не устарел ли кэш и не привышен ли лимит кэша
        {
         // Записываем сообщение в кэш
         logCache[sizeCache++]=log_msg;
        }
      else
        {
         // Записываем сообщение в кэш и сбрасываем кэш в файл
         logCache[sizeCache++]=log_msg;
         flush();
        }

     }
   else
     {
      //кэш запрещен, сразу записываем в файл
      FileWrite(handleFile,log_msg);
     }
   if(FileTell(handleFile)>(MAX_FILE_SIZEMB*1000000)) // проверяем текущий размер файла
     {
      // Размер файла больше установленного лимита, закрываем файл и открываем новый
      deinit();
      init();
     }
  }
//+------------------------------------------------------------------+
//|   Формирование сообщения и запись в лог                          |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category="")
  {
   string msg_log;
   if(category=="")                // проверяем наличие переданной категории
     {   category=defCategory;   } // устанавливаем категорию по умолчанию

// формируем строку и вызываем метод для записи сообщения
   StringConcatenate(msg_log,category,":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Формирование сообщения и запись в лог                         |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category,color colorOfMsg,string file="",int line=0)
  {
   string msg_log;
   int red,green,blue;
   red=(colorOfMsg  &Red);           // Выделяем красный цвет из константы
   green=(colorOfMsg  &0x00FF00)>>8; // Выделяем зеленый цвет из константы
   blue=(colorOfMsg  &Blue)>>16;     // Выделяем синий цвет из константы
                                     // Проверяем переданы ли файл и строка, формируем строку и вызываем метод для записи сообщения
   if(file!="" && line!=0)
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",
                        "файл: ",file,"   строка: ",line,"   ",msg);
     }
   else
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
     }
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Формирование сообщения и запись в лог                         |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category,uchar red,uchar green,uchar blue,string file="",int line=0)
  {
   string msg_log;

// Проверяем переданы ли файл и строка, формируем строку и вызываем метод для записи сообщения
   if(file!="" && line!=0)
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",
                        "файл: ",file,"   строка: ",line,"   ",msg);
     }
   else
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
     }
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Сброс кеша в файл                                             |
//+------------------------------------------------------------------+
void CLogger::flush(void)
  {
   for(int i=0;i<sizeCache;i++) // В цикле записываем все сообщения в файл
     {
      FileWrite(handleFile,logCache[i]);
     }
   sizeCache=0; // сбрасываем счетчик кэша
   cacheTime=TimeCurrent(); // устанавливаем время сброса кэша
  }
//+------------------------------------------------------------------+

Создаем включаемый файл (.mqh) в MetaEditor и копируем код класса, сохраняем под именем "CLogger.mqh". Теперь поподробнее о том, что делает каждый метод и как применять класс.

Применение класса CLogger

Чтобы начать записывать сообщения в лог с помощью данного класса нам необходимо включить файл класса в советник/индикатор/скрипт:

#include <CLogger.mqh>

Далее необходимо создать объект данного класса:

CLogger logger;

Все действия мы будем производить с объектом "logger".  Теперь нам необходимо задать настройки, вызвав метод "SetSetting()". В метод мы должны передать название проекта, имя файла. А также есть два необязательных параметра - название категории по умолчанию и время хранения кэша в секундах перед записью в файл. Если указать ноль, все сообщения будут записываться сразу.

SetSetting(string project,             // Название проекта
           string file_name,           // Название файла лога
           string default_category="", // Категория используемая по умолчанию
           int cache_time_limit=0      // Количество секунд хранения кэша      
           );

Пример вызова:

logger.SetSetting("Мой проект","лог","Замечание",60);

В результате сообщения будут записываться в папку "папка_терминала\MQL5\Files\log\log_Мой проект\log_лог_дата.txt", категория по умолчанию - "Замечание" и время кэша 60 секунд. Затем нужно вызвать метод init() для открытия/создания файла лога. Пример вызова предельно прост, т.к. параметров передавать не нужно:

logger.init();

Данный метод формирует путь и имя файла лога, открывает его, проверяет, не превышен ли максимальный размер. Если размер больше установленной константы, то открывается другой файл, к имени которого прибавляется  единица. Затем опять проверяется размер и так до тех пор, пока не будет открыт файл с нужным размером.

Потом передвигается указатель на позицию в файле на конец файла. Теперь объект готов записывать лог. Мы перегрузили метод write, благодаря этому мы можем задавать разную структуру сообщения, примеры вызова метода write и результат в файле:

// Записываем сообщение с категорией по умолчанию
logger.write("Тестовое сообщение");
// Записываем сообщение с категорией "ошибки"
logger.write("Тестовое сообщение", "ошибки");
// Записываем сообщение с категорией "ошибки", которое в программе LogMon будет с красным фоном
logger.write("Тестовое сообщение", "ошибки",Red);
// Записываем сообщение с категорией "ошибки", которое в программе LogMon будет с красным фоном.
// А так же в сообщение будет вставлено название текущего файла и текущая строка
logger.write("Тестовое сообщение", "ошибки",Red,__FILE__,__LINE__);
// Записываем сообщение с категорией "ошибки", которое в программе LogMon будет с фоном GreenYellow
// Но теперь мы указываем каждый цвет отдельно в порядке: красный, зеленый, синий. 0-темный, 255 - светлый
logger.write("Тестовое сообщение", "ошибки",173,255,47);
// Записываем сообщение с категорией "ошибки", которое в программе LogMon будет с фоном GreenYellow
// Но теперь мы указываем каждый цвет отдельно в порядке: красный, зеленый, синий. 0-темный, 255 - светлый
// А так же в сообщение будет вставлено название текущего файла и текущая строка
logger.write("Тестовое сообщение", "ошибки",173,255,47,__FILE__,__LINE__);

В файл лога запишутся следующие строки:

notice:|:23:13:12    Тестовое сообщение
ошибки:|:23:13:12    Тестовое сообщение
ошибки:|:255,0,0:|:23:13:12    Тестовое сообщение
ошибки:|:255,0,0:|:23:13:12    файл: testLogger.mq5   строка: 27   Тестовое сообщение
ошибки:|:173,255,47:|:23:13:12    Тестовое сообщение
ошибки:|:173,255,47:|:23:13:12    файл: testLogger.mq5   строка: 29   Тестовое сообщение

Как видите, все очень просто. В любом месте вызвать метод write() с нужными параметрами и сообщение будет записано в файл. В конце вашей программы необходимо вставить вызов двух методов - flush() и deinit().

logger.flush();  // принудительно сбрасываем кэш на диск
logger.deinit(); // закрываем файл лога

Вот пример простейшего скрипта который записывает в лог просто числа в цикле:

//+------------------------------------------------------------------+
//|                                                   testLogger.mq5 |
//|                                                             ProF |
//|                                                          http:// |
//+------------------------------------------------------------------+
#property copyright "ProF"
#property link      "http://"
#property version   "1.00"
#include <Сlogger.mqh>
CLogger logger;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

void OnStart()
  {
//---
   logger.SetSetting("proj","lfile");      // Задаем настройки
   logger.init();                          // инициализация
   logger.write("Старт скрипта","system");  
   for(int i=0;i<100000;i++)               // записываем сто тысяч сообщений в лог
     {
      logger.write("log: "+(string)i,"notice",100,222,100,__FILE__,__LINE__);
     }
   logger.write("Остановка скрипта","system"); 
   logger.flush();                         // Сбрасываем буфер
   logger.deinit();                        // Деинициализация
  }
//+------------------------------------------------------------------+

Скрипт выполнился за три секунды, создал 2 файла:

Содержимое файла:

file_log_msg

И так все сто тысяч сообщений. Как видите, все работает и довольно быстро. Вы можете доработать данный класс, добавить новые функции или оптимизировать.

Уровень вывода сообщений


Когда мы пишем программу, приходится выводить несколько типов сообщений:

  1. Критические ошибки (Программа ведет себя не правильно);
  2. Уведомления о не критических ошибках, торговых операциях и т.п. (В программе возникли временные ошибки, или программа совершила важное действие, о котором нужно уведомить);
  3. Отладочная информация (Содержимое массивов, переменных и другая информация не нужная во время реальной работы).

А также желательно иметь возможность настраивать, какие сообщения нужно выводить без изменения исходного кода. Реализуем данную цель в виде простой функции, не будем использовать классы и методы из-за длины записи вызова.

Объявим переменную-параметр, которая будет содержать уровень вывода сообщений. Чем больше число в переменной, тем больше категорий сообщений будет выводиться. Если мы хотим совсем запретить вывод сообщений, необходимо записать число "-1".

input int dLvl=2;

Ниже приведен код функции, ее необходимо объявлять после создания объекта класса CLogger.

void debug(string debugMsg,             // текст сообщения
          int lvl        )              // уровень сообщения
{
   if (lvl<=dLvl)                       // сравниваем уровень сообщения с уровнем вывода сообщений
   {
       if (lvl==0)                      // если сообщение критическое (уровень равен нулю)
       {logger.write(debugMsg,"",Red);} // помечаем его красным цветом
       else
       {logger.write(debugMsg);}        // иначе, записываем его со стандартным цветом
   }
}

Теперь пример использования, самым важным сообщениям нужно задавать уровень "0", а самым бесполезным любое число по возрастанию от нуля:

debug("Ошибка в эксперте!",0);      // Критическая ошибка
debug("Сработал стоп-лосс",1);      // Уведомление
int i = 99;
debug("Переменная i:"+(string)i,2); // Отладочная информация, содержимое переменной

Удобный просмотр лога программой LogMon

Вот у нас есть файлы с тысячами строчек лога, но искать информацию в них сложно. Они не разделены по категориям и не отличаются видом друг от друга. Я постарался решить данную проблему, а именно написал программу для просмотра логов генерируемых классом CLogger. Сейчас я проведу краткое знакомство с программой LogMon, написанной на C++ с применением winapi, благодаря этому она получилась быстрой и малого размера. Программа абсолютно бесплатна.

Для работы программы ее нужно:

  1. Скопировать в папку "папка_терминала\MQL5\Files\" и запустить - в случае штатной работы.
  2. Скопировать в папку "папка агентов\агент\MQL5\Files\" и запустить - в случае прогона в тестере или оптимизации.

  Главное окно программы выглядит так:

В главном окне присутствует панель кнопок и окошко с древовидным списком, для раскрытия элементов нужно сделать двойной клик левой кнопкой мыши. Папки в списке - это проекты, которые находятся в папке "папка_терминала\MQL\Files\log\". Название проекта вы задаете в классе CLogger методом SetSetting(). Файлы в папках списка - это файлы логов, сообщения в файлах разделяются на категории, которые вы задавали в методе write(). Цифры в скобках - количество сообщений в данной категории.

Теперь рассмотрим кнопки слева на право.

Кнопка удаления проекта или файла лога, а так же для сброса древовидного списка

При нажатии появляется следующее окно:

Если нажать на кнопку "Удалить и сбросить" будут остановлены все потоки сканирования файлов/папок, сброшен древовидный список и отображен стандартный запрос на удаления выделенного файла или проекта (выделение - один клик на элементе, ставить галочку не нужно!). Кнопка "Сброс" - будут остановлены все потоки сканирования файлов/папок, сброшен древовидный список.

Кнопка вызова окна "О программе"

Вызывает краткую информацию о программе и авторе.

Кнопка закрепления окна поверх всех окон

Помещает окно поверх всех окон.

Кнопка активации мониторинга новых сообщений в логах

Данная кнопка прячет программу в трей  и активирует мониторинг за новыми сообщениями в логах. Для того чтобы отметить проект/файл/категорию, который будет сканироваться, необходимо расставить галочки рядом с необходимыми элементами.

Если галочка поставлена напротив категории сообщений, то сработает оповещение при новом сообщении данного проекта (файла, категории). Если галочка стоит напротив файла, оповещение сработает при новом сообщении в этом файле любой категории. И наконец, если установлена галочка около проекта, то оповещение будет срабатывать при появлении новых файлов лога и сообщений в них.

Мониторинг

Если активирован мониторинг и окно свернуто в трей, то при появлении новой записи в отмеченных элементах появится главное окно приложения и будет проигрываться звук уведомления. Чтобы отключить уведомления, кликните на нем в любом месте списка левой кнопкой мыши. Прекратить мониторинг можно нажатием на иконку программы в трее  . Чтобы изменить звук уведомления на свой необходимо рядом с исполняемым файлом программы положить wav-файл с именем "alert.wav".

Просмотр категории лога

Для просмотра определенной категории нужно сделать двойной клик на категории. Затем появится окно с сообщениями:

view_category

В этом окне есть поле ввода для поиска по сообщениям, кнопка закрепления окна поверх всех окон и переключатель автоскроллинга. Цвет каждого сообщения в отдельности определяется вами с помощью метода write() класса CLogger. Какой цвет вы укажете, таким фоном будет выделено сообщение.

При двойном клике на сообщение появляется окно просмотра отдельного сообщения, это может понадобиться, если сообщение слишком длинное и полностью не входит в окно:

view_msg

Теперь у вас есть удобный инструмент для просмотра и мониторинга файлов логов. Надеюсь, данная программа будет вашим помощником при разработке и использованию программ написанных на языке MQL5.

Заключение

Запись событий вашей программы в лог очень полезна, помогает выявить скрытые ошибки и возможности улучшить программу. В данной статье я описал способы и программы для максимального упрощения записи лога в файл, мониторинга логов и удобного просмотра.

Буду рад вашим замечаниям и предложениям!

Прикрепленные файлы |
logmon.rar (78.26 KB)
logmon_source.rar (96.87 KB)
clogger.mqh (8.42 KB)
testlogger.mq5 (1.29 KB)
Rashid Umarov
Rashid Umarov | 21 сен 2010 в 22:48
papaklass:

Мне тоже понравилась статья, спасибо. А то МТ печает с пропусками и это очень не удобно.

Пропуски только в закладке "Эксперты". Все сообщения находятся в файле лога.
Roman Zamozhnyy
Roman Zamozhnyy | 21 сен 2010 в 22:58

А я все больше такими или подобными штучками баловался:

void PrintDebugInfo(string InputString)
{
  CopyTime(Symbol(),Period(),0,1,Time);
  ArraySetAsSeries(Time,true);
  FileWrite(FileHandle,Time[0],"   ",InputString);
}

Спасибо за код, будем юзать... 

Дмитрий Александрович
Дмитрий Александрович | 22 сен 2010 в 13:13
Lizar:

Спасибо за статью. Давно сам хотел создать что-то подобное, да все руки не доходили. А тут... Уже пользуюсь.

Пользуйтесь на здоровье! :)
По мере использования, буду дорабатывать класс и прогу, т.к. писал сначала для себя, а потом уже про статью мысль пришла))
Yedelkin
Yedelkin | 22 сен 2010 в 19:56

Очень полезный материал!

Serhiy Dotsenko
Serhiy Dotsenko | 4 янв 2015 в 15:14

Дмитрий Александрович, спасибо за Вашу работу, давно искал что-то подобное и вот наконец нашёл ))

предложение по совершенствованию logmon.exe, сделать настройку что бы можно было прописать пути где искать файлы с логами, т.к. иметь 2 копии в разных папках (для тестера и штатной работы) как-то по аматорски ))

хотя мож как руки дойдут и сам допилю )) 

если ещё есть что-нибудь полезное - выкладывайте, Ваш стиль программирования и подачи материала - ооч. мозгофрэндный ))

Оценка торговых систем - эффективности входа, выхода и сделок Оценка торговых систем - эффективности входа, выхода и сделок

Существует масса критериев, которые позволяют оценить эффективность или прибыльность торговой стратегии. Но трейдеры всегда готовы подвергнуть любую систему новому краштесту. В статье рассказывается, как можно применить статистику для платформы MetaTrader 5 на основе измерения эффективности. Представлен класс перевода учёта статистики сделок в вид, не противоречащий описанному в книге "Статистика для трейдера" Булашева С.В. Приведён пример пользовательской функции оптимизации.

Simulink: в помощь разработчику эксперта Simulink: в помощь разработчику эксперта

Я не являюсь профессиональным программистом. И поэтому принцип «от простого к сложному» имеет для меня первостепенное значение, когда я встречаюсь с таким понятием как МТС, а точнее создание МТС. Что есть для меня простое? Прежде всего это визуализация самого процесса создания системы и логики её функционирования. А также минимум рукописного кода. В данной статье я попробую создать и протестировать МТС на основе матлабовского пакета, а затем напишу эксперт для MetaTrader 5. Причём для тестирования будут использованы исторические данные из МetaTrader 5.

Растущий нейронный газ - реализация на языке программирования MQL5 Растущий нейронный газ - реализация на языке программирования MQL5

В статье приводится пример написания на языке MQL5 программы, реализующий адаптивный алгоритм кластеризации, называемый "Растущий нейронный газ" (Growing neural gas, GNG). Статья рассчитана на пользователей, изучивших документацию к языку, а также уже имеющих определенные навыки программирования и базовые знания в области нейроинформатики.

Технический анализ: Что мы анализируем? Технический анализ: Что мы анализируем?

В статье предпринята попытка в общем виде проанализировать некоторые особенности представления котировок доступных для анализа в терминале MetaTrader. Техника программирования в статье не затрагивается, статья носит общий характер.