Вывод сообщений в журнал

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

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

Мы уже знакомы с двумя функциями для вывода в журнал — Print и PrintFormat — они активно использовались в примерах предыдущих разделов. Нам пришлось в упрощенном режиме "ввести их в обиход" раньше времени, поскольку обойтись без них практически невозможно.

Один вызов функции порождает, как правило, одну запись. Однако если в выводимой строке встречается символ перевода строки ('\n'), он поделит информацию на две части.

Напомним, что все вызовы Print и PrintFormat трансформируются в записи в журнале на вкладке Эксперты окна Инструменты. Хотя вкладка называется Эксперты, в ней собираются результаты всех инструкций "печати", вне зависимости от типа MQL-программы.

Журналы хранятся в файлах, организованных по принципу "одни сутки — один файл": они имеют имена вида YYYYMMDD.log (Y — год, M — месяц, D — день). Располагаются файлы в папке <каталог данных>/MQL5/Logs (не следует путать их с системными журналами терминала в папке <каталог данных>/Logs).

Учтите, что при массированном выводе в журнал (если вызовы функции Print генерируют большой объем информации за короткий промежуток времени) терминал отображает в окне лишь некоторые записи. Это делается для оптимизации быстродействия. Кроме того, пользователь в любом случае не способен увидеть все сообщения на лету. Для того чтобы увидеть полную версию журнала, необходимо выполнить команду Просмотр контекстного меню. В результате откроется окно с логом.
 
Также следует помнить, что информация из журнала кэшируется при записи на диск, то есть пишется в файлы большими блоками в отложенном режиме, из-за чего в каждый момент времени файл с журналом, как правило, не содержит самых последних записей (хотя они видны в окне). Чтобы инициировать сброс кэша на диск, можно выполнить команду Просмотр или Открыть в контекстном меню журнала.

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

 

void Print(argument, ...)

Функция печатает одно или больше значений в журнал экспертов, в одну строку (если в выводимых данных не встретится символ '\n').

Аргументы могут иметь любой встроенный тип и разделяются запятыми. Количество параметров не может превышать 64. Их переменное число обозначено в прототипе многоточием, но MQL5 не позволяет описывать свои собственные функции с подобной характеристикой: переменное количество параметров имеют лишь некоторые встроенные функции API (в частности, StringFormat, Print, PrintFormat и Comment).

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

Также функция не способна обрабатывать массивы. Их можно выводить поэлементно или воспользоваться функцией ArrayPrint.

Значения типа double выводятся функцией с точностью до 16 значащих цифр (совокупно в мантиссе и дробной части). Число может выводиться либо в традиционном, либо в научном формате (с показателем степени), в зависимости от того, какая запись более компактна. Значения типа float выводятся с точностью до 7 десятичных разрядов. Для вывода вещественных чисел с другой точностью или для явного указания формата необходимо использовать функцию PrintFormat.

Значения типа bool выводятся в виде строк "true" или "false".

Даты выводятся с указанием дня и времени с максимальной точностью (до секунды), в формате "YYYY.MM.DD hh:mm:ss". Для вывода даты в другом формате необходимо использовать функцию TimeToString (см. раздел Дата и время).

Значения перечислений выводятся как целые числа. Для отображения названий элементов используйте функцию EnumToString (см. раздел Перечисления).

Однобайтовые и двухбайтовые символы также выводятся как целые числа. Для отображения символов в виде знаков или букв используйте функции CharToString или ShortToString (см. раздел Работа с символами и кодовыми страницами).

Значения типа color выводятся либо в виде строки с тройкой чисел, обозначающих интенсивность каждой компоненты цвета ("R,G,B"), либо в виде названия цвета, если этот цвет присутствует в наборе цветов.

Более подробно о преобразованиях значений разных типов в строки описано в главе Преобразование данных встроенных типов (в частности, в разделах Числа в строки и обратно, Дата и время, Цвет).

При работе в тестере стратегий в режиме одиночного прохода (тестирования эксперта или индикатора), результаты функции Print выводятся в журнал агента тестирования.

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

Все аргументы, после преобразования в строковое представление, состыковываются в одну общую строку без каких-либо символов-разделителей. Если это требуется, необходимо явным образом прописать такие символы в списке аргументов. Например,

int x;
bool y;
datetime z;
...
Print(x", "y", "z);

