English Русский 中文 Español Deutsch 日本語
preview
A arte de trabalhar com logs (Parte 8): Registros de erros com tradução automática

A arte de trabalhar com logs (Parte 8): Registros de erros com tradução automática

MetaTrader 5 — Exemplos |
51 6
joaopedrodev
joaopedrodev

Introdução

Neste ponto da nossa jornada, já está claro que trabalhar com logs vai muito além de simplesmente registrar eventos. Trata-se de entender com precisão o que seu EA está tentando comunicar em meio ao turbilhão de ticks, decisões e incertezas que fazem parte do trading algorítmico.

Durante o uso diário do Logify, percebi algo que me incomodava: o tratamento de erros ainda era superficial. Mesmo com uma estrutura robusta de formatação, os logs continuavam exibindo apenas o código numérico do erro, sem explicar seu significado. 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

O resultado? Uma mensagem vaga. Sabemos onde o erro ocorreu, mas não sabemos por quê. E quem nunca precisou consultar a documentação para descobrir o significado de dezenas de códigos de erro do MQL5? Eu mesmo fazia isso com bastante frequência: sempre que recebia um código de erro, precisava recorrer à documentação para entender o que realmente havia acontecido. Foi justamente esse incômodo que me deu uma ideia: e se o Logify pudesse interpretar o erro por mim? E se, em vez de exibir apenas o código, ele apresentasse seu significado de forma clara, contextualizada e pronta para ser registrada no log?

Foi assim que surgiu uma nova funcionalidade: agora, basta passar o código de erro diretamente para a biblioteca, que identifica a descrição associada ao código, formata a mensagem e a exibe automaticamente. Com essa melhoria, o exemplo anterior passa a ser exibido assim:

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

Muito mais claro e útil, mas decidi ir além. Um sistema que busca clareza precisa ser flexível não apenas no código, mas também no idioma. Por isso, nesta oitava etapa, o Logify também passa a oferecer suporte multilíngue para mensagens de erro, com tradução automática para 11 idiomas: inglês, português, espanhol, alemão, francês, italiano, russo, turco, chinês, japonês e coreano. Agora, seus logs se comunicam no idioma da sua equipe ou do seu cliente, sem necessidade de configuração manual.

Nesta etapa, você aprenderá a:

  1. Enriquecer os logs de erro com descrições precisas, obtidas diretamente da documentação do MQL5.
  2. Exibir mensagens de erro em vários idiomas, selecionando dinamicamente o mais adequado a cada contexto.
  3. Personalizar a formatação conforme o nível de logging, definindo modelos distintos para erros, avisos e mensagens informativas de acordo com sua criticidade.

Ao final, o Logify estará mais inteligente, acessível e muito mais útil em condições reais de uso. Afinal, no trading algorítmico, cada detalhe importa, e cada segundo gasto para decifrar um erro é um segundo a menos para tomar a próxima decisão certa.


Criando um mecanismo de tratamento de erros

Todo bom sistema precisa de uma base sólida. Por isso, vamos começar definindo uma estrutura para representar os erros. Essa estrutura, que chamaremos de MqlError, será utilizada em todo o sistema para armazenar três informações fundamentais sobre cada erro:

  • Código numérico (code)
  • Constante simbólica
  • Descrição clara (description)

Vamos definir essa estrutura no arquivo <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 = "";
     }
  };
//+------------------------------------------------------------------+

A estrutura é simples, mas suficiente para representar qualquer erro retornado pela plataforma, seja um erro de execução, um erro ao executar uma operação de negociação ou até mesmo um erro definido pelo usuário. Agora que temos onde armazenar essas informações, precisamos preencher as instâncias dessa estrutura com os códigos, as constantes simbólicas e as descrições dos erros.

A MetaQuotes disponibiliza centenas de códigos de erro, cada um com sua constante simbólica e uma descrição em inglês. Mas queremos ir além. Queremos que a biblioteca ofereça mensagens em alemão, espanhol, francês, português, chinês e outros idiomas, permitindo que os logs se adaptem automaticamente ao idioma configurado.

A estratégia consiste em criar um arquivo .mqh para cada idioma, contendo uma função responsável por inicializar um array com todos os erros conhecidos. Os arquivos seguem o padrão de nomenclatura ErrorMessages.xx.mqh, em que xx representa o código do idioma (por exemplo, en para inglês, de para alemão e pt para português).

Veja como é o arquivo em 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";
  }
//+------------------------------------------------------------------+

A função InitializeErrorsEnglish() recebe um array de estruturas MqlError e o preenche com todos os erros conhecidos, usando as descrições em inglês. Repetimos esse padrão para os demais idiomas: alemão, espanhol, francês, italiano, japonês, coreano, português, russo, turco e chinês. Todos esses arquivos ficam armazenados na pasta <Include/Logify/Error/Languages>. Ao todo, são 11 arquivos de idioma, cada um com 274 registros. E sim, foi um trabalho minucioso, mas agora basta incluir a biblioteca para ter acesso a todas essas descrições traduzidas. Lembre-se de que o código completo está disponível no final do artigo.

Com todos esses dados estruturados, precisamos de uma interface para consultá-los. É aqui que entra a classe CLogifyError, responsável por carregar as informações dos erros no idioma desejado e retornar os dados completos de qualquer código de erro consultado.

A interface da classe é simples:

//+------------------------------------------------------------------+
//| 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)
  {
  }
//+------------------------------------------------------------------+

Ao criar uma instância, a classe carrega as mensagens de erro em inglês por padrão. Você pode alterar o idioma a qualquer momento usando SetLanguage(), e a biblioteca recarregará os dados no idioma selecionado.

O método Error(int code) é o núcleo da classe. Ele percorre o array de erros em busca do código informado e retorna a estrutura MqlError correspondente. Se o código não for encontrado, o método retorna uma estrutura MqlError com informações genéricas como resposta padrão. Além disso, identifica automaticamente se o código pertence ao intervalo reservado para erros definidos pelo usuário (de ERR_USER_ERROR_FIRST a ERR_USER_ERROR_LAST) e também fornece uma resposta para os códigos pertencentes a esse intervalo.

//+------------------------------------------------------------------+
//| 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 fim, vamos adicionar essa estrutura de erro ao modelo de dados de log MqlLogifyModel, para que possamos acessar as informações do erro diretamente na estrutura de cada 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;
     }
  };
//+------------------------------------------------------------------+

Com essas alterações, a estrutura da nossa biblioteca fica assim:


Integração dos erros à classe principal

Já temos uma estrutura robusta para representar os erros, arquivos específicos para cada idioma, contendo as descrições dos erros, e uma classe responsável por recuperar essas mensagens conforme o idioma selecionado. No entanto, esses componentes ainda estão isolados, sem integração com o núcleo da biblioteca, a classe CLogify.

Chegou a hora de integrar o sistema de erros ao núcleo do Logify, permitindo que cada registro de log inclua, quando necessário, uma descrição clara do erro no idioma selecionado. Para isso, vamos seguir três etapas: criar uma instância da classe de erros, adaptar o método responsável por adicionar registros (Append) e, por fim, configurar o formatador para reconhecer os novos placeholders {err_code}, {err_constant} и {err_description}.

1. Incorporando o tratamento de erros ao núcleo do Logify

A classe CLogify é responsável por integrar a formatação, o processamento e o envio dos registros de log. Portanto, o primeiro passo é permitir que ela acesse diretamente nosso sistema de erros. Para isso, basta incluir o arquivo LogifyError.mqh e criar uma instância da classe CLogifyError como membro privado de CLogify. Assim, o sistema de erros estará sempre disponível internamente, sem que o usuário da biblioteca precise se preocupar com seu gerenciamento. Essa pequena alteração já abre possibilidades importantes. Agora, quando ocorrer um erro, podemos usar seu código para consultar rapidamente a descrição e a constante simbólica correspondentes.

Como o idioma padrão de CLogifyError é o inglês, faz sentido permitir que o usuário escolha o idioma mais adequado. Por isso, adicionamos dois métodos simples. Veja como ficou o código:

#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. Adicionando o código de erro ao método Append

Até agora, o método Append() recebia a mensagem principal, argumentos adicionais, origem, nome do arquivo, linha, função e outras informações, mas não o código do erro propriamente dito. Se queremos que o log inclua informações sobre as falhas, precisamos permitir que esse código também seja informado, ainda que opcionalmente. Por isso, adicionamos o parâmetro code_error ao final da assinatura do 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);

Com essa alteração, quem não precisa informar um código de erro pode continuar chamando Append() da mesma forma. Já quem deseja incluí-lo precisa apenas acrescentar o código ao final da chamada. Essa abordagem preserva a compatibilidade com o código existente.

Agora que temos o código de erro, utilizamos o método Error() da classe CLogifyError para recuperar a estrutura MqlError correspondente:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Dessa forma, o objeto MqlLogifyModel passa a armazenar não apenas as informações do log, mas também os detalhes do erro que deu origem ao registro. Resta adicionar versões sobrecarregadas dos métodos Error() e Fatal(), mantendo as versões existentes para evitar problemas de compatibilidade, já que nem sempre o usuário da biblioteca precisa informar um código de erro. Veja como ficou o 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. Formatação das mensagens com placeholders de erro

Até aqui, as informações do erro já eram carregadas, mas ainda não apareciam no log formatado. Para exibi-las, precisamos ampliar o sistema de placeholders do CLogifyFormatter, incluindo os campos de erro. A lógica é simples, mas elegante: exibimos os detalhes do erro apenas quando o nível de logging é ERROR ou superior. Logs informativos ou de depuração não precisam desse nível de detalhamento. Nosso objetivo é oferecer clareza, não sobrecarregar os registros com informações desnecessárias.

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Para isso, criamos três novos placeholders:

  • {err_code}: exibe o código exato do erro (por exemplo, 10006).
  • {err_constant}: exibe a constante correspondente (por exemplo, TRADE_RETCODE_REJECT).
  • {err_description}: exibe uma descrição clara do erro (por exemplo, Request rejected).

Imagine que tenhamos definido o formato global desta maneira:

"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})"

Em teoria, isso deveria produzir algo assim:

2025.06.09 14:22:15 [ERROR] Failed to send order (10016 TRADE_RETCODE_REJECT: Request rejected)

E, de fato, para os níveis ERROR e FATAL, funciona perfeitamente. Mas o que acontece quando usamos o mesmo formato para logs DEBUG, INFO ou ALERT? O resultado será mais ou menos assim:

2025.06.09 14:22:15 [INFO] Operation completed successfully (  : )

Ou pior: dependendo da formatação, os registros podem ficar visualmente desalinhados. Mesmo deixando vazios os valores associados aos placeholders de erro nos níveis menos críticos, o problema persiste: o formato foi definido supondo que {err_code}, {err_constant} e {err_description} sempre teriam valores. Quando isso não acontece, o log fica... estranho e incompleto. Temos, portanto, uma inconsistência semântica: o mesmo modelo de formatação é aplicado a mensagens com contextos completamente diferentes.

A solução não é tentar ajustar os dados, mas reconhecer que cada nível de logging representa um tipo específico de mensagem, com suas próprias necessidades de formatação.

Por isso, em vez de manter um formato único no CLogifyFormatter, passamos a definir um formato específico para cada nível (DEBUG, INFO, ALERT, ERROR e FATAL). Isso nos permite configurar, por exemplo:

  • DEBUG: exibir a função, o arquivo e a linha.
  • INFO: manter apenas o essencial, informando o que aconteceu.
  • ALERT: incluir informações de contexto, como a origem e os argumentos.
  • ERROR: apresentar os detalhes do erro.
  • FATAL: fornecer o máximo de informações possível.


