精通日志记录(第一部分):MQL5中的基础概念与入门步骤·进阶篇
(2/3)·自带日志看不清EA内部行为?本篇落地项目结构与核心类,把黑匣子变成可控工具
接上篇的基础概念,我们继续深挖:很多开发者在EA里直接调Print,等到真出拒绝请求或超时,翻记录像在噪里捞针。日志不是写了就完事,缺级别、缺模型、缺主类,后面排查成本只会翻倍。
◍ 搭好日志库的目录与类骨架
在 MT5 里做模块化开发,第一步不是写逻辑,而是把文件和文件夹命名规范定死。打开导航选项卡,右键 Includes 文件夹新建子目录,取名 Logify,所有日志相关代码都塞进这一层,后期维护成本会低很多。 接着在 Logify 里用「新建类」向导生成主文件 Logify.mqh,类名填 CLogify,作者和链接字段随便改不影响编译。同样步骤再补两个文件:LogifyLevel.mqh 放日志级别枚举,LogifyModel.mqh 放单条日志的数据结构。最终磁盘路径是 Include/Logify/ 下三个 .mqh,框架就算立起来了。 向导生成的 CLogify 目前只有空构造和析构,但已经能 #include 进 EA 编译通过。下面这段是主文件初始代码,注意 #property 那几行只是元数据,删掉也不影响运行。 别把空类当完工。三个文件齐了才算「框架完备」,真正写日志写入和级别过滤是后面小节的事,现在先确认 MT5 导航树里这三个文件都在、能无报错编译。
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 必须停止运行。
<span class="keyword">enum</span> ENUM_LOG_LEVEL { LOG_LEVEL_DEBUG = <span class="number">class="num">0</span>, <span class="comment">class=class="str">"cmt">// Debug</span> LOG_LEVEL_INFOR, <span class="comment">class=class="str">"cmt">// Infor</span> LOG_LEVEL_ALERT, <span class="comment">class=class="str">"cmt">// Alert</span> LOG_LEVEL_ERROR, <span class="comment">class=class="str">"cmt">// Error</span> LOG_LEVEL_FATAL, <span class="comment">class=class="str">"cmt">// Fatal</span> };
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 刷屏会拖慢回测。外汇与贵金属波动大,日志分级能帮你更快定位穿仓前的异常,但任何日志手段都不降低交易本身的高风险。
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+魔术码,后期用小布类工具捞日志会省一半时间。
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 里看出苗头。
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 只打关键信号,终端刷屏量可能从每秒几十条降到个位数。
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)); }