精通日志记录(第八部分):具备自动翻译能力的错误日志记录
引言
经过前几期的讲解,想必您已深知日志记录绝非简单的事件流水账。在算法交易的日常操作中,K线跳动、策略决策与市场不确定性交织成一张复杂的网,日志的真正价值是精准记录EA程序的"真实意图"。
在日常使用Logify时,我发现一个亟待解决的问题:错误处理机制仍然停留在表层。即使拥有完善的日志格式结构,最终输出的日志内容仍只能显示原始错误代码,无法直观体现背后的真实含义。比如以下这段日志:
logify.Error("Failed to send sell order", "Order Management", "Code: "+IntegerToString(GetLastError())); // Console: // 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | Code: 10016
结果呢?一句模棱两可的提示。我们能知道报错发生在哪个模块,但无法得知失败的具体原因。试问,谁不曾为了查阅文档而翻阅那几十个晦涩难懂的 MQL5 错误代码?我以前就常常面临这种困境:拿到错误代码后,还得打开文档慢慢搜索,才能搞清楚问题的真正起因。正是源于这种真实的痛点,我萌生了一个想法:如果 Logify 能帮我解读报错会怎样?它能不能不只给我一串数字代码,还自动生成清晰、贴合场景的错误解释,直接写入日志文件?
由此,Logify迎来了一次重要的功能进化:现在用户只需将错误代码直接传入库中,剩下的翻译、格式化与显示工作都由库自动完成。将前面的示例按新方案重构后,代码将变得更加简洁高效:
logify.Error("Failed to send sell order", GetLastError(), "Order Management"); // Console: // 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | 10016: Invalid stops in the request
日志的可读性和实用性已经提升了一大截,但我决定更进一步。一个致力于传递清晰信号的系统,不仅要在代码层面简洁易懂,更要在自然语言表达上保持一致。因此,本文的Logify新增了多语言报错支持,可自动转换为11种语言,覆盖英语、葡萄牙语、西班牙语、德语意大利语、俄语、土耳其语、中文、日语和韩语。从此,你的日志可以直接使用团队或客户的母语,无需手动调整语言包。
本期学习目标:
- 直接调用MQL5官方文档,为报错日志补充精准的语义描述。
- 实现多语言报错提示,根据使用场景动态切换适配语言
- 根据日志的重要级别自定义格式,为错误、警告和消息创建差异化的呈现模板
归根结底,Logify 将变得更加智能、易用,在实战中也更加实用。在算法交易领域,细节决定成败,每一秒浪费在破解错误代码的时间,都可能错过下一个关键决策窗口。
建立报错处理体系
万丈高楼平地起,我们首先定义一个结构体来表示报错信息。我们将这个结构称为 MqlError,它将在整个系统中用于存储任何报错的三个基本要素:
- 数值代码 (code)
- 符号常量 (constant)
- 可读描述 (description)
我们在 <Include/Logify/Error/Error.mqh> 文件中创建了这个结构体:
//+------------------------------------------------------------------+ //| Data structure for error handling | //+------------------------------------------------------------------+ struct MqlError { int code; // Cod of error string description; // Description of error string constant; // Type error MqlError::MqlError(void) { code = 0; description = ""; constant = ""; } }; //+------------------------------------------------------------------+
这个结构虽然简单,但足以表示平台返回的任何报错,无论是执行报错、交易报错,还是用户自定义报错。既然有了容器,我们需要用实际的报错数据来填充它。
MetaQuotes 提供了数百个错误代码,每个代码都有其符号常量和英文描述,但我们想走得更远。我们希望库能说德语、西班牙语、法语、葡萄牙语、中文……并且日志能自动适应配置的语言。
这里的策略是为每种语言创建一个 .mqh 文件,其中包含一个用于初始化所有已知报错数组的函数。文件名遵循 ErrorMessages.xx.mqh 的标准,其中 xx 代表语言缩写(例如 en 代表英语,de 代表德语,pt 代表葡萄牙语)。
以下是英语文件的样貌:
//+------------------------------------------------------------------+ //| ErrorMessages.en.mqh | //| joaopedrodev | //| https://www.mql5.com/en/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/en/users/joaopedrodev" //+------------------------------------------------------------------+ //| Import struct //+------------------------------------------------------------------+ #include "../Error.mqh" void InitializeErrorsEnglish(MqlError &errors[]) { //--- Free and resize ArrayFree(errors); ArrayResize(errors,274); //+------------------------------------------------------------------+ //| Unknown error | //+------------------------------------------------------------------+ errors[0].code = 0; errors[0].description = "No error found"; errors[0].constant = "ERROR_UNKNOWN"; //+------------------------------------------------------------------+ //| Server error | //+------------------------------------------------------------------+ errors[1].code = 10004; errors[1].description = "New quote"; errors[1].constant = "TRADE_RETCODE_REQUOTE"; //--- errors[2].code = 10006; errors[2].description = "Request rejected"; errors[2].constant = "TRADE_RETCODE_REJECT"; //--- // Remaining error codes... //--- errors[272].code = 5625; errors[272].description = "Parameter binding error, wrong index"; errors[272].constant = "ERR_DATABASE_RANGE"; //--- errors[273].code = 5626; errors[273].description = "Open file is not a database file"; errors[273].constant = "ERR_DATABASE_NOTADB"; } //+------------------------------------------------------------------+
InitializeErrorsEnglish() 函数接收一个 MqlError 数组,并用英语的所有已知错误填充它。我们对德语、西班牙语、法语、意大利语、日语、韩语、葡萄牙语、俄语、土耳其语和中文重复此模式。所有这些文件都保存在 <Include/Logify/Error/Languages> 文件夹中。总共有 11 个语言文件,每个文件包含 274 个条目。是的,这是一项艰苦的工作,但现在您只需简单包含即可享用。请记住,所有代码都附在文章末尾。
既然数据都已整理完毕,我们需要一个接口来查询它们。这就是 CLogifyError 类的用武之地。该类将负责加载正确语言的报错,并为任何请求的错误代码返回完整信息。
类的接口非常简洁:
//+------------------------------------------------------------------+ //| class : CLogifyError | //| | //| [PROPERTY] | //| Name : LogifyError | //| Heritage : No heritage | //| Description : class to look up the error code and return details | //| of each error code. | //| | //+------------------------------------------------------------------+ class CLogifyError { private: ENUM_LANGUAGE m_language; MqlError m_errors[]; public: CLogifyError(void); ~CLogifyError(void); //--- Set/Get void SetLanguage(ENUM_LANGUAGE language); ENUM_LANGUAGE GetLanguage(void); //--- Get error MqlError Error(int code); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyError::CLogifyError() { InitializeErrorsEnglish(m_errors); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyError::~CLogifyError(void) { } //+------------------------------------------------------------------+
实例化时,默认加载英文报错信息。您可以随时使用 SetLanguage() 更改语言,它将重新加载为相应语言的数据。
Error(int code) 方法是该类的核心。它在错误数组中搜索输入的代码,并返回对应的 MqlError。如果未找到该代码,函数将返回一个通用错误作为回退。此外,它会自动检测错误是否位于为用户定义错误保留的范围内(ERR_USER_ERROR_FIRST 到 ERR_USER_ERROR_LAST),并同样为它们返回答案。
//+------------------------------------------------------------------+ //| Returns error information based on the error code received | //+------------------------------------------------------------------+ MqlError CLogifyError::Error(int code) { int size = ArraySize(m_errors); for(int i=0;i<size;i++) { if(m_errors[i].code == code) { //--- Return return(m_errors[i]); } } //--- User error if(code >= ERR_USER_ERROR_FIRST && code < ERR_USER_ERROR_LAST) { MqlError error; error.code = code; error.constant = "User error"; error.description = "ERR_USER_ERROR"; //--- Return return(m_errors[274]); } //--- Return return(m_errors[0]); } //+------------------------------------------------------------------+
最后,我们将此错误结构添加到 MqlLogifyModel 日志数据模型中,以便我们可以在记录的数据结构内部访问错误数据。
#include "LogifyLevel.mqh" #include "Error/Error.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct MqlLogifyModel { string formated; // The log message formatted according to the specified format. string levelname; // Textual name of the log level (e.g., "DEBUG", "INFO") string msg; // Main content of the log message string args; // Additional arguments associated with the log message ulong timestamp; // Timestamp of the log event, represented in seconds since the start of the Unix epoch datetime date_time; // Date and time of the log event, in datetime format ENUM_LOG_LEVEL level; // Enumeration representing the severity level of the log string origin; // Source or context of the log message (e.g., class or module) string filename; // Name of the source file where the log message was generated string function; // Name of the function where the log was called ulong line; // Line number in the source file where the log was generated MqlError error; // Error data void MqlLogifyModel::Reset(void) { formated = ""; levelname = ""; msg = ""; args = ""; timestamp = 0; date_time = 0; level = LOG_LEVEL_DEBUG; origin = ""; filename = ""; function = ""; line = 0; } MqlLogifyModel::MqlLogifyModel(void) { this.Reset(); } MqlLogifyModel::MqlLogifyModel(string _formated,string _levelname,string _msg,string _args,ulong _timestamp,datetime _date_time,ENUM_LOG_LEVEL _level,string _origin,string _filename,string _function,ulong _line,MqlError &_error) { formated = _formated; levelname = _levelname; msg = _msg; args = _args; timestamp = _timestamp; date_time = _date_time; level = _level; origin = _origin; filename = _filename; function = _function; line = _line; error = _error; } }; //+------------------------------------------------------------------+
完成这些更改后,我们的库函数结构如下所示:

将错误集成到主类中
我们已经有了表示错误的可靠结构、承载其描述的多语言文件以及一个专门根据语言检索这些消息的类。但到目前为止,所有这些都还是空中楼阁,与库的中央引擎——CLogify 类——处于断开状态。
是时候将错误系统集成到 Logify 的核心中了,以便生成的每条日志(如有必要)都能包含对问题清晰、多语言的解释。为此,我们将遵循三个步骤:实例化错误类、调整添加日志的方法(Append),最后调整格式化器以识别新的占位符 {err_code}、{err_constant} 和 {err_description}。
1. 将智能报错处理引入 Logify 核心
CLogify 类负责连接日志的格式化、处理和发送,因此,首要任务是让该类直接对接我们的错误处理系统。要实现这点,只需导入 LogifyError.mqh 文件并将 CLogifyError 类实例化为 CLogify 的私有成员。这保证了它在内部始终可用,而无需用户操心。这一小小的补充打开了通往成功的大门。现在,每当发生报错,我们都可以快速查询其代码、描述和常量。
由于 CLogifyError 的默认语言是英语,如果不允许用户选择最合适的语言就太浪费了。这就是为什么我们添加了两个简单的方法。代码最终如下所示:
#include "LogifyModel.mqh" #include "Handlers/LogifyHandler.mqh" #include "Handlers/LogifyHandlerComment.mqh" #include "Handlers/LogifyHandlerConsole.mqh" #include "Handlers/LogifyHandlerDatabase.mqh" #include "Handlers/LogifyHandlerFile.mqh" #include "Error/LogifyError.mqh" //+------------------------------------------------------------------+ //| class : CLogify | //| | //| [PROPERTY] | //| Name : Logify | //| Heritage : No heritage | //| Description : Core class for log management. | //| | //+------------------------------------------------------------------+ class CLogify { private: CLogifyError m_error; public: CLogify(); ~CLogify(); //--- Language void SetLanguage(ENUM_LANGUAGE language); ENUM_LANGUAGE GetLanguage(void); }; //+------------------------------------------------------------------+
2. 用错误代码丰富 Append 方法
到目前为止,Append() 方法接收主消息、附加参数、来源、文件名、行号、函数……但唯独不包含错误代码本身。如果我们希望日志携带有关故障的上下文,就需要允许传递此代码,即使是可选的。这就是为什么我们在方法签名的末尾添加了 code_error 参数:
bool Append(ENUM_LOG_LEVEL level, string msg, string origin = "", string args = "", string filename = "", string function = "", int line = 0, int code_error = 0);
这样一来,对于那些不想传递错误的用户,对 Append() 的调用不会发生变化;而对于想传递错误的用户,只需将其附加在末尾即可。这种方法避免了对现有代码兼容性的破坏。
现在,手头有了错误代码,我们使用 CLogifyError 类的 Error() 方法来获取相应的 MqlError 结构:
//+------------------------------------------------------------------+ //| Generic method for adding logs | //+------------------------------------------------------------------+ bool CLogify::Append(ENUM_LOG_LEVEL level,string msg, string origin = "", string args = "",string filename="",string function="",int line=0,int code_error=0) { //--- Ensures that there is at least one handler this.EnsureDefaultHandler(); //--- Textual name of the log level string levelStr = ""; switch(level) { case LOG_LEVEL_DEBUG: levelStr = "DEBUG"; break; case LOG_LEVEL_INFO : levelStr = "INFO"; break; case LOG_LEVEL_ALERT: levelStr = "ALERT"; break; case LOG_LEVEL_ERROR: levelStr = "ERROR"; break; case LOG_LEVEL_FATAL: levelStr = "FATAL"; break; } //--- Creating a log template with detailed information datetime time_current = TimeCurrent(); MqlLogifyModel data("",levelStr,msg,args,time_current,time_current,level,origin,filename,function,line,m_error.Error(code_error)); //--- Call handlers int size = this.SizeHandlers(); for(int i=0;i<size;i++) { data.formated = m_handlers[i].GetFormatter().Format(data); m_handlers[i].Emit(data); } return(true); } //+------------------------------------------------------------------+
MqlLogifyModel 对象现在不仅携带日志信息,还携带了导致报错的“灵魂”。现在剩下要做的就是为 Error() 和 Fatal() 方法添加一个新的函数重载,同时不删除现有的重载,因为这可能会引发错误,因为库用户并不总是需要添加错误代码。代码如下所示:
class CLogify { public: //--- Specific methods for each log level bool Debug(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Info(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Alert(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Error(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0); bool Fatal(string msg, string origin = "", string args = "",string filename="",string function="",int line=0); bool Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0); }; bool CLogify::Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_ERROR,msg,origin,args,filename,function,line,code_error)); } bool CLogify::Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_FATAL,msg,origin,args,filename,function,line,code_error)); }
3. 使用报错占位符格式化消息
到目前为止,报错信息已被加载,但尚未出现在格式化的日志中。为此,我们需要扩展 CLogifyFormatter 的占位符以包含错误属性。逻辑简单而巧妙:我们仅在日志级别为 ERROR 或更高时显示错误数据。信息或调试日志不需要这种多余的内容,我们要的是简洁的日志,而非干扰信息。
//+------------------------------------------------------------------+ //| Format logs | //+------------------------------------------------------------------+ string CLogifyFormatter::FormatLog(MqlLogifyModel &data) { string formated = m_log_format; StringReplace(formated,"{levelname}",data.levelname); StringReplace(formated,"{msg}",data.msg); StringReplace(formated,"{args}",data.args); StringReplace(formated,"{timestamp}",IntegerToString(data.timestamp)); StringReplace(formated,"{date_time}",this.FormatDate(data.date_time)); StringReplace(formated,"{level}",IntegerToString(data.level)); StringReplace(formated,"{origin}",data.origin); StringReplace(formated,"{filename}",data.filename); StringReplace(formated,"{function}",data.function); StringReplace(formated,"{line}",IntegerToString(data.line)); if(data.level >= LOG_LEVEL_ERROR) { StringReplace(formated,"{err_code}",IntegerToString(data.error.code)); StringReplace(formated,"{err_constant}",data.error.constant); StringReplace(formated,"{err_description}",data.error.description); } else { StringReplace(formated,"{err_code}",""); StringReplace(formated,"{err_constant}",""); StringReplace(formated,"{err_description}",""); } return(formated); } //+------------------------------------------------------------------+
至此,我们为Logify新增了三个定制化扩展接口:
- {err_code}:显示确切的错误编号(例如 10006)
- {err_constant}:关联的常量(例如 TRADE_RETCODE_REJECT)
- {err_description}:可读的解释(例如 Request rejected)
假设我们定义了一个全局格式,如下所示:
"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})" 在您的设想中,这应该产生类似这样的结果:
2025.06.09 14:22:15 [ERROR] Failed to send order (10016 TRADE_RETCODE_REJECT: Request rejected)
实际上,对于 ERROR 和 FATAL 日志级别,这非常完美。但是,当我们将同一格式用于 DEBUG、INFO 或 ALERT 日志时会怎样?结果会是这样:
2025.06.09 14:22:15 [INFO] Operation completed successfully ( : )
或者更糟,根据格式的不同,甚至可能导致日志视觉上的错位。尽管我们小心翼翼地清除了较低级别的错误占位符,但问题依然存在:该格式是预期 {err_code}、{err_constant} 和 {err_description} 有值的。当它们没有值时,日志看起来就会……怪异且残缺。我们这里遇到的是语义上的断裂:同一套格式掩码被应用到了上下文完全不同的消息上。
解决方案不在于试图修正数据,而在于将每个日志级别视为它本来的样子:一种不同类型的消息,有着不同的需求和格式。
因此,我们不再使用具有通用格式的 CLogifyFormatter,而是让每个级别(DEBUG、INFO、ALERT、ERROR、FATAL)都有自己的格式化器。这允许我们进行定制,例如:
- DEBUG:显示函数、文件和行号
- INFO:极简风格,只显示发生了什么
- ALERT:添加上下文,如来源和参数
- ERROR:包含错误数据
- FATAL:尽可能完整
实现多种格式
首先,我们调整格式的存储方式,不再是一个简单的字符串,而是将其更改为一个包含 5 种可能性(每个重要级别一种)的数组。
| 原代码 | 新代码 |
|---|---|
class CLogifyFormatter { private: //--- Stores the formats string m_date_format; string m_log_format; }; | class CLogifyFormatter { private: //--- Stores the formats string m_date_format; string m_format[5]; }; |
我们将 m_log_format 替换为一个字符串数组,每个位置对应一个日志级别。现在我们有五种不同的格式,每一种都可以根据将要发送的消息类型进行调整。
格式按级别分离后,我们需要一种便捷的方法来设置它们。为此,我们创建了两个 SetFormat 方法,一个将相同的格式应用于所有级别,另一个单独配置特定级别:
class CLogifyFormatter { public: void SetFormat(string format); void SetFormat(ENUM_LOG_LEVEL level, string format); }; //+------------------------------------------------------------------+ //| Sets the format for all levels | //+------------------------------------------------------------------+ void CLogifyFormatter::SetFormat(string format) { m_format[LOG_LEVEL_DEBUG] = format; m_format[LOG_LEVEL_INFO] = format; m_format[LOG_LEVEL_ALERT] = format; m_format[LOG_LEVEL_ERROR] = format; m_format[LOG_LEVEL_FATAL] = format; } //+------------------------------------------------------------------+ //| Sets the format to a specific level | //+------------------------------------------------------------------+ void CLogifyFormatter::SetFormat(ENUM_LOG_LEVEL level, string format) { m_format[level] = format; } //+------------------------------------------------------------------+
这种设计不仅方便,而且既通用又具体。对于追求简单的用户,一个 SetFormat(...) 即可搞定。对于追求精准的用户,只需为每个所需级别调用 SetFormat(level, format)。
格式分离后,格式化方法现在需要反映这种划分。以前,我们使用通用变量 m_log_format 来获取消息掩码。。现在,我们直接在数组中查找格式,通过日志级别进行索引:
| 原代码 | 新代码 |
|---|---|
string CLogifyFormatter::FormatLog(MqlLogifyModel &data) { string formated = m_log_format; ... } | string CLogifyFormatter::Format(MqlLogifyModel &data) { string formated = m_format[data.level]; ... } |
这以一种干净、可扩展且可预测的方式解决了问题。无需额外检查,无需抑制空占位符的变通方法。每个级别都确切知道自己应该包含什么。
测试
接下来,我们来到了让所有努力焕发生机的部分——实际测试,看看它在不同场景和日志级别下的表现如何。同上一篇文章一样,在用于测试的文件 LogifyTest.mqh 中,我们准备了以下元素:
- 一个通过 Comment() 的可视化处理程序,带有边框、标题和行数限制。
- 一个通过 Print() 的控制台处理程序,用于在平台内部日志中记录消息。
- 一个在处理程序之间共享的格式化器。
- 四条不同级别的日志消息:两条 DEBUG,一条 INFO 和一条带代码的 ERROR。
//+------------------------------------------------------------------+ //| Import CLogify | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify logify; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { //--- Handler config MqlLogifyHandleCommentConfig m_config; m_config.size = 5; // Max log lines m_config.frame_style = LOG_FRAME_STYLE_SINGLE; // Frame style m_config.direction = LOG_DIRECTION_UP; // Log direction (up) m_config.title = "Expert name"; // Log panel title CLogifyFormatter *formatter = new CLogifyFormatter("{date_time} [{levelname}]: {msg}"); formatter.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}]: {msg} [{err_constant} | {err_code} | {err_description}]"); //--- Create and configure handler CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment(); handler_comment.SetConfig(m_config); handler_comment.SetLevel(LOG_LEVEL_DEBUG); // Min log level handler_comment.SetFormatter(formatter); CLogifyHandlerConsole *handler_console = new CLogifyHandlerConsole(); handler_console.SetLevel(LOG_LEVEL_DEBUG); // Min log level handler_console.SetFormatter(formatter); //--- Add handler to Logify logify.AddHandler(handler_comment); logify.AddHandler(handler_console); //--- Test logs logify.Debug("Initializing Expert Advisor...", "Init", ""); logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14"); logify.Info("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1"); logify.Error("Failed to send sell order", 10016,"Order Management"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
当您运行此代码时,MetaTrader 控制台会显示以下消息,格式正如我们所预期。视觉效果清楚地表明,较低级别(如 DEBUG 和 INFO)遵循较精简的格式,而 ERROR 则调用包含报错信息的更详细格式:
09/06/2025 14:22:31 [DEBUG]: Initializing Expert Advisor... 09/06/2025 14:22:31 [DEBUG]: RSI indicator value calculated: 72.56 09/06/2025 14:22:31 [INFO]: Buy order sent successfully 09/06/2025 14:22:31 [ERROR]: Failed to send sell order [TRADE_DISABLED | 10016 | Trade operations not allowed]
请注意最后一条日志如何在方括号中携带了一个附加块,其中包含符号常量、数字代码和直观易懂的报错描述。而这一切的实现,都无需在 EA 代码中添加额外的条件逻辑。每个日志级别都带有自己的格式,正确的占位符仅在有意义的地方出现。
结论
在这篇文章中,我们对 Logify 库进行了改进。在第一篇文章中,我们首先构建了数据模型,然后为不同的输出通道创建了灵活的处理程序。而今天,我们增加了对报错消息的多语言支持,允许每条日志为开发者带来清晰的技术上下文,无论使用何种语言。
我们通过动态格式化器取得了进展,它能够处理自定义占位符并自动适应日志的重要级别。当我们发现通用格式导致日志风格不统一后,对架构进行了升级,让系统可以为每个重要等级配置专属格式模板。最终我们通过一场实战测试完成了全部功能验证,Logify在真实交易环境下运行稳定高效,不仅输出的日志简洁规范,表现符合预期,而且可以灵活拓展新功能。
如果您坚持读到了这里,说明您已经学会了如何:
- 设计可扩展且解耦的日志结构;
- 基于 MetaTrader 代码集成多语言报错消息;
- 结合日志的重要等级,使用占位符动态格式化上下文信息;
- 构建具有独立配置的可重用处理程序;
Logify 像任何好工具一样,可以继续演进。如果实现了新功能,我会在未来的文章中带给大家。在此期间,请尽情使用、调整和改进。
| 文件名 | 说明 |
|---|---|
| Experts/Logify/LogifyTest.mq5 | 用于测试库功能的文件,包含一个实际示例 |
| Include/Logify/Error/Languages/ErrorMessages.XX.mqh | 统计每种语言的报错消息数量,其中 X 代表语言的缩写 |
| Include/Logify/Error/Error.mqh | 用于存储报错的数据结构 |
| Include/Logify/Error/LogifyError.mqh | 用于获取详细报错信息的类 |
| Include/Logify/Formatter/LogifyFormatter.mqh | 负责格式化日志记录的类,将占位符替换为具体值 |
| Include/Logify/Handlers/LogifyHandler.mqh | 用于管理日志处理器的基类,包括级别设置和日志发送 |
| Include/Logify/Handlers/LogifyHandlerComment.mqh | 负责格式化日志记录的类,将占位符替换为具体值 |
| Include/Logify/Handlers/LogifyHandlerConsole.mqh | 将格式化后的日志直接发送到 MetaTrader 终端控制台的日志处理器 |
| Include/Logify/Handlers/LogifyHandlerDatabase.mqh | 将格式化后的日志发送到数据库的日志处理器(目前仅包含打印输出,但很快我们会将其保存到真正的 sqlite 数据库中) |
| Include/Logify/Handlers/LogifyHandlerFile.mqh | 将格式化后的日志发送到文件的日志处理器 |
| Include/Logify/Utils/IntervalWatcher.mqh | 检查一个时间间隔是否已经过去,允许您在库内部创建定时执行的例程。 |
| Include/Logify/Logify.mqh | 日志管理的核心类,集成了级别、模型和格式化功能 |
| Include/Logify/LogifyLevel.mqh | 定义 Logify 库日志级别的文件,支持精细控制 |
| Include/Logify/LogifyModel.mqh | 用于建模日志记录的结构,包含级别、消息、时间戳和上下文等详细信息 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18467
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
在 MQL5 中实现其他语言的实用模块(第 01 部分):构建受 Python 启发的 SQLite3 库
数据科学和机器学习(第 37 部分):利用烛条形态和人工智能战胜市场
新手在交易中的10个基本错误
在 MQL5 中构建自优化智能交易系统(第八部分):多策略分析
谢谢,我很乐意提供帮助。如有任何改进建议,请与我联系!
我编译了日志库 Logify,在 MT5 build 5100 之后,出现了几个与 CLogifyHandlerDatabase::Query 中的 类型有关的编译错误。我相信您应该已经解决了这个问题。
感谢您的建议,我会在今后的文章中采纳这些建议。
关于语言的一个小想法。
或许可以将原始英文信息编排成这样一种方式,即可以很容易地将其复制并粘贴到翻译器(deepl.com 或 translate.google.com)中,然后将结果放回程序中。这样,任何人都可以轻松地用自己的语言设置程序。
Deeple 可识别 36 种语言,Google 甚至可识别约 130 种语言。