A arte de trabalhar com logs (Parte 9): aplicando o padrão Builder e definindo configurações padrão
Introdução
Desde que comecei a utilizar a Logify em diversos projetos pessoais e profissionais, percebi rapidamente que a maior dificuldade não estava na confiabilidade nem nos recursos da biblioteca, mas em sua configuração. A Logify é uma ferramenta poderosa para gerenciar logs em EAs (Expert Advisors), oferecendo diversos handlers, níveis de log, formatos personalizáveis, suporte a diferentes idiomas e muito mais. No entanto, tudo isso exigia que o usuário configurasse manualmente cada handler, formatador e parâmetro. Embora esse procedimento seja aceitável em projetos pequenos, ele rapidamente se torna uma tarefa repetitiva, cansativa e sujeita a erros à medida que aumenta o número de EAs e projetos.
Imagine ter que reproduzir o mesmo conjunto complexo de configurações para cada EA: criar handlers específicos para exibir comentários no gráfico, enviar mensagens ao console e gravar registros em arquivos e bancos de dados; definir os níveis mínimos de log para cada handler; estabelecer formatos específicos para mensagens de erro, informações de depuração, notificações e assim por diante. Embora necessária, cada uma dessas etapas acaba exigindo um código extenso, cheio de detalhes e pouco intuitivo, prejudicando o fluxo de desenvolvimento. Isso pode até desestimular o uso da Logify, apesar de sua excelente capacidade de gerenciar logs durante a execução.
Essa constatação me levou a uma reflexão: como simplificar a configuração e facilitar a vida do usuário sem abrir mão da flexibilidade e das possibilidades de personalização? Foi então que surgiu a ideia de criar uma classe Builder para a Logify, capaz de reunir toda a configuração em uma sequência fluida de chamadas encadeadas. Com métodos intuitivos, seria possível criar handlers com configurações predefinidas adequadas e realizar ajustes pontuais de maneira rápida e precisa. O objetivo é reduzir dezenas de linhas de configuração a algumas chamadas de métodos, como se estivéssemos escrevendo uma descrição clara do que queremos obter, em vez de montar tudo manualmente.
Neste artigo, mostrarei como implementei essas melhorias. Apresentarei a classe Builder, explicando sua estrutura e como utilizá-la. Em seguida, demonstrarei, com exemplos práticos, como configurar a Logify.
Entendendo o padrão Builder: simplificando a criação de objetos complexos
Antes de entrarmos na implementação da nossa classe CLogifyBuilder, é importante compreender o princípio por trás do padrão Builder que vamos utilizar.
Na prática, o Builder é um padrão de projeto com um objetivo bem definido: simplificar a criação de objetos complexos, especialmente quando exigem várias etapas de configuração ou oferecem muitas opções de personalização. A ideia é separar a construção do objeto de sua representação final, permitindo que uma mesma estrutura de montagem produza diferentes versões de objetos prontos para uso.
Vamos considerar um exemplo simples: imagine que você vai montar um carro. É possível escolher o modelo, a cor, o tipo de motor, a transmissão, os opcionais, o tamanho das rodas, o acabamento interno e dezenas de outros detalhes. Fazer tudo isso diretamente no código, passando centenas de argumentos para um construtor, seria pouco prático e difícil de manter.
É justamente nesse tipo de situação que o padrão Builder se destaca. Ele divide a criação do objeto em uma sequência de chamadas de métodos encadeados, em que cada método configura uma parte específica do objeto.
Ao final, basta chamar o método .Build() ou outro equivalente para obter o objeto pronto para uso. Essa abordagem oferece três vantagens principais:
- Código claro e sequencial: a criação do objeto segue uma sequência lógica de etapas, quase como uma descrição do resultado que queremos obter.
- Redução de erros: como cada etapa tem uma finalidade específica, fica muito mais fácil identificar e corrigir configurações incorretas.
- Flexibilidade e reutilização: o mesmo Builder pode ser reutilizado para criar diferentes versões de um objeto com pequenas alterações.
Aplicando o padrão Builder à Logify
No caso da Logify, criar uma instância do logger (CLogify) envolve adicionar diversos handlers (por exemplo, para o console, comentários no gráfico e arquivos), configurar os níveis mínimos de log, definir formatadores específicos para cada handler e ajustar parâmetros como o tamanho do painel ou o estilo da borda. Repetir todas essas configurações manualmente, linha por linha, acaba tornando o código extenso e difícil de manter.
A aplicação do padrão Builder resolve esse problema. Em vez de exigir que o usuário monte manualmente cada componente, oferecemos uma interface mais intuitiva, como esta:
CLogify *logify = logify .Create() .AddHandlerComment() .SetTitle("My Logger") .SetSize(5) .Done() .AddHandlerConsole() .Done() .Build();
Observe como a sequência de chamadas é clara e expressiva. O método .Create() inicia a construção do objeto, cada .AddHandlerXXX() permite configurar um handler específico, os métodos .SetX() ajustam seus parâmetros, .Done() conclui a configuração do handler e .Build() retorna uma instância de CLogify pronta para uso.
É aí que está a principal vantagem do padrão Builder: ele permite que o desenvolvedor descreva o resultado desejado sem precisar lidar diretamente com os detalhes internos da implementação.
Agora que entendemos por que adotamos esse padrão e como ele torna a Logify mais prática e escalável, vamos examinar a implementação da classe e aprender a utilizá-la em situações reais.
Classes Builder especializadas para cada handler
Criamos um novo arquivo <Include/Logify/LogifyBuilder.mqh>. Nele está definida a classe CLogifyBuilder, que mantém uma instância de CLogify em um campo privado. Essa instância será configurada ao longo da construção e, ao final, retornada ao usuário.
//+------------------------------------------------------------------+ //| LogifyBuilder.mqh | //| joaopedrodev | //| https://www.mql5.com/en/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/en/users/joaopedrodev" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "Logify.mqh" //+------------------------------------------------------------------+ //| class : CLogifyBuilder | //| | //| [PROPERTY] | //| Name : LogifyBuilder | //| Heritage : No heritage | //| Description : Build CLogify objects, following the Builder design| //| pattern. | //| | //+------------------------------------------------------------------+ class CLogifyBuilder { private: CLogify *m_logify; public: CLogifyBuilder(void) ~CLogifyBuilder(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyBuilder::CLogifyBuilder(void) { m_logify = new CLogify(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyBuilder::~CLogifyBuilder(void) { } //+------------------------------------------------------------------+
A classe CLogifyBuilder é o componente central da criação do objeto CLogify, mas delega a configuração de cada tipo de handler a classes Builder especializadas: CLogifyHandlerCommentBuilder, CLogifyHandlerConsoleBuilder, CLogifyHandlerDatabaseBuilder e CLogifyHandlerFileBuilder.
Cada uma dessas classes encapsula os detalhes de configuração de um tipo específico de saída de logs. Essa divisão evita misturar responsabilidades e mantém a lógica central de construção simples e modular. Vamos examinar sua estrutura usando o ConsoleBuilder como exemplo.
CLogifyHandlerConsoleBuilder
A classe começa com uma estrutura básica para dar suporte a uma interface fluente (fluent API). Seu construtor recebe um ponteiro para o Builder principal (CLogifyBuilder*) e mantém uma referência ao contexto de construção. Assim, após configurar o handler, é possível retornar a esse contexto por meio do método Done():
//+------------------------------------------------------------------+ //| class : CLogifyHandlerConsoleBuilder | //| | //| [PROPERTY] | //| Name : LogifyHandlerConsoleBuilder | //| Heritage : No heritage | //| Description : Console handler constructor. | //| | //+------------------------------------------------------------------+ class CLogifyHandlerConsoleBuilder { private: CLogifyBuilder *m_parent; public: CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify); ~CLogifyHandlerConsoleBuilder(void); CLogifyBuilder *Done(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify) { m_parent = logify; }; //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::~CLogifyHandlerConsoleBuilder(void) { } //+------------------------------------------------------------------+ //| Finalizes the handler configuration. | //+------------------------------------------------------------------+ CLogifyBuilder *CLogifyHandlerConsoleBuilder::Done(void) { m_parent.AddHandler(GetPointer(m_handler)); delete GetPointer(this); return(m_parent); } //+------------------------------------------------------------------+
O método Done() permite retornar ao Builder principal. Ele adiciona o handler à instância de CLogify e destrói o Builder intermediário. Dessa forma, mantém o encadeamento da construção e evita que objetos desnecessários permaneçam na memória.
Todas as classes Builder especializadas seguem a mesma estrutura básica:
- CLogifyBuilder *m_parent: referência ao Builder pai, utilizada para retornar a ele por meio do método Done().
- CLogifyFormatter *m_formatter: instância do formatador que será associada ao handler.
- CLogifyHandlerX *m_handler: o próprio handler que está sendo configurado.
As classes Builder mais complexas (como as destinadas a arquivos ou bancos de dados) também utilizam uma estrutura interna de configuração (MqlLogifyHandleXConfig), que armazena temporariamente os valores dos parâmetros até que o handler esteja pronto para ser registrado.
Essa separação entre os dados de configuração e sua aplicação ao handler permite validar parâmetros, utilizar configurações predefinidas (presets) e combinar opções sem complicar a lógica do próprio handler.
Classe Builder completa para o console (Console Builder)
A seguir, apresentamos o Builder com os métodos de configuração já implementados:
//+------------------------------------------------------------------+ //| class : CLogifyHandlerConsoleBuilder | //| | //| [PROPERTY] | //| Name : LogifyHandlerConsoleBuilder | //| Heritage : No heritage | //| Description : Console handler constructor. | //| | //+------------------------------------------------------------------+ class CLogifyHandlerConsoleBuilder { private: CLogifyBuilder *m_parent; CLogifyFormatter *m_formatter; CLogifyHandlerConsole *m_handler; public: CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify); ~CLogifyHandlerConsoleBuilder(void); CLogifyHandlerConsoleBuilder *SetLevel(ENUM_LOG_LEVEL level); CLogifyHandlerConsoleBuilder *SetFormatter(string format); CLogifyHandlerConsoleBuilder *SetFormatter(ENUM_LOG_LEVEL level, string format); CLogifyBuilder *Done(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify) { m_parent = logify; m_formatter = new CLogifyFormatter(); m_handler = new CLogifyHandlerConsole(); m_handler.SetFormatter(GetPointer(m_formatter)); }; //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::~CLogifyHandlerConsoleBuilder(void) { } //+------------------------------------------------------------------+ //| Sets the log level for the handler. | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetLevel(ENUM_LOG_LEVEL level) { m_handler.SetLevel(level); return(GetPointer(this)); } //+------------------------------------------------------------------+ //| Sets the default format string for the formatter. | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetFormatter(string format) { m_formatter.SetFormat(format); m_handler.SetFormatter(GetPointer(m_formatter)); return(GetPointer(this)); } //+------------------------------------------------------------------+ //| Sets a log-level-specific format for the formatter. | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetFormatter(ENUM_LOG_LEVEL level, string format) { m_formatter.SetFormat(level,format); m_handler.SetFormatter(GetPointer(m_formatter)); return(GetPointer(this)); } //+------------------------------------------------------------------+ //| Finalizes the handler configuration. | //+------------------------------------------------------------------+ CLogifyBuilder *CLogifyHandlerConsoleBuilder::Done(void) { m_parent.AddHandler(GetPointer(m_handler)); delete GetPointer(this); return(m_parent); } //+------------------------------------------------------------------+
Com isso concluído, podemos passar às demais classes Builder especializadas.
Outras classes Builder especializadas
CLogifyHandlerCommentBuilder
Responsável por configurar o handler que exibe mensagens diretamente no gráfico, por meio da função Comment(), utilizando a estrutura MqlLogifyHandleCommentConfig.
Permite definir:
- SetSize(int): quantidade de mensagens exibidas.
- SetFrameStyle(ENUM_LOG_FRAME_STYLE): estilo da borda ao redor da área de logs.
- SetDirection(ENUM_LOG_DIRECTION): orientação vertical ou horizontal.
- SetTitle(string): título fixo na parte superior da área de logs.
Também oferece os métodos SetLevel() e SetFormatter(), que podem ser aplicados globalmente ou a um nível específico de log. A configuração é concluída com a chamada ao método Done().
CLogifyHandlerDatabaseBuilder
Configura o handler responsável por armazenar dados em um banco de dados (por enquanto, em uma estrutura binária). Utiliza a estrutura MqlLogifyHandleDatabaseConfig e oferece os seguintes métodos:
- SetDirectory(string)
- SetBaseFileName(string)
- SetMessagesPerFlush(int)
Sua estrutura segue o mesmo padrão das demais classes Builder, garantindo consistência.
CLogifyHandlerFileBuilder
É a classe mais completa de todas. Configura a gravação de logs em arquivos (.log, .txt etc.) por meio da estrutura MqlLogifyHandleFileConfig.
Opções disponíveis:
- SetDirectory(), SetFilename(), SetFileExtension()
- SetRotationMode(): rotação por data, por tamanho ou manual
- SetMessagesPerFlush()
- SetCodepage(): por exemplo, CP_UTF8
- SetFileSizeMB(), SetMaxFileCount()
Além disso, oferece três métodos auxiliares com configurações predefinidas (presets):
- ConfigNoRotation(): sem rotação
- ConfigDateRotation(): rotação por data
- ConfigSizeRotation(): rotação por tamanho
Esses métodos reúnem toda a configuração necessária em uma única chamada, o que é especialmente útil para configurações utilizadas com frequência. As classes Builder dos demais handlers seguem a mesma estrutura, com opções de configuração específicas para cada tipo. Os códigos completos estão disponíveis nos arquivos anexos.
A classe principal: CLogifyBuilder
Agora que vimos como funcionam as classes Builder especializadas, é hora de conhecer o componente responsável por coordená-las: a classe CLogifyBuilder.
Ela é responsável por criar e manter a instância principal de CLogify, à qual todos os handlers serão adicionados. No entanto, em vez de configurar cada componente diretamente, delega essa tarefa às classes Builder especializadas, cada uma responsável por um tipo específico de handler. Dessa forma, CLogifyBuilder atua como um coordenador, permitindo que o logger seja construído de maneira modular.
A seguir, apresentamos a implementação completa da classe:
//+------------------------------------------------------------------+ //| class : CLogifyBuilder | //| | //| [PROPERTY] | //| Name : LogifyBuilder | //| Heritage : No heritage | //| Description : Build CLogify objects, following the Builder design| //| pattern. | //| | //+------------------------------------------------------------------+ class CLogifyBuilder { private: CLogify *m_logify; public: CLogifyBuilder(void); ~CLogifyBuilder(void); CLogifyBuilder *UseLanguage(ENUM_LANGUAGE language); //--- Starts configuration handlers CLogifyHandlerCommentBuilder *AddHandlerComment(void); CLogifyHandlerConsoleBuilder *AddHandlerConsole(void); CLogifyHandlerDatabaseBuilder *AddHandlerDatabase(void); CLogifyHandlerFileBuilder *AddHandlerFile(void); void AddHandler(CLogifyHandler *handler); CLogify *Build(void); }; //+------------------------------------------------------------------+
Essa classe reúne várias funcionalidades importantes:
- UseLanguage(ENUM_LANGUAGE language): permite definir o idioma principal do sistema de logs. Essa escolha afeta as mensagens internas de erro (por meio de CLogifyError) e a formatação que depende das configurações de idioma.
- AddHandlerX(): são os pontos de entrada para a configuração dos handlers. Cada método (AddHandlerConsole(), AddHandlerFile() etc.) cria uma instância do Builder especializado correspondente e passa a ele um ponteiro para o próprio CLogifyBuilder (this). Assim, após concluir a configuração, o método Done() permite retornar ao Builder principal.
- AddHandler(CLogifyHandler *handler): é chamado internamente pelas classes Builder especializadas ao final da configuração, por meio do método Done(). Sua função é registrar o handler já configurado na instância de CLogify que está sendo construída.
- Build(): conclui a construção, libera a memória ocupada pelo Builder usando delete GetPointer(this) e retorna o logger pronto para uso. Isso reforça a ideia de que a instância do Builder existe apenas durante a construção do objeto.
Com essa estrutura, temos uma implementação completa do padrão Builder: modular, clara e extensível. O código completo está disponível nos arquivos anexos.
Um ponto de entrada mais elegante
Embora já tenhamos o construtor de CLogifyBuilder, exigir que o usuário o chame diretamente com new CLogifyBuilder() não é a maneira mais clara e intuitiva de iniciar a criação de um logger.
Por isso, adicionamos um método estático chamado Create() à classe CLogify:
//+------------------------------------------------------------------+ //| Returns an instance of the builder | //+------------------------------------------------------------------+ #include "LogifyBuilder.mqh" CLogifyBuilder *CLogify::Create(void) { return(new CLogifyBuilder()); } //+------------------------------------------------------------------+
Esse método é declarado na classe CLogify da seguinte forma:
class CLogify { public: static CLogifyBuilder *Create(void); };
O método Create() é estático por vários motivos:
- Ele pertence à classe, e não a uma instância: quando queremos começar a criar uma instância de CLogify, ainda não temos uma instância dessa classe.
- Não depende de nenhum estado interno: sua única função é criar e retornar um Builder.
- Evita o acoplamento direto à classe Builder: se a implementação do Builder mudar no futuro, poderemos manter a mesma interface estática em CLogify, preservando a compatibilidade com o código existente.
Configurações padrão
À medida que a biblioteca Logify evolui, precisamos considerar uma situação bastante comum: o usuário quer começar a registrar logs rapidamente, sem precisar configurar nada. Ele não está preocupado com handlers, idiomas, formatos ou diretórios; precisa apenas visualizar as mensagens durante o desenvolvimento ou os testes. Para atender a essa necessidade, implementamos o método EnsureDefaultHandler().
Esse método funciona como um mecanismo automático de fallback: se nenhum handler tiver sido configurado explicitamente, ele adiciona dois handlers básicos e funcionais, um para o console (Console) e outro para exibir comentários no gráfico (Comment). Ambos utilizam recursos nativos do MQL5 e permitem visualizar as mensagens imediatamente.
void CLogify::EnsureDefaultHandler() { //--- Check if there is no handler if(this.SizeHandlers() == 0) { this.AddHandler(new CLogifyHandlerConsole()); this.AddHandler(new CLogifyHandlerComment()); } }
A chamada é feita dentro do método Append(), e não no construtor:
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(); // (continues...) }
Essa decisão tem um motivo importante: se adicionássemos os handlers padrão no construtor, eles seriam mantidos junto com qualquer configuração de handlers feita posteriormente. Como resultado, o logger poderia acabar com handlers duplicados ou indesejados. Isso seria especialmente problemático quando o usuário quisesse direcionar todos os logs para um único destino, como um arquivo, banco de dados ou servidor remoto.
Ao transferir essa lógica para o método Append(), mantemos o controle nas mãos do desenvolvedor. O funcionamento é o seguinte:
- Se nenhum handler estiver configurado, o método EnsureDefaultHandler() ativa os dois handlers padrão na primeira chamada de Append().
- Se pelo menos um handler tiver sido adicionado manualmente, o método não realiza nenhuma ação.
- O comportamento padrão garante que as mensagens sejam exibidas, mas não interfere quando existe uma configuração explícita.
Essa abordagem oferece um equilíbrio entre praticidade e previsibilidade. Para quem precisa de uma solução rápida, que funcione imediatamente, a configuração é feita automaticamente. Já para quem deseja um controle mais detalhado, a biblioteca respeita integralmente as escolhas do desenvolvedor.
Com isso, a Logify passa a oferecer uma experiência plug-and-play, sem abrir mão das possibilidades de personalização. É um avanço importante para facilitar sua adoção tanto por iniciantes quanto por equipes que precisam seguir padrões mais rigorosos de registro de logs.
Testes
Vamos comparar como era a utilização da biblioteca antes e depois das melhorias implementadas neste artigo.
Antes, configurar o registro de logs exigia uma série de etapas manuais: instanciar objetos, definir níveis de log, criar formatadores, preencher estruturas de configuração e adicionar os handlers um por um. Agora, com a introdução das configurações padrão por meio de EnsureDefaultHandler(), o desenvolvedor pode começar a utilizar a biblioteca com apenas uma linha de código.
A seguir, mostramos os dois cenários lado a lado:
| Código antigo | Código novo |
|---|---|
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify *logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { MqlLogifyHandleCommentConfig m_config; m_config.size = 5; m_config.frame_style = LOG_FRAME_STYLE_SINGLE; m_config.direction = LOG_DIRECTION_UP; m_config.title = "Expert name"; CLogifyFormatter *formatter = new CLogifyFormatter("{date_time} [{levelname}]: {msg}"); formatter.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}]: {msg} [{err_constant} | {err_code} | {err_description}]"); CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment(); handler_comment.SetConfig(m_config); handler_comment.SetLevel(LOG_LEVEL_DEBUG); handler_comment.SetFormatter(formatter); CLogifyHandlerConsole *handler_console = new CLogifyHandlerConsole(); handler_console.SetLevel(LOG_LEVEL_DEBUG); handler_console.SetFormatter(formatter); logify = new CLogify(); logify.AddHandler(handler_comment); logify.AddHandler(handler_console); 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); } void OnDeinit(const int reason) { delete logify; } //+------------------------------------------------------------------+ | //+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify *logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { logify = new CLogify(); 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); } void OnDeinit(const int reason) { delete logify; } //+------------------------------------------------------------------+ |
Essa abordagem minimalista atende à maioria das situações sem exigir nenhuma configuração manual, sendo ideal para prototipagem rápida e testes.
Quando precisamos de maior controle sobre o comportamento dos logs, entra em cena o novo Builder. Ele oferece uma interface fluente (fluent interface) e, mais importante, é totalmente tipada. Isso significa que o próprio editor de código sugere os métodos disponíveis à medida que digitamos, reduzindo a possibilidade de erros e eliminando a necessidade de memorizar as assinaturas das funções.
Quando você digita logify.Create().AddHandler, o editor já apresenta todos os handlers disponíveis:

Ao continuar com .AddHandlerComment(), são exibidas apenas as opções de configuração válidas para esse tipo específico de handler:

Veja como fica o código final, configurando um handler para comentários no gráfico com um formato específico para mensagens de erro.
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify *logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { logify = logify.Create().AddHandlerComment().SetLevel(LOG_LEVEL_DEBUG).SetFormatter(LOG_LEVEL_ERROR,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})").SetTitle("My expert").SetSize(5).Done().Build(); //--- 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); } void OnDeinit(const int reason) { delete logify; } //+------------------------------------------------------------------+
Essa abordagem elimina dúvidas durante a configuração e facilita o desenvolvimento. O resultado é um código mais limpo, claro e menos propenso a erros.
Correção para o MetaTrader 5 build 5100 e versões posteriores
Com o lançamento da build 5100 do terminal MetaTrader 5, algumas alterações internas no compilador passaram a exigir maior rigor no tratamento de tipos em chamadas de funções como DatabaseColumnLong() e DatabaseColumnInteger().
Na prática, isso significa que já não é seguro passar diretamente referências a campos de estruturas (como data[size].timestamp) para essas funções. Para evitar erros de compilação, o ideal é primeiro armazenar o valor em uma variável temporária do tipo adequado e só então passá-la por referência à função.
| Código antigo | Código novo |
|---|---|
//+------------------------------------------------------------------+ //| Get data by sql command | //+------------------------------------------------------------------+ bool CLogifyHandlerDatabase::Query(string query, MqlLogifyModel &data[]) { //--- The rest of the method code remains the same //--- Reads query results line by line for(int i=0;DatabaseRead(request);i++) { int size = ArraySize(data); ArrayResize(data,size+1,size); //--- Maps database data to the MqlLogifyModel model DatabaseColumnText(request,1,data[size].formated); DatabaseColumnText(request,2,data[size].levelname); DatabaseColumnText(request,3,data[size].msg); DatabaseColumnText(request,4,data[size].args); DatabaseColumnLong(request,5,data[size].timestamp); string value; DatabaseColumnText(request,6,value); data[size].date_time = StringToTime(value); DatabaseColumnInteger(request,7,data[size].level); DatabaseColumnText(request,8,data[size].origin); DatabaseColumnText(request,9,data[size].filename); DatabaseColumnText(request,10,data[size].function); DatabaseColumnLong(request,11,data[size].line); } //--- The rest of the method code remains the same } //+------------------------------------------------------------------+ | //+------------------------------------------------------------------+ //| Get data by sql command | //+------------------------------------------------------------------+ bool CLogifyHandlerDatabase::Query(string query, MqlLogifyModel &data[]) { //--- The rest of the method code remains the same //--- Reads query results line by line for(int i=0;DatabaseRead(request);i++) { int size = ArraySize(data); ArrayResize(data,size+1,size); //--- Maps database data to the MqlLogifyModel model DatabaseColumnText(request,1,data[size].formated); DatabaseColumnText(request,2,data[size].levelname); DatabaseColumnText(request,3,data[size].msg); DatabaseColumnText(request,4,data[size].args); long timestamp = (long)data[size].timestamp; DatabaseColumnLong(request,5,timestamp); string value; DatabaseColumnText(request,6,value); data[size].date_time = StringToTime(value); int level = data[size].level; DatabaseColumnInteger(request,7,level); DatabaseColumnText(request,8,data[size].origin); DatabaseColumnText(request,9,data[size].filename); DatabaseColumnText(request,10,data[size].function); long line = (long)data[size].line; DatabaseColumnLong(request,11,line); } //--- The rest of the method code remains the same } //+------------------------------------------------------------------+ |
O restante do código permanece inalterado. Trata-se de um ajuste pontual, mas necessário para manter a compatibilidade com as versões mais recentes do terminal.
Vale lembrar que esse tipo de correção é comum quando o compilador passa a aplicar verificações de tipos mais rigorosas, geralmente para evitar problemas difíceis de identificar durante a execução. Portanto, embora pareça uma alteração simples, é uma atualização importante para garantir que a Logify continue funcionando de maneira estável nas novas versões do MetaTrader 5.
Conclusão
Até agora, a configuração da biblioteca Logify oferecia muitas possibilidades, mas era um pouco burocrática. Era preciso criar objetos, definir configurações manualmente, lembrar a ordem das chamadas... Enfim, funcionava, mas dava um certo trabalho.
Nesta parte do artigo, resolvemos esse problema. Criamos uma nova maneira de configurar o logger: simples, clara e rápida. O padrão Builder entrou em cena para tornar tudo mais natural: você digita logify.Create() e o próprio editor apresenta as próximas opções. Precisa de um handler para exibir comentários no gráfico? Digite AddHandlerComment(). Quer alterar o título? O método SetTitle() aparece entre as sugestões. Não é preciso memorizar nada nem consultar a documentação a cada etapa. Basta seguir a sequência de chamadas.
Também tornamos a biblioteca ainda mais fácil de usar ao adicionar configurações padrão. Se você quer apenas registrar mensagens e não precisa personalizar os logs, não é necessário fazer nenhuma configuração especial. Basta criar o objeto e começar a utilizá-lo. A própria Logify se encarrega de exibir as mensagens no console e no gráfico.
Por fim, corrigimos um detalhe técnico importante: com o lançamento da build 5100 do MetaTrader 5, o compilador passou a ser mais rigoroso com a passagem de referências em funções como DatabaseColumnLong() e DatabaseColumnInteger(). Para garantir a compatibilidade, fizemos pequenos ajustes em CLogifyHandlerDatabase, utilizando variáveis temporárias antes de passar os valores por referência para essas funções. Para quem utiliza a biblioteca, nada muda, mas internamente ela continua funcionando de maneira estável, mesmo após as atualizações do terminal.
Com isso, alcançamos um resultado que todo desenvolvedor valoriza: menos código, menos erros e mais clareza. Agora, a biblioteca oferece uma interface mais intuitiva, sem impor restrições desnecessárias nem complicar tarefas que deveriam ser simples. À medida que a Logify evoluir, ganhando mais flexibilidade e novos recursos que acredito serem úteis para a maioria dos usuários, continuarei publicando artigos para apresentar as melhorias e facilitar nosso dia a dia. A ideia é que a biblioteca evolua junto com quem a utiliza, sem truques ou soluções misteriosas, apenas com código bem projetado.
| Nome do arquivo | Descrição |
|---|---|
| Experts/Logify/LogiftTest.mq5 | Arquivo utilizado para testar os recursos da biblioteca, contendo um exemplo prático. |
| Include/Logify/Error/Languages/ErrorMessages.XX.mqh | Arquivo com 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 informações sobre erros. |
| Include/Logify/Error/LogifyError.mqh | Classe responsável por obter 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 definição dos níveis de log e o envio das mensagens. |
| Include/Logify/Handlers/LogifyHandlerComment.mqh | Handler que exibe os logs formatados diretamente na área de comentários do gráfico do terminal MetaTrader. |
| Include/Logify/Handlers/LogifyHandlerConsole.mqh | Handler que envia os logs formatados diretamente para o console do terminal MetaTrader. |
| Include/Logify/Handlers/LogifyHandlerDatabase.mqh | Handler destinado a enviar os logs formatados para um banco de dados. Na versão atual, apenas exibe as informações, mas em breve permitirá armazená-las em um banco de dados SQLite. |
| Include/Logify/Handlers/LogifyHandlerFile.mqh | Handler que grava os logs formatados em um arquivo. |
| Include/Logify/Utils/IntervalWatcher.mqh | Verifica se um intervalo de tempo definido já decorreu, permitindo implementar rotinas internas na biblioteca. |
| Include/Logify/Logify.mqh | Classe principal para o gerenciamento dos logs, integrando níveis de log, modelos e formatação. |
| Include/Logify/LogifyBuilder.mqh | Classe responsável pela criação do objeto CLogify, simplificando sua configuração. |
| Include/Logify/LogifyLevel.mqh | Arquivo que define os níveis de log da biblioteca Logify, permitindo um controle detalhado sobre o registro das mensagens. |
| Include/Logify/LogifyModel.mqh | Estrutura que representa os registros de log, incluindo informações como nível, mensagem, marca de tempo e contexto. |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18602
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Desenvolvimento de ferramentas para análise de Price Action (Parte 29): EA "Boom and Crash Interceptor"
Criação de EAs auto-otimizáveis em MQL5 (Parte 8): Análise de múltiplas estratégias (2)
Machine Learning e Data Science (Parte 45): Previsão de séries temporais no Forex com o modelo PROPHET do Facebook
Desenvolvimento de ferramentas para análise de Price Action (Parte 28): Ferramenta para operar rompimentos da faixa de abertura
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Estou pensando se não seria melhor exibir apenas mensagens de depuração e de erro. E já tenho todas as mensagens de informação, alerta etc. integradas ao EA. Talvez seja possível definir um valor booleano para cada tipo na ‘enum ENUM_LOG_LEVEL’ para mostrar o que queremos?
No código de produção, se desativarmos alguns dos registros, eles não devem ser compilados no arquivo ex5 final.
Para fazer isso, você pode usar uma variável ou até mesmo um endereço IP no expert que armazene o valor do nível desejado e simplesmente o passe para o manipulador. Aqui está um exemplo.
Isso exibirá apenas mensagens com um nível de gravidade maior ou igual ao definido no manipulador.
O idioma padrão de exibição de erros do log pode ser ajustado para o idioma do terminal do usuário de acordo com este código
A Parte 10 deste artigo está na fila para publicação. Ela aborda uma maneira de suprimir registros idênticos e também define o idioma padrão do terminal. Obrigado pela sugestão!
Para fazer isso, você pode usar uma variável ou até mesmo um endereço IP no expert que armazene o valor do nível desejado e simplesmente o passe para o manipulador. Aqui está um exemplo.
Isso exibirá apenas mensagens com um nível de gravidade maior ou igual ao definido no manipulador.
O autor mostra como alterar o nível em tempo de execução sem modificar o código.
Acho que ele só quer mostrar um único nível de log. Ele precisa modificar o código da seguinte forma: