日志消息

日志是将有关程序运行的当前信息告知用户的最常用的方式。这些信息可能是常规完成状态、长计算过程期间的进度指示,或者用于查找和复制错误的调试数据。

遗憾的是,任何编程人员在写代码时都难免犯错。因此,开发人员通常都尽量留下所谓的“面包屑面痕迹”:记录程序执行的主要阶段(至少记录函数调用顺序)。

我们已经熟悉了一个日志函数,分别是 PrintPrintFormat。我们在前面章节的示例中使用过。我们不得不以简化模式提前“将它们投入使用”,因为少了它们几乎是无法进行的。

原则上,一次函数调用生成一条记录。然而,如果在输出字符串中碰到一个换行符字符 ('\n'),则其将会把信息分为两个部分。

请注意,所有 PrintPrintFormat 调用都会转换为Toolbox窗口的Experts选项卡上的日志条目。虽然该选项卡名为Experts,但是它收集的是所有打印指令的结果,无论何种 MQL 程序类型

日志存储在根据“一天一文件”原则组织的文件中:它们的名称为 YYYYMMDD.log(Y 为年,M 为 月,D 为天)。文件位于 <data directory>/MQL5/Logs(不要与终端系统日志文件夹 <data directory>/Logs 混淆了)。

请注意,在批量日志记录期间(如果 Print 函数调用在短时间内生成大量信息),终端在窗口中仅显示一部分条目。这是为了优化性能。此外,用户在任何情况下都无法实时查看所有消息。要看到完整版本的日志,你需要运行上下文菜单中的View命令。结果,将会打开一个包含日志的窗口。
 
切记,日志信息在写入到磁盘时会进行缓存,也就是说,它以懒模式大块写入到文件,这就是为什么原则上在任何给定时间,日志文件均不包含最近条目的原因(虽然它们在窗口中可见)。要发起将缓存刷写到磁盘的操作,可以在日志上下文菜单中运行命令ViewOpen

每个日志条目前面都是精确到最近毫秒数的时间,以及生成或导致该消息的程序的名称(及其图形)。

 

void Print(argument, ...)

该函数将一个或多个值以一行(如果输出数据不包含字符 '\n')打印 到“专家”日志。

自变量可以是任何 内置类型。它们以逗号分隔。参数的数量不能超过 64。原型中的省略号表示参数数量是可变的,但 MQL5 不允许你以类似特性描述你自己的函数:仅一些内置 API 函数可以具有可变数量的参数(具体来说,就是 StringFormatPrintPrintFormat,以及 Comment)。

对于结构体和类,你应实现一种内置打印方法,或者单独显示它们的字段。

此外,该函数不能够处理数组。你可以逐元素显示它们,或者使用函数 ArrayPrint

double 类型的值由该函数以 16 位有效数位的精度输出(尾数和小数部分的总和)。一个数字可以以传统或科学格式(采用指数)显示,取更紧凑的方式。float 类型的值以 7 个小数位的精度显示。要以不同的精度显示实数或者要明确指定格式,必须使用 PrintFormat 函数。

bool 类型的值输出为字符串 "true" 或 "false"。

显示的日期包含日和时间,并取最大精度(精确到秒)以 "YYYY.MM.DD hh:mm:ss" 格式显示。要以不同的格式显示日期,使用 TimeToString 函数(参见 日期和时间章节)。

枚举值显示为整数。要显示元素名称,使用 EnumToString 函数(参见 枚举章节)。

单字节和双字节字符也输出为整数。要将符号显示为字符或字母,使用函数 CharToStringShortToString,参见章节 处理符号和代码页)。

color 类型的值显示字符串,其中包含数字三元组,分别表示每种颜色分量 ("R, G, B") 强度,或者显示为颜色名称(如果该颜色出现在颜色集合中)。

有关将不同类型的值转换为字符串的更多信息,参见: 内置类型数据转换 (尤其是以下章节 数字转换为字符串以及相反转换日期和时间颜色)。

以单传递模式在策略测试程序中工作时(测试 EA 交易或指标),Print 函数的结果输出到测试代理日志中。

优化模式在策略测试程序中工作时,出于性能原因会抑制日志记录,因此 Print 函数没有直观效果。但是给定为自变量的所有表达式均被求值。

所有自变量在转换为字符串表示后被连接为一个没有定界符的公用字符串。如果需要,这些字符必须显式写在自变量列表中。例如,

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

其中,3 个变量被记录到日志,以逗号分隔。如果没有中间字面量 ", ",变量的值将会在日志条目中粘连在一起。

从本书最初章节开始,就有很多应用 Print 的情况(例如, 第一个程序赋值和初始化,表达式和数组以及其它章节)。

作为一种新的使用 Print 的方式,我们将实现一个简单类,该简单类将允许你显示一系列任意值而无需在每个相邻值之间指定一个分隔符。我们使用 '<<' 运算符重载法,类似于在 C++ I/O 流中使用的方法 (std::cout)。

类定义将放在单独的头文件 OutputStream.mqh 中。下面显示了简化形式的类。

class OutputStream
{
protected:
   ushort delimiter;
   string line;
   
   // add the next argument, separated by a separator (if any)
   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);// output of the composed string
         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'127128129';
   int array[] = {1000, -100};
   os << M_PI << "text" << clrBlue << b << array << dt << clr << '@' << os;
   
   /*
      output example
      
      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 将形成的字符串返回到调用代码,而 print format 将其发送到日志。我们可以说,PrintFormat 具有以下条件性等效内容:

Print(StringFormat(<list of arguments as isincluding format>))

除了全称 PrintFormat,你还可以使用短别名 printf

Print 函数一样,在测试程序中以优化模式工作时,PrintFormat 有一些特殊特性:其日志输出被禁止以提高性能。

我们已在很多章节遇到了使用 PrintFormat 的脚本,例如, 返回转换颜色动态数组文件描述符管理获取全局变量列表