查找错误和记录·进阶篇
◍ 把日志写入改成类封装
MQL5 是面向对象语言,运行速度和 C++ 差距不大,所以把记录逻辑直接做成 MQL5 类最划算,不用绕去调外部模块。 这个类要接管几件具体事:记住日志文件落盘路径、按「名称+日期时间」自动建文件、把传进来的参数拼成一行日志、给每条消息打上时间和分类、还能指定颜色,并且做消息缓存——每 n 秒或每 n 条才真正写盘一次,减少 IO 抖动。 作者另写了一个叫 LogMon 的 Windows 小工具(C++ 实现)专门看这类日志,但核心记录机制先在 MQL5 侧用类解决,后面直接复用。
把日志落盘的类怎么接进EA
把 CLogger 类单独存成 CLogger.mqh,放在 MQL5 的 Include 路径下,EA、指标或脚本里用 #include 引一下就能用。类内部用 MAX_CACHE_SIZE 10000 条做缓存上限,MAX_FILE_SIZEMB 10 限制单文件 10MB,超出就自动开带序号的新文件。 实例化后先调 SetSetting() 传项目名和文件名,可选参数里 default_category 给缺省分类、cache_time_limit 是缓存秒数;传 0 表示每条都直接写盘。示例里设了缺省分类 "Comment" 和 60 秒缓存,日志最终落到 Client_Terminal_dir\MQL5\Files\log\log_MyProject\log_myLog_date.txt。 SetSetting 之后必须调 init() 才会真正建文件路径、开句柄并做大小检查,指针移到末尾待写。程序退出前调 flush() 和 deinit() 把残余缓存刷进文件并关句柄,否则可能丢最后一段日志。 实测一个脚本 3 秒内循环写 10 万条消息,生成 2 个文件,全程未卡终端,说明这种缓存+翻文件机制在高频记录下够轻量。外汇和贵金属策略跑这类日志需注意:高频写盘在 VPS 磁盘 IO 差时可能拖慢 tick 处理,属高风险环境下的隐性成本。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Clogger.mqh | class=class="str">"cmt">//| ProF | class=class="str">"cmt">//| http:// | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "ProF" class="macro">#class="kw">property link "http:class=class="str">"cmt">//" class=class="str">"cmt">// 高速缓存的最大尺寸 (数量) class="macro">#define MAX_CACHE_SIZE class="num">10000 class=class="str">"cmt">// 以MB为单位最大文件尺寸 class="macro">#define MAX_FILE_SIZEMB class="num">10 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Logger | class=class="str">"cmt">//+------------------------------------------------------------------+ class CLogger { class="kw">private: class="type">class="kw">string project,file; class=class="str">"cmt">// 项目的名称和日志文件 class="type">class="kw">string logCache[MAX_CACHE_SIZE]; class=class="str">"cmt">// 最大缓存大小 class="type">int sizeCache; class=class="str">"cmt">// 缓存计数器 class="type">int cacheTimeLimit; class=class="str">"cmt">// 缓存时间 class="type">class="kw">datetime cacheTime; class=class="str">"cmt">// 最后刷缓存至文件的时间 class="type">int handleFile; class=class="str">"cmt">// 日志文件句柄 class="type">class="kw">string defCategory; class=class="str">"cmt">// 省缺类别 class="type">void writeLog(class="type">class="kw">string log_msg); class=class="str">"cmt">// 写消息到日志或文件, 并刷缓存 class="kw">public: class="type">void CLogger(class="type">void){cacheTimeLimit=class="num">0; cacheTime=class="num">0; sizeCache=class="num">0;}; class=class="str">"cmt">// 构造器 class="type">void ~CLogger(class="type">void){}; class=class="str">"cmt">// 析构器 class="type">void SetSetting(class="type">class="kw">string project,class="type">class="kw">string file_name, class="type">class="kw">string default_category="",class="type">int cache_time_limit=class="num">0); class=class="str">"cmt">// 设置 class="type">void init(); class=class="str">"cmt">// 初始化, 打开文件写 class="type">void deinit(); class=class="str">"cmt">// 关闭文件 class="type">void write(class="type">class="kw">string msg,class="type">class="kw">string category=""); class=class="str">"cmt">// 生成消息 class="type">void write(class="type">class="kw">string msg,class="type">class="kw">string category,class="type">class="kw">color colorOfMsg,class="type">class="kw">string file="",class="type">int line=class="num">0); class=class="str">"cmt">// 生成消息 class="type">void write(class="type">class="kw">string msg,class="type">class="kw">string category,class="type">uchar red,class="type">uchar green,class="type">uchar blue, class="type">class="kw">string file="",class="type">int line=class="num">0); class=class="str">"cmt">// 生成消息 class="type">void flush(class="type">void); class=class="str">"cmt">// 刷缓存至文件 };
「给日志类加上文件轮转与缓存控制」
在 MT5 里跑长周期黄金或欧美策略时,日志文件如果不加限制会无限膨胀,拖慢 FileWrite 效率。下面这段 CLogger 实现把单文件上限钉在 MAX_FILE_SIZEMB(以 MB 计,代码内按 *1000000 换算成字节)这个常量上,超了就自动序号新建。
| 构造函数里 default_category 留空时,默认归到「注释」类;cache_time_limit 传 0 代表禁用缓存、每次直接落盘。init() 用 TimeCurrent 取系统时间拼出 log_项目名/log_文件名_年月日.txt 的路径,并以 FILE_WRITE | FILE_READ | FILE_UNICODE | FILE_TXT | FILE_SHARE_READ 打开,允许其他程序同时读。 |
|---|
writeLog() 的缓存逻辑值得抄:cacheTimeLimit 非零且距上次未超时限、缓存数组没满,就先塞进 logCache;否则 flush 清盘。每次写完后用 FileTell 比对文件大小,超了就 deinit+init 滚动到新文件。 让小布替你跑这套 把 MAX_FILE_SIZEMB 设成 5,挂一个每 tick 写日志的 EA 在 XAUUSD 上,半天就能看到 .txt 自动跳到 _1、_2,验证轮转是否按字节触发而非按时间。外汇与贵金属波动剧烈,日志 IO 虽小但高频仍可能卡顿,实盘前请在策略测试器跑满 24 小时。
class="type">class="kw">string default_category="",class="type">int cache_time_limit=class="num">0) { project=project_name; class=class="str">"cmt">// 项目名 file=file_name; class=class="str">"cmt">// 文件名 cacheTimeLimit=cache_time_limit; class=class="str">"cmt">// 缓存时间 if(default_category=="") class=class="str">"cmt">// 设置省缺类别 { defCategory="注释"; } else {defCategory = default_category;} } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 初始化 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLogger::init(class="type">void) { class="type">class="kw">string path; class="type">MqlDateTime date; class="type">int i=class="num">0; TimeToStruct(TimeCurrent(),date); class=class="str">"cmt">// 得到当前时间 StringConcatenate(path,"log\\log_",project,"\\log_",file,"_", date.year,date.mon,date.day); class=class="str">"cmt">// 生成文件名和路径 handleFile=FileOpen(path+".txt",FILE_WRITE|FILE_READ| FILE_UNICODE|FILE_TXT|FILE_SHARE_READ); class=class="str">"cmt">// 打开或创建新文件 class="kw">while(FileSize(handleFile)>(MAX_FILE_SIZEMB*class="num">1000000)) class=class="str">"cmt">// 检查文件大小 { class=class="str">"cmt">// 打开或创建新日志文件 i++; FileClose(handleFile); handleFile=FileOpen(path+"_"+(class="type">class="kw">string)i+".txt", FILE_WRITE|FILE_READ|FILE_UNICODE|FILE_TXT|FILE_SHARE_READ); } FileSeek(handleFile,class="num">0,SEEK_END); class=class="str">"cmt">// 设置指针到文件尾 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 去初始化 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLogger::deinit(class="type">void) { FileClose(handleFile); class=class="str">"cmt">// 关闭文件 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 写信息至文件或缓存 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLogger::writeLog(class="type">class="kw">string log_msg) { if(cacheTimeLimit!=class="num">0) class=class="str">"cmt">// 检查缓存是否允许 { if((sizeCache<MAX_CACHE_SIZE-class="num">1 && TimeCurrent()-cacheTime<cacheTimeLimit) || sizeCache==class="num">0) class=class="str">"cmt">// 检查缓存时间是否超出或到达缓存限制 { class=class="str">"cmt">// 写文件至缓存 logCache[sizeCache++]=log_msg; } else { class=class="str">"cmt">// 写文件至缓存并刷缓存至文件 logCache[sizeCache++]=log_msg; flush(); } } else { class=class="str">"cmt">// 缓存被禁止, 立即写入文件 FileWrite(handleFile,log_msg); } if(FileTell(handleFile)>(MAX_FILE_SIZEMB*class="num">1000000)) class=class="str">"cmt">// 检查当前文件大小 { class=class="str">"cmt">// 文件大小超出允许限制, 关闭当前文件并打开新的文件 deinit(); init(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 生成信息和写入日志 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLogger::write(class="type">class="kw">string msg,class="type">class="kw">string category="") { class="type">class="kw">string msg_log; if(category=="") class=class="str">"cmt">// 检查传递的类别是否存在 { category=defCategory; } class=class="str">"cmt">// 设置省缺类别 class=class="str">"cmt">// 生成行并调用写消息方法 StringConcatenate(msg_log,category,":|: ",TimeToString(TimeCurrent(),TIME_SECONDS)," ",msg); writeLog(msg_log); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 生成信息和写入日志 | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 正文
<span class="keyword">void</span> CLogger::write(<span class="keyword">string</span> msg,<span class="keyword">string</span> category,<span class="keyword">color</span> colorOfMsg,<span class="keyword">string</span> file=<span class="string">""</span>,<span class="keyword">int</span> line=<span class="number">0</span>) { <span class="keyword">string</span> msg_log; <span class="keyword">int</span> red,green,blue; red=(colorOfMsg &<span>Red</span>); <span class="comment"> // 从常量选择红色</span> green=(colorOfMsg &<span class="number">0x00FF00</span>)>><span class="number">8</span>; <span class="comment">// 从常量选择绿色</span> blue=(colorOfMsg &<span>Blue</span>)>><span class="number">16</span>; <span class="comment"> // 从常量选择蓝色</span> <span class="comment"> // 检查文件或行是否已传递,生成行并调用写消息方法</span> <span class="keyword">if</span>(file!=<span class="string">""</span> && line!=<span class="number">0</span>) { <span class="functions">StringConcatenate</span>(msg_log,category,<span class="string">":|:"</span>,red,<span class="string">","</span>,green,<span class="string">","</span>,blue, <span class="string">":|:"</span>,<span class="functions">TimeToString</span>(<span class="functions">TimeCurrent</span>(),TIME_SECONDS),<span class="string">" "</span>, <span class="string">"文件: "</span>,file,<span class="string">" 行: "</span>,line,<span class="string">" "</span>,msg); } <span class="keyword">else</span> { <span class="functions">StringConca
给日志上色并定位到行
在 MT5 里自定义日志类时,write 方法支持直接传 RGB 三通道值来高亮某类消息。比如传 173,255,47 这组参数,终端会以绿黄色把“错误”类别的消息标出来,方便你在密集输出里一眼抓到异常。 带 __FILE__ 和 __LINE__ 的写法会在日志里补上源文件名与行号。上面样例输出中,行 29 的测试消息被记成“文件: testLogger.mq5 行: 29”,排查 EA 或脚本的问题时不用再手动翻代码。 压力测试里用 for 循环连写 100000 条带颜色的注释消息,单条 RGB 设为 100,222,100。跑完记得调 logger.flush() 强制落盘、logger.deinit() 关文件,否则缓存可能丢最后几行。外汇与贵金属行情波动剧烈,这类诊断日志仅用于本地验证,不构成任何交易信号。
logger.write("测试消息", "错误",class="num">173,class="num">255,class="num">47); class=class="str">"cmt">// 以“错误”类别写消息,将以绿黄色突出显示在日志监视 class=class="str">"cmt">// 但现在我们指定每种独立颜色为: 红, 绿, 蓝. class="num">0-黑, class="num">255 - 白 class=class="str">"cmt">// 同样,消息将包括当前文件名和当前行 logger.write("测试消息", "错误",class="num">173,class="num">255,class="num">47,__FILE__,__LINE__); 注释:|:class="num">23:class="num">13:class="num">12 测试消息 错误:|:class="num">23:class="num">13:class="num">12 测试消息 错误:|:class="num">255,class="num">0,class="num">0:|:class="num">23:class="num">13:class="num">12 测试消息 错误:|:class="num">255,class="num">0,class="num">0:|:class="num">23:class="num">13:class="num">12 文件: testLogger.mq5 行: class="num">27 测试消息 错误:|:class="num">173,class="num">255,class="num">47:|:class="num">23:class="num">13:class="num">12 测试消息 错误:|:class="num">173,class="num">255,class="num">47:|:class="num">23:class="num">13:class="num">12 文件: testLogger.mq5 行: class="num">29 测试消息 logger.flush(); class=class="str">"cmt">// 强制刷缓存至硬盘 logger.deinit(); class=class="str">"cmt">// 关闭文件 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| testLogger.mq5 | class=class="str">"cmt">//| ProF | class=class="str">"cmt">//| http:// | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "ProF" class="macro">#class="kw">property link "http:class=class="str">"cmt">//" class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Сlogger.mqh> CLogger logger; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 脚本程序初始函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- logger.SetSetting("proj","lfile"); class=class="str">"cmt">// 设置 logger.init(); class=class="str">"cmt">// 初始化 logger.write("启动脚本","系统"); for(class="type">int i=class="num">0;i<class="num">100000;i++) class=class="str">"cmt">// 写 class="num">100000 消息到日志 { logger.write("日志: "+(class="type">class="kw">string)i,"注释",class="num">100,class="num">222,class="num">100,__FILE__,__LINE__); } logger.write("停止脚本","系统"); logger.flush(); class=class="str">"cmt">// 刷缓存 logger.deinit(); class=class="str">"cmt">// 卸载 } class=class="str">"cmt">//+------------------------------------------------------------------