Dominando los registros (Parte 8): Registros de errores con traducción automática
Introducción
Llegados a este punto del proceso, no sorprende que el registro de datos no se trate solo de registrar eventos. Se trata de captar con precisión lo que tu EA intenta decirte en medio del torbellino de ticks, decisiones e incertidumbres que definen el trading algorítmico cotidiano.
Durante mi uso diario de Logify, noté algo que me molestaba: el manejo de errores seguía siendo superficial. Incluso con una estructura de formato robusta, los registros seguían mostrando únicamente el código de error sin procesar, sin ninguna indicación de lo que realmente significaba. Algo como:
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
¿El resultado? Un mensaje vago. Sabemos dónde se produjo el error, pero no por qué. ¿Y quién ha tenido que investigar alguna vez docenas de códigos MQL5 en la documentación? Yo mismo solía hacerlo con bastante frecuencia: en cuanto obtenía el código de error, tenía que buscar en la documentación para averiguar qué había pasado realmente. Fue precisamente de esa fricción real de donde surgió la idea: ¿Y si Logify pudiera interpretar el error por mí? ¿Y si, en lugar de limitarse a darme el código, también me explicara su significado de forma clara y contextualizada, listo para registrarse en el log?
De este modo, nació una evolución funcional: ahora es posible pasar el código de error directamente a la biblioteca, y esta se encarga automáticamente de buscar, formatear y mostrar la descripción correspondiente. El mismo ejemplo anterior, rehecho con esta mejora, queda así:
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
Mucho más claro y útil, pero decidí ir más allá. Un sistema que pretende ofrecer claridad debe ser fluido, no solo en el código, sino también en el lenguaje. Por eso, en esta octava fase, Logify también incorpora soporte multilingüe para los mensajes de error, con traducción automática a 11 idiomas, entre ellos inglés, portugués, español, alemán, francés, italiano, ruso, turco, chino, japonés y coreano. Ahora, tus registros hablan el idioma de tu equipo o de tu cliente sin necesidad de ajustes manuales.
En este paso, aprenderás a:
- Enriquecer los registros de errores con descripciones precisas, tomadas directamente de la documentación de MQL5.
- Mostrar los mensajes de error en varios idiomas, con selección dinámica del idioma más apropiado para cada contexto.
- Personaliza el formato según la gravedad, creando patrones distintos para errores, advertencias e información en función de su grado de criticidad.
En definitiva, Logify será más inteligente, más accesible y mucho más útil en el mundo real. Porque en el trading algorítmico, cada detalle importa, y cada segundo que se dedica a descifrar un error supone un segundo menos para tomar la siguiente decisión correcta.
Crear una forma de manejar errores
Como todo buen sistema necesita una base sólida, comenzamos definiendo una estructura para representar los errores. Esta estructura, a la que llamaremos MqlError, se utilizará en todo el sistema para almacenar las tres partes fundamentales de cualquier error:
- El código numérico (código)
- La constante simbólica
- La descripción legible (descripción)
Hemos creado esta estructura en el archivo <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 = ""; } }; //+------------------------------------------------------------------+
Esta estructura es sencilla pero suficiente para representar cualquier error que devuelva la plataforma, ya sea un error de ejecución, un error de negociación o incluso un error definido por el usuario. Ahora que tenemos un lugar para almacenar los datos, necesitamos completar esta estructura con los errores reales.
MetaQuotes proporciona cientos de códigos de error, cada uno con su constante simbólica y descripción en inglés, pero queremos ir más allá. Queremos que la biblioteca hable alemán, español, francés, portugués, chino... y que los registros se adapten automáticamente al idioma configurado.
La estrategia consiste en crear un archivo .mqh para cada idioma, que contenga una función que inicialice un array con todos los errores conocidos. El nombre del archivo sigue el formato estándar ErrorMessages.xx.mqh, donde xx representa la abreviatura del idioma (por ejemplo, en para inglés, de para alemán, pt para portugués).
Así es como se ve el archivo en inglés:
//+------------------------------------------------------------------+ //| 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"; } //+------------------------------------------------------------------+
La función InitializeErrorsEnglish() recibe una matriz de MqlError y la llena con todos los errores conocidos en el idioma inglés. Repetimos este patrón para otros idiomas como el alemán, el español, el francés, el italiano, el japonés, el coreano, el portugués, el ruso, el turco y el chino. Todos estos archivos se guardan en la carpeta <Include/Logify/Error/Languages>. En total, tenemos 11 archivos de idioma y 274 entradas por archivo. Y sí, fue un trabajo minucioso, pero ahora puedes disfrutarlo con una simple inclusión. Recuerda que todo el código se adjunta al final del artículo.
Ahora que tenemos todos los datos organizados, necesitamos una interfaz para consultarlos. Ahí es donde entra en juego la clase CLogifyError. Esta clase se encargará de cargar los errores en el idioma correcto y de devolver la información completa para cualquier código de error solicitado.
La interfaz de la clase es simple:
//+------------------------------------------------------------------+ //| 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) { } //+------------------------------------------------------------------+
Al crearse, carga los errores en inglés por defecto. Puedes cambiar el idioma en cualquier momento con SetLanguage() y la biblioteca recargará los datos en el idioma apropiado.
El método Error(int code) es el núcleo de la clase. Busca en el array de errores el código introducido y devuelve el MqlError correspondiente. Si no se encuentra el código, la función devuelve un error genérico como alternativa. Además, detecta automáticamente si el error se encuentra dentro del rango reservado para errores definidos por el usuario (ERR_USER_ERROR_FIRST a ERR_USER_ERROR_LAST) y también devuelve una respuesta para ellos.
//+------------------------------------------------------------------+ //| 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]); } //+------------------------------------------------------------------+
Por último, agreguemos esta estructura de error al modelo de datos de registro MqlLogifyModel, para que podamos acceder a los datos de error dentro de la estructura de datos de un registro.
#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; } }; //+------------------------------------------------------------------+
Con estos cambios, esta es la estructura de nuestra biblioteca:

Integración de errores en la clase principal
Ya contamos con una estructura sólida para representar errores, archivos multilingües que contienen sus descripciones y una clase dedicada a recuperar estos mensajes según el idioma. Pero hasta ahora, todo esto seguía desacoplado del motor central de la biblioteca: la clase CLogify.
Es hora de integrar el sistema de errores en el núcleo de Logify, de modo que cada registro generado tenga, si es necesario, una explicación clara y multilingüe de lo que salió mal. Para ello, seguiremos tres pasos: instanciar la clase de error, ajustar el método para agregar registros (Append) y, finalmente, ajustar el formateador para reconocer los nuevos marcadores de posición {err_code}, {err_constant} y {err_description}.
1. Integrando la inteligencia de errores en el núcleo de Logify
La clase CLogify es la que conecta el formateo, la manipulación y el envío del registro, por lo que el primer paso lógico es darle acceso directo a nuestro sistema de errores. Para ello, simplemente importe el archivo LogifyError.mqh e instancie la clase CLogifyError como un miembro privado de CLogify. Esto garantiza que siempre estará disponible internamente, sin que el usuario de la biblioteca tenga que preocuparse por ello. Esta pequeña incorporación ya abre puertas importantes. Ahora, cuando se produce un error, podemos consultar rápidamente su código, descripción y constante.
Dado que el idioma predeterminado de CLogifyError es el inglés, sería un desperdicio no permitir que el usuario elija el idioma más apropiado. Por eso hemos añadido dos métodos sencillos. Así es como se ve el código al final:
#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. Enriquecimiento del método Append con código de error
Hasta ahora, el método Append() recibía el mensaje principal, argumentos adicionales, origen, nombre de archivo, línea, función... pero no el código de error en sí. Si queremos que el registro contenga información sobre los fallos, debemos permitir que se pase este código, aunque sea de forma opcional. Por eso añadimos el parámetro code_error al final de la firma del método:
bool Append(ENUM_LOG_LEVEL level, string msg, string origin = "", string args = "", string filename = "", string function = "", int line = 0, int code_error = 0);
Con esto, la llamada a Append() no cambia para aquellos que no quieren pasar el error, pero para aquellos que sí lo quieren, basta con añadirlo al final. Este enfoque evita romper la compatibilidad con el código existente.
Ahora, con el código de error en mano, utilizamos el método Error() de la clase CLogifyError para obtener la estructura MqlError correspondiente:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
El objeto MqlLogifyModel ahora contiene no solo la información de registro, sino también la información completa del error que lo causó. Ahora solo tienes que añadir una nueva sobrecarga de función para los métodos Error() y Fatal(), sin eliminar las que ya existen, ya que esto puede generar errores, porque el usuario de la biblioteca no siempre necesita añadir un código de error. Así es como se ve el código:
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. Formatear el mensaje con marcadores de posición de error
Hasta el momento, el error se está cargando, pero aún no aparece en el registro formateado. Para ello, necesitamos ampliar el sistema de marcadores de posición de CLogifyFormatter para incluir las propiedades de error. La lógica es simple pero ingeniosa: solo mostramos los datos de error si el nivel de registro es ERROR o superior. Los registros informativos o de depuración no necesitan tanta verbosidad; buscamos claridad, no ruido innecesario en el log.
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Con esto, hemos creado tres nuevos puntos de personalización:
- {err_code} : Muestra el número de error exacto (por ejemplo, 10006)
- {err_constant} : La constante asociada (por ejemplo, TRADE_RETCODE_REJECT).
- {err_description} : La explicación legible (por ejemplo, Solicitud rechazada)
Imaginemos que definimos un formato global como este:
"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})" En tu mente, esto debería producir algo como:
2025.06.09 14:22:15 [ERROR] Failed to send order (10016 TRADE_RETCODE_REJECT: Request rejected)
Y, en efecto, para los niveles de registro ERROR y FATAL, funciona a la perfección. Pero ¿qué ocurre cuando utilizamos este mismo formato para los registros DEBUG, INFO o ALERT? Bueno... el resultado es algo así:
2025.06.09 14:22:15 [INFO] Operation completed successfully ( : )
O peor aún, dependiendo del formato, puede incluso provocar una desalineación visual en los registros. Incluso con nuestro cuidado de limpiar los marcadores de posición de error a niveles inferiores, el problema persiste: el formato se creó esperando que {err_code}, {err_constant} y {err_description} tuvieran valores. Cuando no lo hacen, el registro se vuelve... extraño e incompleto. Lo que tenemos aquí es una ruptura semántica: se está aplicando la misma máscara de formato a mensajes que tienen contextos totalmente diferentes.
La solución no reside en intentar corregir los datos. La clave está en tratar cada nivel de registro como lo que es: un tipo de mensaje diferente, con necesidades y formatos distintos.
Así que, en lugar de usar un formato universal en CLogifyFormatter, hacemos que cada nivel (DEBUG, INFO, ALERT, ERROR, FATAL) tenga su propio formato. Esto nos permite personalizar, por ejemplo:
- DEBUG : Mostrar función, archivo y línea
- INFO : Formato minimalista, solo lo esencial
- ALERT : Añade información contextual, como el origen y los argumentos
- ERROR : Incluir datos de errores
- FATAL : Incluir la mayor cantidad posible de detalle
Implementación de múltiples formatos
Primero ajustamos la forma en que se almacena el formato; en lugar de una simple cadena de texto, lo cambiamos a una matriz con 5 posibilidades (una para cada nivel de gravedad).
| Código antiguo | Nuevo código |
|---|---|
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]; }; |
Hemos sustituido m_log_format por una matriz de cadenas, una posición para cada nivel de registro. Ahora disponemos de cinco formatos diferentes, cada uno de los cuales puede ajustarse según el tipo de mensaje que se vaya a enviar.
Dado que los formatos están separados por nivel, necesitamos una forma práctica de configurarlos. Para ello, creamos dos métodos SetFormat, uno que aplica el mismo formato a todos los niveles y otro que configura individualmente un nivel específico:
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; } //+------------------------------------------------------------------+
Este diseño no solo es práctico, sino que nos permite ser a la vez genéricos y específicos. Para quienes buscan simplicidad, bastará con un solo SetFormat(...). Para aquellos que deseen precisión, simplemente llamen a SetFormat(nivel, formato) para cada nivel deseado.
Ahora que los formatos están separados, el método de formato debe reflejar esta división. Anteriormente, utilizábamos una variable genérica, m_log_format, para obtener la plantilla del mensaje. Ahora, buscamos el formato directamente en el array, indexando por nivel de registro:
| Código antiguo | Nuevo código |
|---|---|
string CLogifyFormatter::FormatLog(MqlLogifyModel &data) { string formated = m_log_format; ... } | string CLogifyFormatter::Format(MqlLogifyModel &data) { string formated = m_format[data.level]; ... } |
Esto resuelve el problema de una manera limpia, extensible y predecible. Sin comprobaciones adicionales, sin soluciones alternativas para suprimir los marcadores de posición vacíos. Cada nivel sabe exactamente lo que debe contener.
Pruebas
Hemos llegado a la parte en la que todo lo que hemos creado cobra vida: las pruebas prácticas y comprobar cómo se comporta en diferentes escenarios y niveles de registro. Dentro del mismo archivo que utilizamos para las pruebas en el artículo anterior, LogifyTest.mqh, hemos preparado los siguientes elementos:
- Un manejador visual a través de Comment() con marco, título y límite de líneas.
- Un manejador de consola mediante Print() para registrar mensajes en el registro interno de la plataforma.
- Un formateador compartido entre los manejadores.
- Cuatro mensajes de registro con diferentes niveles: dos DEBUG, uno INFO y uno ERROR con código.
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Al ejecutar este código, la consola de MetaTrader muestra los siguientes mensajes, con el formato exacto que esperábamos. El resultado visual deja claro que los niveles inferiores (como DEBUG e INFO) siguen un formato más conciso, mientras que ERROR utiliza un formato más detallado con datos de 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]
Observe cómo el último registro contiene un bloque adicional entre corchetes, con la constante simbólica, el código numérico y la descripción del error comprensible para el usuario. Y todo esto fue posible sin añadir lógica condicional adicional al código del EA. Cada nivel de registro tiene su propio formato, y los marcadores de posición correctos aparecen solo donde tienen sentido.
Conclusión
En este artículo, hemos introducido mejoras en la biblioteca Logify. En el primer artículo, empezamos por estructurar el modelo de datos y, a continuación, creamos manejadores flexibles para los distintos canales de salida. Y hoy hemos añadido soporte multilingüe para los mensajes de error, lo que permite que cada registro proporcione un contexto técnico claro al desarrollador, independientemente del idioma.
Hemos avanzado en el desarrollo de un formateador dinámico, capaz de gestionar marcadores de posición personalizados y de adaptarse automáticamente al nivel de gravedad del registro. Cuando nos dimos cuenta de que los formatos genéricos provocaban incoherencias, modificamos la arquitectura para que admitiera un formato específico por nivel. Y, por último, lo validamos todo con una prueba práctica que demostró que Logify funciona en tiempo real, de forma clara, predecible y extensible.
Si has llegado hasta aquí, ya has aprendido a:
- Diseñar una estructura de registros escalable y desacoplada;
- Integrar mensajes de error multilingües basados en el código de MetaTrader;
- Trabajar con marcadores de posición y un formato adaptado al nivel de gravedad;
- Creación de manejadores reutilizables con tus propias configuraciones;
Logify, como cualquier buena herramienta, puede seguir evolucionando. Si se implementan nuevas funciones, se las presentaré en un artículo futuro. Hasta entonces, úsalo, adáptalo y mejóralo.
| Nombre del archivo | Descripción |
|---|---|
| Experts/Logify/LogifyTest.mq5 | Archivo donde probamos las funcionalidades de la biblioteca, que contiene un ejemplo práctico. |
| Include/Logify/Error/Languages/ErrorMessages.XX.mqh | Contiene los mensajes de error de cada idioma, donde XX representa el acrónimo del idioma. |
| Include/Logify/Error/Error.mqh | Estructura de datos para almacenar errores |
| Include/Logify/Error/LogifyError.mqh | Clase para obtener información detallada sobre errores. |
| Include/Logify/Formatter/LogifyFormatter.mqh | Clase responsable de formatear los registros de log, reemplazando los marcadores de posición con valores específicos. |
| Include/Logify/Handlers/LogifyHandler.mqh | Clase base para gestionar los manejadores de registros, incluyendo la configuración de niveles y el envío de registros. |
| Include/Logify/Handlers/LogifyHandlerComment.mqh | Gestor de registros que envía registros formateados directamente al comentario en el gráfico de la terminal en MetaTrader. |
| Include/Logify/Handlers/LogifyHandlerConsole.mqh | Gestor de registros que envía registros formateados directamente a la consola del terminal en MetaTrader. |
| Include/Logify/Handlers/LogifyHandlerDatabase.mqh | Gestor de registros que envía registros formateados a una base de datos (actualmente solo realiza una salida por consola, pero pronto lo guardaremos en una base de datos SQLite real). |
| Include/Logify/Handlers/LogifyHandlerFile.mqh | Controlador de registros que envía registros formateados a un archivo. |
| Include/Logify/Utils/IntervalWatcher.mqh | Comprueba si ha transcurrido un intervalo de tiempo, lo que le permite crear rutinas dentro de la biblioteca. |
| Include/Logify/Logify.mqh | Clase principal para la gestión de registros, integrando niveles, modelos y formato. |
| Include/Logify/LogifyLevel.mqh | Archivo que define los niveles de registro de la biblioteca Logify, lo que permite un control detallado. |
| Include/Logify/LogifyModel.mqh | Estructura que modela los registros de log, incluyendo detalles como nivel, mensaje, marca de tiempo y contexto. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18467
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Simulación de mercado: Position View (XV)
Optimización por Comunidad de Científicos: Teoría
De novato a experto: Períodos del mercado Forex
Introducción a MQL5 (Parte 25): Creación de un asesor experto (EA) que opere a partir de objetos del gráfico (II)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Gracias, me alegro de poder ayudarte. Si tienes alguna sugerencia para mejorar, ¡ponte en contacto conmigo!
He compilado la biblioteca de registro Logify y, a partir de la versión 5100 de MT5, aparecen varios errores de compilación relacionados con los tipos en `CLogifyHandlerDatabase ::Query`. Creo que ya deberías haber resuelto este problema.
Gracias por las sugerencias; las tendré en cuenta en futuros artículos.
Una pequeña idea sobre los idiomas.
Quizá se podrían organizar las noticias originales en inglés de tal forma que se puedan copiar y pegar fácilmente en un traductor (deepl.com o translate.google.com) y volver a pegar el resultado en el programa. Así, cada uno podría configurar el programa en su idioma con toda facilidad.
Deeple reconoce 36 idiomas, y Google, incluso unos 130.