Implementação de múltiplos formatos

Primeiro, alteramos a forma como o formato é armazenado: em vez de uma única string, passamos a usar um array com cinco posições, uma para cada nível de logging.

Código anterior Novo 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];
  };

Substituímos m_log_format por um array de strings, com uma posição para cada nível de logging. Agora temos cinco formatos distintos, que podem ser configurados de acordo com o tipo de mensagem registrada.

Com os formatos separados por nível, precisamos de uma maneira prática de configurá-los. Para isso, criamos duas versões do método SetFormat: uma aplica o mesmo formato a todos os níveis, enquanto a outra permite configurar individualmente um nível 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;
  }
//+------------------------------------------------------------------+

Essa implementação não apenas facilita o uso, mas também oferece flexibilidade sem abrir mão da precisão. Para quem prefere simplicidade, basta uma única chamada a SetFormat(...). Já quem deseja maior controle pode usar SetFormat(level, format) para configurar cada nível conforme a necessidade.

Depois de separar os formatos por nível, precisamos adaptar o método de formatação para utilizar essa nova estrutura. Antes, usávamos a variável m_log_format para obter o modelo de formatação da mensagem. Agora, buscamos o formato diretamente no array, usando o nível de logging como índice.

Código anterior Novo código
string CLogifyFormatter::FormatLog(MqlLogifyModel &data)
  {
   string formated = m_log_format;
   ...
  }
string CLogifyFormatter::Format(MqlLogifyModel &data)
  {
   string formated = m_format[data.level];
   ...
  }

Isso resolve o problema de forma limpa, extensível e previsível. Sem verificações desnecessárias nem soluções alternativas para ocultar placeholders vazios. Cada nível passa a ter um formato definido exatamente para as informações que precisa apresentar.


Testes

Chegamos à etapa em que tudo o que desenvolvemos ganha vida: testar a implementação na prática e observar o comportamento do sistema em diferentes cenários e níveis de logging. No mesmo arquivo utilizado para os testes do artigo anterior, LogifyTest.mqh, preparamos os seguintes componentes:

  • Um handler visual usando Comment(), com borda, título e limite de linhas.
  • Um handler de console usando Print(), para registrar mensagens no log interno da plataforma.
  • Um único formatador compartilhado pelos dois handlers.
  • Quatro mensagens de log em diferentes níveis: duas DEBUG, uma INFO e uma ERROR com código de erro.
//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Ao executar esse código, o console do MetaTrader exibirá as mensagens formatadas exatamente como esperávamos. O resultado mostra claramente que os níveis menos críticos, como DEBUG e INFO, utilizam formatos mais concisos, enquanto ERROR apresenta informações mais detalhadas, incluindo os dados do erro.

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 como o último registro contém um bloco adicional entre colchetes, com a constante simbólica, o código numérico e uma descrição clara do erro. Tudo isso sem precisar adicionar lógica condicional desnecessária ao código do EA. Cada nível de logging utiliza seu próprio formato, e os placeholders são incluídos apenas nos contextos em que fazem sentido.


Conclusão

Neste artigo, avançamos no desenvolvimento da biblioteca Logify. No primeiro artigo, começamos estruturando o modelo de dados e, em seguida, criamos handlers flexíveis para diferentes canais de saída. Agora, adicionamos suporte multilíngue para mensagens de erro, permitindo que cada registro apresente ao desenvolvedor informações técnicas claras sobre o erro, no idioma selecionado.

Também aprimoramos o formatador dinâmico, capaz de processar placeholders personalizados e ajustar automaticamente a formatação das mensagens conforme o nível de logging. Ao percebermos que um formato único gerava inconsistências, evoluímos a arquitetura para permitir um formato específico para cada nível. Por fim, validamos essas melhorias com um teste prático, demonstrando o funcionamento do Logify em tempo real, de maneira limpa, previsível e extensível.

