下载MetaTrader 5

查找错误和记录

9 十二月 2013, 13:04
Дмитрий Александрович
0
852

简介

亲爱的读者,您好!

本文中,我们会研究在“EA 交易”/脚本/指标中查找错误的方式以及记录方法。我还会向您推荐一款查看日志的小程序 - 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 变量多加注意:

调试

接下来点击 "Resume debugging" (继续调试),直到我们认定程序的整个过程均已正常。我们看到, "i" 变量值为 "8",我们就会退出循环,所以我们推断错误就在此行:

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

也就是说,在对比 i 值与数字 9 的时候。将该行 "i<9" " 修复为 "i<10" 或者 "i<=9",再检查结果。我们得到了数字 9 - 正是预期的结果。利用调试,我们知道了程序运行时是如何操作的,而且能够修复出现的问题。此方法的弊端:

  1. 无法凭直觉弄清楚错误发生所在位置。
  2. 您需要将变量添加到 Watch (观察)列表,而且每步之后都要查看。
  3. 此法不能探测完整程序执行期间的错误,比如真实或演示账户上的 EA 交易。

最后,我们来看看查找错误的第三种方法。

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. - 程序运行时间延长(主要对优化而言属重要)。

小结:

第一种查找错误的方法不能追踪错误所在实际位置。我们利用的主要是它的速度。第二种方法 - 逐步调试,允许您找到错误的精确位置,但极为耗时。而且,如果您错过了目标代码块,就不得不重新开始。

最后,第三种方法 - 将逻辑步骤记录到日志中,允许您快速分析程序的工作,并保存结果。将您的“EA 交易”/指标/脚本的事件写入到日志的同时,您还可以轻松地找到错误,而且无需寻找错误发生的相应条件,无需花费大量时间来调试您的程序。接下来,我们会详细讲解这些记录信息的方法,并加以对比。而且,我还会为您提供最便利、最快捷的方式。

什么情况下需要记录日志:

下面列举出了一些记录原因:
  1. 程序的错误行为。
  2. 程序运行时间过长(优化)。
  3. 运行时间监控(显示建仓/平仓通知、已执行操作等)。  
  4. 学习 MQL5,比如说 - 打印数组。  
  5. 在锦标赛之前检查“EA 交易”等。

记录方法

将信息写入日志的方法多种多样,但只有一些是从始至终都在使用,其它的仅在特别情况下使用。比如说,通过电子邮件或 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() 函数用于显示“EA 交易”的当前状态。比如 "Open 2 deal" 或 "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. + 一切内容都被写入 "Terminal_dir\MQL5\Logs\data.txt" 文件。
  4. - 来自脚本/“EA 交易”/指标的所有信息均被写入一个日志。
  5. - 策略测试程序中不适用。
  6. - 如被频繁调用,则其可能长时间冻结终端(比如说,如果每一次价格跳动均调用或循环打印数组)。
  7. - 无法实现消息分组。
  8. - 日志文件查看不便。
  9. - 不能将消息保存到标准数据文件夹以外的文件夹。

实际交易中第 6 点非常关键,尤其是超短线交易或修改止损价时。缺点非常多,不胜枚举,但我觉得这就够了。  

使用 Print() 函数

此函数会将日志消息写入名为 "Experts" 的专门窗口。代码如下:

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() 函数类似,只是现在是在无通知的情况下,将所有消息写入 "Experts" 选项卡,并写入 "Terminal_dir\MQL5\Logs\data.txt" 文件。再研究一下该方法的利弊:  

  1. + 所有信息均被一致记录。
  2. + 一切内容都已被写入 "Terminal_dir\MQL5\Logs\data.txt" 文件。
  3. + 适合程序工作的持续记录。
  4. - 来自脚本/“EA 交易”/指标的所有信息均被写入一个日志。
  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); // 关闭日志文件
  }

运行并浏览至 "Terminal_dir\MQL5\Files" 文件夹,用文本编辑器打开 "log.txt" 文件。内容如下:

Log to File

