请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3364740 新帖:4

查找错误和记录

爱德华发表于:2 月 25 日 04:00回复(1)

简介

亲爱的读者,您好!

本文中,我们会研究在“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 程序的过程中给您帮助。

总结

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

期待您的评论和建议!

全部回复

0/140

量化课程

    移动端课程