Se você chegou até aqui, aprendeu a:

  • Projetar uma estrutura de logging escalável e com baixo acoplamento;
  • Integrar mensagens de erro multilíngues com base nos códigos de erro do MetaTrader;
  • Utilizar placeholders e personalizar a formatação conforme o nível de logging;
  • Criar handlers reutilizáveis com configurações próprias.

Como toda boa ferramenta, o Logify pode continuar evoluindo. Se novas funcionalidades forem implementadas, apresentarei essas melhorias em um próximo artigo. Até lá, aproveite a biblioteca, adapte-a às suas necessidades e continue aprimorando-a.


Nome do arquivo Descrição
Experts/Logify/LogifyTest.mq5
Arquivo utilizado para testar as funcionalidades da biblioteca, contendo um exemplo prático.
Include/Logify/Error/Languages/ErrorMessages.XX.mqh Arquivo que contém as mensagens de erro de cada idioma, em que XX representa o código do idioma.
Include/Logify/Error/Error.mqh
Estrutura de dados para armazenar as informações dos erros.
Include/Logify/Error/LogifyError.mqh
Classe responsável por recuperar informações detalhadas sobre os erros.
Include/Logify/Formatter/LogifyFormatter.mqh
Classe responsável por formatar os registros de log, substituindo os placeholders pelos valores correspondentes.
Include/Logify/Handlers/LogifyHandler.mqh
Classe base para o gerenciamento dos handlers de log, incluindo a configuração do nível de logging e o envio das mensagens.
Include/Logify/Handlers/LogifyHandlerComment.mqh
Handler de log que exibe as mensagens formatadas diretamente no gráfico do MetaTrader, por meio de Comment().
Include/Logify/Handlers/LogifyHandlerConsole.mqh
Handler de log que envia as mensagens formatadas diretamente para o console do terminal MetaTrader.
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
Handler de log destinado a armazenar as mensagens formatadas em um banco de dados. Atualmente, apenas imprime as mensagens, mas em breve passará a armazená-las em um banco de dados SQLite.
Include/Logify/Handlers/LogifyHandlerFile.mqh
Handler de log que grava as mensagens formatadas em um arquivo.
Include/Logify/Utils/IntervalWatcher.mqh
Verifica se um intervalo de tempo definido já decorreu, permitindo implementar rotinas na biblioteca que dependem dessa verificação.
Include/Logify/Logify.mqh Classe principal responsável pelo gerenciamento dos logs, integrando os níveis de logging, os modelos de dados e a formatação.
Include/Logify/LogifyLevel.mqh Arquivo que define os níveis de logging da biblioteca Logify, permitindo um controle detalhado dos registros.
Include/Logify/LogifyModel.mqh Estrutura que representa os registros de log, incluindo informações como nível de logging, mensagem, marca de tempo e contexto.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18467

Arquivos anexados |
Logify.zip (151.36 KB)
Últimos Comentários | Ir para discussão (6)
joaopedrodev
joaopedrodev | 20 jun. 2025 em 12:44
Obrigado, fico feliz em ajudar. Se tiver alguma sugestão de melhoria, entre em contato comigo!
hini
hini | 20 jun. 2025 em 17:42
joaopedrodev #:
Obrigado, fico feliz em ajudar. Se tiver alguma sugestão de melhoria, entre em contato comigo!
O que devo fazer se o registro for muito frequente, mas eu quiser evitar impressões repetidas? Por exemplo, quando um sinal de abertura de negociação é detectado, mas o spread é muito grande e persiste por dezenas de segundos — se o registro for usado, ele seria exibido pelo menos dezenas ou até centenas de vezes. Como essa situação pode ser resolvida? Sei que uma solução é usar uma variável para garantir que o registro seja exibido apenas uma vez, mas seria melhor se a própria biblioteca de registro pudesse lidar com isso. Você poderia dar algumas sugestões?
hini
hini | 20 jun. 2025 em 17:54