如您所见,作为结果的输出无额外消息,只是我们向该文件写入的内容。说一说利弊:

  1. + 快速。
  2. + 只写入我们想要的内容。
  3. + 您可以将来自不同程序的消息写入不同的文件,从而杜绝了日志交叉。
  4. - 日志中没有新消息通知。
  5. - 没办法区分特定消息或消息类别。
  6. - 打开日志耗时长,必须浏览至文件夹再打开文件。

小结:

上述的所有方法都有自身缺点,但是您可以修正改善一些。前三种记录方法不够灵活,我们几乎无法影响其行为。而最后一种方法 - 将日志写入文件则最为灵活,我们可以决定消息记录的方式和时间。如果您想显示单独的一个数字,则显然前三种方法更方便。但如果您拥有一个含有大量代码的复杂程序,没有记录则很难使用。


新记录方法


现在,我来告诉您怎样改善把记录写入文件的方法,再给你一种查看日志的称手工具。这是一款 Windows 应用程序,名为 LogMon,是我用 C++ 编写的。

先开始编写类吧,让它来执行所有的记录,也就是说:

  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
// 以MB为单位最大文件尺寸
#define MAX_FILE_SIZEMB 10
//+------------------------------------------------------------------+
//|   Logger                                                         |
//+------------------------------------------------------------------+
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="注释";   }
     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(); // 设置缓存重置时间
  }
//+------------------------------------------------------------------

在 MetaEditor 中创建包含文件 (.mqh),复制类的源代码,并保存于 "CLogger.mqh" 名下。现在,我们再多谈谈每一种方法,说说如何应用这个类。

使用 CLogger 类

要开始利用该类将消息录入日志,我们需要把类文件纳入到“EA 交易”/指标/脚本:

#include <CLogger.mqh>

接下来,您必须要创建一个该类的对象:

CLogger logger;

我们会利用 "logger" 对象执行所有操作。现在,我们需要通过调用 "SetSetting()" 法调整设置。我们需要将项目名称和文件名称传递到该方法内。还有两个可选参数 - 缺省分类的名称和缓存时间(以秒计,指缓存被写入文件之前的存储期)。如果指定为零,则所有消息会被写入一次。

SetSetting(string project,             // 项目名
           string file_name,           // 日志文件名
           string default_category="", // 省缺类别
           int cache_time_limit=0      // 缓存生命时间秒数
           );

调用示例:

logger.SetSetting("我的项目","我的日志","注释",60);

结果是,消息会被写入 "Client_Terminal_dir\MQL5\Files\log\log_MyProject\log_myLog_date.txt" 文件,缺省分类为 "Comment",缓存时间为 60 秒。之后,您需要调用 init() 方法以打开/创建日志文件。调用示例很简单,因为您无需传递参数:  

logger.init();

此方法会生成日志文件的路径和名称,打开它并检查其是否超过了大小上限。如果大小超过了之前设置的常量值,则会打开另一份文件,且有 1 连接其名称。然后再次检查尺寸,直到打开的文件大小正确。

之后,指针移至文件末尾位置。现在,对象已做好了写入日志的准备。我们覆盖了写入方法。我们可以靠它设置消息的不同结构、调用写入方法的示例以及文件中的结果:

// 以省缺类别写消息
logger.write("测试消息");
// 以“错误”类别写消息
logger.write("测试消息", "错误");
// 以“错误”类别写消息,将以红色突出显示在日志监视
logger.write("测试消息", "错误",Red);
// 以“错误”类别写消息,将以红色突出显示在日志监视
// 同样,消息将包括当前文件名和当前行
logger.write("测试消息", "错误",Red,__FILE__,__LINE__);
// 以“错误”类别写消息,将以绿黄色突出显示在日志监视
// 但现在我们指定每种独立颜色为: 红, 绿, 蓝. 0-黑, 255 - 白
logger.write("测试消息", "错误",173,255,47);
// 以“错误”类别写消息,将以绿黄色突出显示在日志监视
// 但现在我们指定每种独立颜色为: 红, 绿, 蓝. 0-黑, 255 - 白
// 同样,消息将包括当前文件名和当前行
logger.write("测试消息", "错误",173,255,47,__FILE__,__LINE__);

日志文件将包含下述行:

注释:|: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;
//+------------------------------------------------------------------+
//| 脚本程序初始函数                                                   |
//+------------------------------------------------------------------+

void OnStart()
  {
//---
   logger.SetSetting("proj","lfile");      // 设置
   logger.init();                          // 初始化
   logger.write("启动脚本","系统");  
   for(int i=0;i<100000;i++)               // 写 100000 消息到日志
     {
      logger.write("日志: "+(string)i,"注释",100,222,100,__FILE__,__LINE__);
     }
   logger.write("停止脚本","系统"); 
   logger.flush();                         // 刷缓存
   logger.deinit();                        // 卸载
  }
//+------------------------------------------------------------------

脚本于 3 秒后执行,并创建了 2 个文件:

Log files

文件内容:

Log file contents

全部 100000 条消息均是如此。看到了吧,一切运行都是相当快速。您可以修改此类,添加新功能或是进行优化。

消息输出量


既然您编写了一个程序,您就必须会显示几种类型的消息:

  1. 重大错误(程序未能正常运行)
  2. 非重大错误、交易操作等等的通知(程序正遭遇临时错误,或是程序做出了重要操作,必须通知用户)。
  3. 调试信息(数组与变量的内容,以及实际工作中不需要的其它信息)。

还有一种明智之举,那就是在不更改源代码的情况下,调整想要打印的信息。我们会将此目标作为一个简单的函数实现,而且不会用到类和方法。

声明会存储消息输出量的变量参数。变量中的数越大,将显示的消息分类就越多。如果您想完全禁用消息输出,则为其赋值 "-1"。

input int dLvl=2;

下面是函数的源代码,且必须在创建 CLogger 类的对象之后声明。

void debug(string debugMsg,             // 消息文本
          int lvl        )              // 消息级别
{
   if (lvl<=dLvl)                       // 以消息输出级别比较消息级别
   {
       if (lvl==0)                      // 如果消息是紧急 (级别 = 0)
       {logger.write(debugMsg,"",Red);} // 标记为红色
       else
       {logger.write(debugMsg);}        // 否则打印省缺颜色
   }
}

来看一个示例:为最重要的消息指定量 "0",将任意数字(从零开始按升序排列)指定给用途最小的消息。

debug("EA 错误!",0);      // 紧急错误
debug("止损执行",1);      // 通知
int i = 99;
debug("变量 i:"+(string)i,2); // 调试信息,变量内容

利用 LogMon 便于日志查看

好,现在我们已经拥有包含数千行内容的日志文件了。但是,要在其中查找信息可是相当困难了。它们未被划分为各个类别,彼此又没什么区别。我曾试图解决这一问题,编写一个程序,来查看由 CLogger 类生成的日志。现在我为您简单地介绍一下 LogMon - 一款利用 WinAPI 以 C++ 语言编写的程序。正因如此,它速度快、且体积小。本程序完全免费。

要使用本程序,您需要:

  1. 将其复制到 "Client_Terminal_dir\MQL5\Files\" 文件夹并运行 - 常规模式下。
  2. 将其复制到 "Agents_dir\Agent\MQL5\Files\" 文件夹并运行 - 测试或优化时。

程序主窗口如下所示:

LogMon main window

主窗口中包含工具栏和带有树状视图的窗口。要展开某个项目,则用鼠标左键双击。列表中的文件中 - 都是项目,位于l "Client_Terminal_dir\MQL\Files\log\" 文件夹中。您要利用 SetSetting() 方法设定 CLogger 中项目的名称。文件夹列表中的文件 - 是实际上的日志文件。日志文件中的消息,都被划分为您利用 write() 方法指定的分类。括号中的数字 - 是指该分类中的消息数量。

现在,我们从左到右来研究工具栏上的按钮。

删除项目或日志文件以及复位树状视图的按钮

如果按下该按钮,就会出现下述窗口:

删除,清除

