精通日志记录(第一部分):MQL5中的基础概念与入门步骤·进阶篇
🛠️

精通日志记录(第一部分):MQL5中的基础概念与入门步骤·进阶篇

(2/3)·自带日志看不清EA内部行为?本篇落地项目结构与核心类,把黑匣子变成可控工具

偏理论进阶 第 2/3 篇

接上篇的基础概念,我们继续深挖:很多开发者在EA里直接调Print,等到真出拒绝请求或超时,翻记录像在噪里捞针。日志不是写了就完事,缺级别、缺模型、缺主类,后面排查成本只会翻倍。

◍ 搭好日志库的目录与类骨架

在 MT5 里做模块化开发,第一步不是写逻辑,而是把文件和文件夹命名规范定死。打开导航选项卡,右键 Includes 文件夹新建子目录,取名 Logify,所有日志相关代码都塞进这一层,后期维护成本会低很多。 接着在 Logify 里用「新建类」向导生成主文件 Logify.mqh,类名填 CLogify,作者和链接字段随便改不影响编译。同样步骤再补两个文件:LogifyLevel.mqh 放日志级别枚举,LogifyModel.mqh 放单条日志的数据结构。最终磁盘路径是 Include/Logify/ 下三个 .mqh,框架就算立起来了。 向导生成的 CLogify 目前只有空构造和析构,但已经能 #include 进 EA 编译通过。下面这段是主文件初始代码,注意 #property 那几行只是元数据,删掉也不影响运行。 别把空类当完工。三个文件齐了才算「框架完备」,真正写日志写入和级别过滤是后面小节的事,现在先确认 MT5 导航树里这三个文件都在、能无报错编译。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Logify.mqh |
class=class="str">"cmt">//|                                                                 joaopedrodev |
class=class="str">"cmt">//|                                        [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "joaopedrodev"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class CLogify
  {
class="kw">private:
class="kw">public:
                      CLogify();
                     ~CLogify();
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CLogify::CLogify()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CLogify::~CLogify()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

给日志定个轻重缓急

在 MT5 里做自己的日志系统,第一步是把「这条信息有多重要」先说清楚。用枚举把级别钉死,比满屏 Print 强得多:后期你要过滤、要告警、要只把致命错误推给小布,都靠这个分级。 下面这段枚举就是分级的核心。从 LOG_LEVEL_DEBUG 到 LOG_LEVEL_FATAL,数值从 0 自增,严重程度递增。DEBUG 最轻,一般只在本地排查时用;FATAL 最重,通常意味着 EA 必须停止运行。

MQL5 / C++
<span class="keyword">enum</span> ENUM_LOG_LEVEL
&nbsp;&nbsp;{
&nbsp;&nbsp; LOG_LEVEL_DEBUG = <span class="number">class="num">0</span>,&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Debug</span>
&nbsp;&nbsp; LOG_LEVEL_INFOR,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Infor</span>
&nbsp;&nbsp; LOG_LEVEL_ALERT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Alert</span>
&nbsp;&nbsp; LOG_LEVEL_ERROR,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Error</span>
&nbsp;&nbsp; LOG_LEVEL_FATAL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Fatal</span>
&nbsp;&nbsp;};
逐行拆一下:第 1 行 enum ENUM_LOG_LEVEL 声明枚举类型名,MT5 惯例加 ENUM_ 前缀避免冲突。第 2–7 行是花括号内的值列表,DEBUG 显式赋 0,后面四个不写值,编译器按 1、2、3、4 顺延。每行尾的 // 注释只是级别名标注,不影响运行。 实盘里有个坑:INFOR 拼错了(应为 INFO),如果你后面用字符串拼级别名,复制粘贴就会对不上。建议直接抄这段代码进 LogifyLevel.mqh,然后在 EA 里用 if(level>=LOG_LEVEL_ERROR) 做过滤——只把 ERROR 以上的丢进文件或推给分析模块,DEBUG 刷屏会拖慢回测。外汇与贵金属波动大,日志分级能帮你更快定位穿仓前的异常,但任何日志手段都不降低交易本身的高风险。

MQL5 / C++
enum ENUM_LOG_LEVEL
  {
  LOG_LEVEL_DEBUG = class="num">0,      class=class="str">"cmt">// Debug
  LOG_LEVEL_INFOR,          class=class="str">"cmt">// Infor
  LOG_LEVEL_ALERT,          class=class="str">"cmt">// Alert
  LOG_LEVEL_ERROR,          class=class="str">"cmt">// Error
  LOG_LEVEL_FATAL,          class=class="str">"cmt">// Fatal
  };

「日志条目的结构体怎么落」

在 MT5 里做一套日志捕获,第一步是把「一条日志」长什么样定下来。把它塞进 LogifyModel.mqh,后面所有模块吐出来的事件都按同一 shape 走,避免各写各的字符串导致后期解析崩。 MqlLogifyModel 用 5 个字段锁死一条记录:timestamp 是 ulong,存事件发生的日期时间,创建时填;level 挂严重级别(依赖前面包含的 LogifyLevel.mqh 里的枚举);origin 是 string,标模块或函数名;message 是事件描述;metadata 留作 JSON 或纯文本,放执行参数之类附加数据。 构造函数给两套:默认版把所有字段清零或置空,level 默认 LOG_LEVEL_DEBUG;参数化版直接吃 5 个入参生成实例。下面代码贴的是完整 struct,开 MT5 新建 mqh 复制即可编译。 别把 metadata 当摆设 很多EA只写 message,真到回测排查「哪根K线、哪个品种、什么参数下出的错」就抓瞎。metadata 里顺手塞 symbol+timeframe+魔术码,后期用小布类工具捞日志会省一半时间。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 LogifyModel.mqh |
class=class="str">"cmt">//|                                                                 joaopedrodev |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "joaopedrodev"
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "LogifyLevel.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct MqlLogifyModel
  {
   class="type">ulong timestamp;          class=class="str">"cmt">// Date and time of the event
   ENUM_LOG_LEVEL level;     class=class="str">"cmt">// Severity level
   class="type">class="kw">string origin;            class=class="str">"cmt">// Log source
   class="type">class="kw">string message;           class=class="str">"cmt">// Log message
   class="type">class="kw">string metadata;          class=class="str">"cmt">// Additional information in JSON or text
   
   MqlLogifyModel::MqlLogifyModel(class="type">void)
     {
      timestamp = class="num">0;
      level = LOG_LEVEL_DEBUG;
      origin = "";
      message = "";
      metadata = "";
     }
   MqlLogifyModel::MqlLogifyModel(class="type">ulong _timestamp,ENUM_LOG_LEVEL _level,class="type">class="kw">string _origin,class="type">class="kw">string _message,class="type">class="kw">string _metadata)
     {
      timestamp = _timestamp;
      level = _level;
      origin = _origin;
      message = _message;
      metadata = _metadata;
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ CLogify 主类怎么搭

日志库的核心是一个叫 CLogify 的类,定义在 Logify.mqh 里,负责统一管理和输出日志。它不继承任何父类,纯独立封装,对外暴露构造、析构和五个分级方法,外加一个通用 Append 供内部调度。 分级方法覆盖了 Debug、Infor、Alert、Error、Fatal 五档,各自只收 message、origin、metadata 三个参数,origin 和 metadata 都带空串默认值。实际输出目前全走 Print 打到 MT5 控制台,但 Append 的签名里已经预留了 timestamp 和 ENUM_LOG_LEVEL,后续接文件、数据库或图表时不用改接口。 写完这个类先别急着接策略,下一步该写测试用例验证五个级别能不能正常落控制台。外汇和贵金属行情波动剧烈、杠杆风险高,日志分级起码能让你在回测炸仓前从 Error 里看出苗头。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class : CLogify                                                    |
class=class="str">"cmt">//|                                                                        |
class=class="str">"cmt">//| [PROPERTY]                                                             |
class=class="str">"cmt">//| Name      : Logify                                                     |
class=class="str">"cmt">//| Heritage  : No heritage                                                |
class=class="str">"cmt">//| Description : Core class for log management.                           |
class=class="str">"cmt">//|                                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CLogify
  {
class="kw">private:

class="kw">public:
                      CLogify();
                     ~CLogify();

  class=class="str">"cmt">//--- Generic method for adding logs
  class="type">bool                Append(class="type">ulong timestamp, ENUM_LOG_LEVEL level, class="type">class="kw">string message, class="type">class="kw">string origin = "", class="type">class="kw">string metadata = "");

  class=class="str">"cmt">//--- Specific methods for each log level
  class="type">bool                Debug(class="type">class="kw">string message, class="type">class="kw">string origin = "", class="type">class="kw">string metadata = "");
  class="type">bool                Infor(class="type">class="kw">string message, class="type">class="kw">string origin = "", class="type">class="kw">string metadata = "");
  class="type">bool                Alert(class="type">class="kw">string message, class="type">class="kw">string origin = "", class="type">class="kw">string metadata = "");
  class="type">bool                Error(class="type">class="kw">string message, class="type">class="kw">string origin = "", class="type">class="kw">string metadata = "");
  class="type">bool                Fatal(class="type">class="kw">string message, class="type">class="kw">string origin = "", class="type">class="kw">string metadata = "");
  };
class=class="str">"cmt">//+------------------------------------------------------------------+

把日志封装丢进EA的构造函数里

CLogify 这个类把 MT5 的 Print 调用包了一层,构造和析构都是空实现,真正干活的是 Append。它接收时间戳、日志等级、消息体和两个可选字段,先把等级枚举翻译成 DEBUG / INFO / ALERT / ERROR / FATAL 这样的字符串,再拼成 [时间] [等级] [来源] - 消息 元数据 打进终端。 Debug 和 Infor 两个便捷方法只是用 TimeCurrent() 自动填时间戳,再转调 Append,分别传 LOG_LEVEL_DEBUG 和 LOG_LEVEL_INFOR。你在 EA 的 OnInit 里 CLogify log; 声明一下,之后任何位置 log.Debug("spread="+(string)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD),"OnTick"); 就能把点差变动留痕,复盘时大概率比翻裸 Print 快。 外汇与贵金属杠杆高,这类日志只帮你定位逻辑漏洞,不预示行情方向。把等级切到 ALERT 只打关键信号,终端刷屏量可能从每秒几十条降到个位数。

MQL5 / C++
CLogify::CLogify()
  {
  }
CLogify::~CLogify()
  {
  }
class="type">bool CLogify::Append(class="type">ulong timestamp,ENUM_LOG_LEVEL level,class="type">class="kw">string message,class="type">class="kw">string origin="",class="type">class="kw">string metadata="")
  {
   MqlLogifyModel model(timestamp,level,origin,message,metadata);
   class="type">class="kw">string levelStr = "";
   class="kw">switch(level)
     {
      case LOGIFY_DEBUG: levelStr = "DEBUG"; class="kw">break;
      case LOGIFY_INFO:  levelStr = "INFO";  class="kw">break;
      case LOGIFY_ALERT: levelStr = "ALERT"; class="kw">break;
      case LOGIFY_ERROR: levelStr = "ERROR"; class="kw">break;
      case LOGIFY_FATAL: levelStr = "FATAL"; class="kw">break;
     }

   Print("[" + TimeToString(timestamp) + "] [" + levelStr + "] [" + origin + "] - " + message + " " + metadata);
   class="kw">return(true);
  }
class="type">bool CLogify::Debug(class="type">class="kw">string message,class="type">class="kw">string origin="",class="type">class="kw">string metadata="")
  {
   class="kw">return(this.Append(TimeCurrent(),LOG_LEVEL_DEBUG,message,origin,metadata));
  }
class="type">bool CLogify::Infor(class="type">class="kw">string message,class="type">class="kw">string origin="",class="type">class="kw">string metadata="")
  {
   class="kw">return(this.Append(TimeCurrent(),LOG_LEVEL_INFOR,message,origin,metadata));
  }
把重复劳动交给小布
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到EA行为异常提醒,你专注决策而非翻终端日志。

常见问题

Print是终端级输出,缺少级别区分与结构化存储;自建系统可定义错误、性能等层级并落模型,便于后续检索与解读。
常见有调试、信息、警告、错误、致命五档,具体可按EA复杂度裁剪,目的是让噪音与关键事件分离。
目前小布内置的是对品种页异常行为的AIGC诊断,不解析用户私有日志文件;但思路一致,都是把黑匣子信号转成可读提醒。
数据模型隔离了事件内容与输出格式,主类只管调度,后期换文件、换终端显示都不用改记录逻辑,扩展性更强。
按include放接口与类、source放实现、example放调用样例,层级浅而命名直白,避免后期多EA复用时的路径纠缠。