来自专业程序员的提示(第三部分):日志。 连接到 Seq 日志收集和分析系统(基础篇)
📘

来自专业程序员的提示(第三部分):日志。 连接到 Seq 日志收集和分析系统(基础篇)

第 1/2 篇

「把 MT5 日志接到 Seq 做集中分析」

做 EA 排错时,终端自带日志只能看当前品种当前图表,跨品种、跨账号的异常很难串起来。把日志统一推到 Seq,就能在一个界面里按时间线、按等级过滤,定位问题快得多。 下面这个 Logger 类封装了向 Seq 发送结构化日志的最小逻辑,核心是用 WinHTTP 把 JSON 发到 Seq 的 ingest 接口。注意 Seq 默认接收端口是 5341,部署时要和 MT5 所在机器网络互通。 [CODE]class SeqLogger {private: string m_url; public: SeqLogger(string url) { m_url = url; } void Send(string level, string msg) { string json = "{\"Level\":\"" + level + "\",\"Message\":\"" + msg + "\"}"; char post[]; int res[]; string hdr = "Content-Type: application/json"; WinHttpPost(m_url, hdr, json, post, res); } }; [/CODE] 代码逐行看:m_url 存的是 Seq 的 ingest 地址;Send 方法拼出带 Level 和 Message 字段的 JSON;WinHttpPost 是 MQL5 标准库函数,负责实际 POST。实盘里建议加一层本地文件兜底,Seq 连不上时先写文件,避免丢日志。 外汇和贵金属波动剧烈、滑点频繁,日志里把 tick 时间戳和报价一起带上,复盘时能还原当时市场状态,但这不保证找出所有 bug,只是提高排查概率。

MQL5 / C++
class SeqLogger
{class="kw">private:
   class="type">class="kw">string m_url;
class="kw">public:
   SeqLogger(class="type">class="kw">string url) { m_url = url; }
   class="type">void Send(class="type">class="kw">string level, class="type">class="kw">string msg)
   {
      class="type">class="kw">string json = "{\"Level\":\"" + level + "\",\"Message\":\"" + msg + "\"}";
      class="type">char post[]; class="type">int res[];
      class="type">class="kw">string hdr = "Content-Type: application/json";
      WinHttpPost(m_url, hdr, json, post, res);
   }
};

◍ MT5 日志落盘与缓存陷阱

MQL5 里 Print 和 PrintFormat 会把消息写进智能系统栏流水账,实际落盘为 Unicode 文本。终端每天新建 MQL5/Logs/yyyymmdd.log,按日期切分避免单文件膨胀。 所有图表上的脚本与 EA 共用同一日志文件,写入先走磁盘缓存。直接从资源管理器打开 log,往往看不到最新几行——数据还滞在缓存里。 要强制刷盘,要么关终端,要么在「智能系统」标签页右键选「打开」,终端会借打开目录的动作把缓存写回文件。外汇与贵金属 EA 调试时这一步容易漏,导致误判策略是否触发。 原生日志在终端内检索很费眼。后续会统一用 Logger 类封装输出,并接 Seq 做在线聚合;也可借 Python 包把历史 MT5 日志灌进 Seq 回看。

把 MT5 日志接到本地 Seq 做实时排查

Seq 是跑在本机的日志检索服务,事件以 JSON 存储并支持类 SQL 查询,界面默认开在 http://localhost:5341/#/events。对跑多 EA 的 MT5 终端来说,它能把分散的 Print、告警和报错聚到一条时间线上,比翻终端日记文件快得多。 先在系统 hosts 文件(c:/windows/system32/drivers/etc/hosts)加一行 127.0.0.1 seqlocal.net,否则 MT5 的 WebRequest 白名单只认域名不认 localhost。接着进 Seq UI 的 admin/Preferences,勾选 Show timestamps in Coordinated Universal Time (UTC),关掉时区转换,保证日志时间和 MT5 服务器时间对齐,不然后面比对 K 线会乱。 MT5 侧打开 工具/选项/智能系统,把 http://seqlocal.net 加进允许 WebRequest 的 URL 列表,EA 里才能用 WebRequest 往外发。外汇与贵金属杠杆高、滑点无常,日志只是排查手段,不代表任何交易结论。 想看实时流,点 Seq 界面上的 Tail 按钮切到在线模式,EA 一打点你这边就刷出来。复杂策略里加几个关键状态的上报点,哪根线卡死一眼能定位。

「零依赖日志类的级别与落地写法」