如果按下 "Delete and Flush" (删除与清除)按钮,扫描文件/文件夹的所有线程都会被停止,树状视图会被重置,并提示您删除选定文件或项目(只需点击某元素以将其选定 - 无需勾选复选框!)。"Reset" (复位)按钮会停止所有扫描文件/文件夹的线程,并清空树状视图。

查看 "About" (关于)对话框的按钮

显示有关程序及其编程者的简要信息。

程序窗口始终置顶显示的按钮

将程序窗口置于所有其它窗口之上。

日志文件中新消息监控激活的按钮

此按钮会将程序窗口隐藏到系统托盘 系统托盘  并激活日志文件中的新消息监控。要选择待扫描的项目/文件/分类,则勾选必要元素旁边的复选框。

如果您勾选消息分类旁边的复选框,则会根据该项目/文件/分类中的新消息触发通知。如果您勾选文件旁边的复选框,则会根据该文件(任何分类)的新消息触发通知。最后,如果您勾选项目旁边的复选框,则会根据新日志文件及文件中的消息触发通知。

监控

如果您已激活监控且将程序窗口最小化至系统托盘,那么,当选定元素中出现新消息时,主应用程序窗口就会最大化,且伴有声音通知。要禁用通知,则用鼠标左键点击列表中的任何地方。要停止监控,则点击系统托盘中的程序图标 LogMon icon。要将通知声音更换为自己的,则将名为 "alert.wav" 的 .wav 文件放入程序执行文件的相同文件夹。  

查看日志分类

要查看具体分类,只需双击它。之后就会看到消息框:

LogMon search

您可以在此窗口中搜索消息,锁定窗口永处最前,并切换自动滚动。每条信息的颜色,都利用 CLogger 类的 write() 方法分别设置。消息的背景会利用选定颜色高亮显示。

双击某消息时,它会打开一个独立的窗口。如果消息太长、与对话框不匹配,用它就会很方便:  

LogMon message

现在,您有了一件查看并监控日志文件的称手工具。衷心期望此款程序能在您开发和使用 MQL5 程序的过程中给您帮助。

总结

您程序中的记录事件非常有用,它会帮助您识别隐藏的错误,发现改善您程序的机会。本文中,我们讲述了记录到文件、日志监控与查看的最简便方法和程序。

期待您的评论和建议!

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/150

附加的文件 |
logmon.rar (78.26 KB)
logmon_source.rar (96.87 KB)
clogger.mqh (15.52 KB)
testlogger.mq5 (2.4 KB)
“EA 交易”运行期间平衡曲线斜率的控制 “EA 交易”运行期间平衡曲线斜率的控制

找到交易系统的规则,再于“EA 交易”中进行编程,任务就完成一半了。随着交易结果的累积,您需要通过某种方式纠正“EA 交易”的操作。本文讲述一种方法,通过创建平衡曲线斜率的测量反馈,改善“EA 交易”的性能。

通过指定的幻数计算总持仓量的最佳方法 通过指定的幻数计算总持仓量的最佳方法

本文探讨了与指定交易品种和幻数有关的总持仓量的计算问题。所提议的方法仅请求交易历史记录的最少必要部分,在总持仓量等于零时查找最接近的时间,并用最新的交易进行计算。还考虑了客户端全局变量的处理。

New Bar (新柱)事件处理程序 New Bar (新柱)事件处理程序

MQL5 编程语言处理问题的能力已达到一个全新的水平。即便是那些已有此类解决方案的任务,也因为面向对象编程而进阶到一个更高的水平。本文中,我们会举一个检查图表中新柱的特别简单的例子,而且,它已经转化成为一种相当强大且用途多样的工具。什么工具?到文中找答案吧。

一个用于通过 Google Chart API 构建图表的库 一个用于通过 Google Chart API 构建图表的库

构建各种类型的图表是分析市场情形及测试交易系统的一个基本部分。通常,为了构建一个精致的图表,必须将数据输出到一个文件,然后在 MS Excel 等应用程序中使用该文件。这样并不是非常方便,并且使我们无法动态更新数据。Google Charts API 通过向服务器发送特别请求,提供了在线创建图表的方式。在本文中,我们将尝试让创建此类请求和从 Google 服务器获得图表的过程实现自动化。