Здесь в журнал выводятся 3 переменные, разделенные запятыми. Если бы не промежуточные литералы ", ", значения переменных склеились бы в записи журнала.

Множество примеров использования Print можно найти, начиная с самых первых разделов книги (например, Первая программа, Присваивание и инициализация, выражения и массивы, и в других).  

В качестве нового приема работы с Print реализуем простой класс, который позволит выводить последовательность произвольных значений, не указывая между каждыми соседними величинами символ-разделитель. Используем подход с перегрузкой оператора '<<', схожий с тем, что применяется в потоках ввода/вывода C++ (std::cout).

Определение класса поместим в отдельный заголовочный файл OutputStream.mqh. В упрощенном виде класс представлен ниже.

class OutputStream
{
protected:
   ushort delimiter;
   string line;
   
   // добавить очередной аргумент, через разделитель (если он есть)
   void appendWithDelimiter(const string v)
   {
      line += v;
      if(delimiter != 0)
      {
         line += ShortToString(delimiter);
      }
   }
   
public:
   OutputStream(ushort d = 0): delimiter(d) { }
   
   template<typename T>
   OutputStream *operator<<(const T v)
   {
      appendWithDelimiter((string)v);
      return &this;
   }
   
   OutputStream *operator<<(OutputStream &self)
   {
      if(&this == &self)
      {
         Print(line); // вывод собранной строки
         line = NULL;
      }
      return &this;
   }
};

Его суть заключается в том, чтобы накапливать в строковой переменной line строковые представления любых аргументов, переданные с помощью оператора '<<'. Если в конструкторе класса задан символ-разделитель, он будет автоматически вставляться между аргументами. Поскольку перегруженный оператор возвращает указатель на объект, мы сможем нанизывать передачу последовательности аргументов в цепочку:

OutputStream out(',');
out << x << y << z << out;

В качестве признака окончания сбора данных и для фактического вывода содержимого line в журнал используется перегрузка того же оператора для самого объекта.

Реальный класс несколько сложнее. Он, в частности, позволяет задать не только символ-разделитель, но и точность отображения вещественных чисел, а также флаги для выбора полей в значениях даты и времени. Кроме того класс поддерживает печать символов ushort в виде знаков (вместо целочисленных кодов), упрощенный вывод массивов (отдельной строкой), цвета в шестнадцатеричном формате единой величиной (а не тройкой чисел через запятую, так как запятая часто используется как символ-разделитель, и тогда компоненты цвета выглядят в журнале как 3 разных переменных).

Демонстрация использования класса приведена в скрипте OutputStream.mq5.

void OnStart()
{
   OutputStream os(5, ',');
   
   bool b = true;
   datetime dt = TimeCurrent();
   color clr = C'127,128,129';
   int array[] = {1000, -100};
   os << M_PI << "text" << clrBlue << b << array << dt << clr << '@' << os;
   
   /*
      пример вывода
      
      3.14159,text,clrBlue,true
      [100,0,-100]
      2021.09.07 17:38,clr7F8081,@
   */
}

 

void PrintFormat(const string format, ...) ≡ void printf(const string format, ...)

Функция выводит в журнал набор аргументов, руководствуясь указанной форматной строкой. Параметр format не только предоставляет шаблон выводимой строки с произвольным текстом, который отображается "как есть", но и может содержать управляющие последовательности, описывающие способ форматирования конкретных аргументов.

Общее количество параметров, включая форматную строку, не может превышать 64. Ограничения на типы параметров аналогичны функции Print.

Принципы работ PrintFormat и спецификации форматов идентичны тем, что были описаны для функции StringFormat (см. раздел Универсальный форматированный вывод данных в строку). Единственное отличие заключается в том, что StringFormat возвращает сформированную строку в вызывающий код, а PrintFormat отправляет в журнал. Можно сказать, что PrintFormat имеет следующий условный эквивалент:

Print(StringFormat(<список аргументов как есть, включая format>))

Помимо полного названия PrintFormat можно использовать более краткий алиас printf.

Как и функция Print, PrintFormat имеет особенности при работе в тестере в режиме оптимизации: её вывод в журнал подавляется для повышения быстродействия.

Скрипты с использованием PrintFormat уже встречались нам во многих разделах, например: Переход return, Цвет, Динамические массивы, Управление дескрипторами файлов, Получение списка глобальных переменных.