做 MT5 EA 调试最怕日志乱成一团。把输出统一成结构化文本,比事后翻终端省事得多。一个不依赖任何外部 include 的 Logger 类能直接拖进项目用,它用三个字母前缀区分消息等级:DBG、INF、WRN、ERR、FTL,在智能系统日志里对齐高亮后一眼能分清轻重。 DEBUG 是给写代码的人看的,频率最高,常带函数名和参数;INFO 面向使用者,记录菜单点击、成交结果这类能读懂的动作;WARNING 提示要留意,比如挂单触发或平仓;ERROR 是非致命错误(拒绝报价、止损非法),之后还能跑;FATAL 一出就得紧急停 EA 修 bug。 MetaTrader 5 终端时间列实际是 TimeLocal,但信息本质属于服务器 TimeCurrent。若消息里要强调真实柱线时间,得自己写进去——下面第二条样本里 13:00 是本地、12:00 才是服务器开盘时间,不写清就会被误导。 宏替换让每次输出自动带文件和函数名,配合 PrintFormat 用“/”切分字段,所有消息就整齐了。Logger 构造接收 appName(Seq 过滤用)和可选 urlSeq;不填 urlSeq 只写本地日志,填了就通过 WebRequest 另发 Seq。注意同项目多文件用 extern 声明 gLog,只能初始化一次,之后都指向同一对象。 Seq 要求 ISO8601 时间,MQL5 的 datetime 只到秒,所以把 GetTickCount64 的毫秒数补上去,方便比消息先后。周期传符号时去掉“PERIOD_”前缀,PERIOD_H1 变“H1”。SendToSeq 先把换行制表换成空格,再拼成 JSON 键值对,@t 固定用 TimeCurrent 而非本地时间,其余参数随你用 SQL 查。

MQL5 / C++
class=class="str">"cmt">// Message levels
class="macro">#define LEV_DEBUG "DBG"   class=class="str">"cmt">// debugging(for service use)
class="macro">#define LEV_INFO  "INF"    class=class="str">"cmt">// information(to track the functions)
class="macro">#define LEV_WARNING "WRN" class=class="str">"cmt">// warning(attention)
class="macro">#define LEV_ERROR   "ERR"  class=class="str">"cmt">// a non-critical error(check the log, work can be continued)
class="macro">#define LEV_FATAL   "FTL"  class=class="str">"cmt">// fatal error(work cannot be continued)
class=class="str">"cmt">// Message output macros
class="macro">#define LOG_SENDER gLog.SetSender(__FILE__, __FUNCTION__)
class="macro">#define LOG_INFO(message) LOG_SENDER; gLog.Info(message)
class="macro">#define LOG_DEBUG(message) LOG_SENDER; gLog.Debug(message)
class="macro">#define LOG_WARNING(message) LOG_SENDER; gLog.Warning(message)
class="macro">#define LOG_ERROR(message) LOG_SENDER; gLog.Error(message)
class="macro">#define LOG_FATAL(message) LOG_SENDER; gLog.Fatal(message)
LOG_INFO(m_result);
LOG_INFO(StringFormat("%s / %s / %s", StringSubstr(EnumToString(m_type), class="num">3),
      TimeToString(m_time0Bar), m_result));
时间               来源         消息
---------------------------------------------------------------------------------------------------------------------
class="num">2022.02.class="num">16 class="num">13:class="num">00:class="num">06.079  Cayman(GBPUSD,H1)  INF: AnalyserRollback::Run Rollback, H1, class="num">12:class="num">00, R1, D1, RO, order class="num">275667165
class="num">2022.02.class="num">16 class="num">13:class="num">00:class="num">06.080  Cayman(GBPUSD,H1)  INF: Analyser::SaveParameters Rollback / class="num">2022.02.class="num">16 class="num">12:class="num">00 / Rollback, H1, class="num">12:class="num">00, R1, D1, RO, order class="num">275667165
class Logger {
class="kw">private:
    class="type">class="kw">string  m_module; class=class="str">"cmt">// module or file name
    class="type">class="kw">string  m_sender; class=class="str">"cmt">// function name
    class="type">class="kw">string  m_level;  class=class="str">"cmt">// message level
    class="type">class="kw">string  m_message;class=class="str">"cmt">// message text
    class="type">class="kw">string  m_urlSeq;  class=class="str">"cmt">// url of the Seq message service
    class="type">class="kw">string  m_appName; class=class="str">"cmt">// application name for Seq
    class=class="str">"cmt">// class="kw">private methods

常见问题

在 MT5 数据目录的 Logs 文件夹找对应日期的 log 文件,用文本工具或接 Seq 做集中检索,比终端里翻滚动条快得多。
多半是日志缓存没刷盘就异常退出,写日志时别依赖默认缓冲,关键级别用强制落盘或异步队列兜底。
可以,小布能接入你的日志流做异常关键词告警,出现 ERROR 级或断连自动推提醒,你只看摘要就行。
用纯文件尾随或轻量 HTTP 转发类把日志推到本机 Seq 容器,不引第三方库,端口通了就能在浏览器看实时流。
落盘时带级别前缀并分文件或分颜色,Seq 里用等级过滤,先扫 ERROR 再回查 INFO 上下文效率最高。