Compilei a biblioteca de registro Logify e, a partir da versão 5100 do MT5, surgiram vários erros de compilação relacionados a tipos na função `CLogifyHandlerDatabase ::Query`. Acredito que vocês já devam ter resolvido esse problema.
joaopedrodev
joaopedrodev | 23 jun. 2025 em 13:24
Obrigado pelas sugestões; abordarei esse assunto em artigos futuros.
Carl Schreiber
Carl Schreiber | 26 set. 2025 em 20:01
joaopedrodev #:
Obrigado pelas sugestões; vou abordá-las em artigos futuros.

Uma pequena sugestão sobre os idiomas.

Talvez seja possível organizar as notícias originais em inglês de forma que possam ser facilmente copiadas e coladas em um tradutor (deepl.com ou translate.google.com) e o resultado seja inserido de volta no programa. Assim, qualquer pessoa poderá configurar o programa para o seu idioma com muita facilidade.

O Deepl reconhece 36 idiomas, e o Google, cerca de 130.

Repensando as estratégias clássicas (Parte 13): Atualização da estratégia de cruzamento de médias móveis (Parte 2) Repensando as estratégias clássicas (Parte 13): Atualização da estratégia de cruzamento de médias móveis (Parte 2)
Vamos tentar incorporar novas melhorias à nossa estratégia de cruzamento de médias móveis, buscando reduzir a defasagem dos sinais e aumentar sua confiabilidade por meio de análises adicionais dos dados. Como sabemos, projetar dados em um espaço multidimensional pode, em alguns casos, melhorar o desempenho dos modelos de machine learning. Vamos entender o que isso significa na prática para nós, traders, e como podemos aplicar esse princípio no terminal MetaTrader 5.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 71): Utilização de padrões MACD e OBV Recursos do Assistente MQL5 que você precisa conhecer (Parte 71): Utilização de padrões MACD e OBV
O oscilador de convergência e divergência de médias móveis (Moving Average Convergence Divergence, MACD) e o indicador de volume em balanço (On-Balance Volume, OBV) formam mais uma combinação de indicadores que pode ser utilizada em um EA MQL5. Como é habitual nesta série de artigos, os dois indicadores se complementam: o MACD confirma as tendências, enquanto o OBV verifica se o comportamento do volume sustenta esses movimentos. Mais uma vez, utilizamos o Assistente MQL5 para criar padrões e avaliar o potencial dessa combinação de indicadores.
Desenvolvimento de um Kit de Ferramentas para Análise da Ação do Preço (Parte 25): Rompimento de Fractais com EMA Dupla Desenvolvimento de um Kit de Ferramentas para Análise da Ação do Preço (Parte 25): Rompimento de Fractais com EMA Dupla
A análise da ação do preço é uma abordagem fundamental para identificar oportunidades de negociação lucrativas. No entanto, monitorar manualmente os movimentos e padrões de preços pode ser uma tarefa desafiadora e demorada. Para resolver esse problema, estamos desenvolvendo ferramentas que analisam automaticamente a ação do preço, fornecendo sinais oportunos sempre que possíveis oportunidades são identificadas. Este artigo apresenta uma ferramenta robusta que combina rompimentos de fractais com as EMAs de 14 e 200 períodos para gerar sinais de negociação confiáveis, ajudando os traders a tomar decisões fundamentadas com maior confiança.
MetaTrader 5 Global Optimizer: Clusterização MetaTrader 5 Global Optimizer: Clusterização
Nesta terceira parte, o Global Optimizer deixa de escolher apenas o melhor passe do ISS e passa a identificar regiões paramétricas estáveis. Os resultados são analisados por clusterização, medóides e vizinhança, com suporte de execuções genéticas e possível refinamento local antes da validação OOS. O objetivo é reduzir a dependência de picos isolados e selecionar candidatos mais robustos.