Dominando registros de log (Parte 2): Formatação dos logs
Introdução
No primeiro artigo desta série, Dominando registros de log (Parte 1): Conceitos fundamentais e primeiros passos em MQL5, começamos criando nossa própria biblioteca de logs para o desenvolvimento de um EA (Expert Advisor). Nessa publicação, definimos as principais motivações para a criação de uma ferramenta tão essencial: superar as limitações dos logs nativos do MetaTrader 5 e introduzir uma solução confiável, personalizável e de alta performance no ecossistema MQL5.
Relembrando os principais pontos abordados: estabelecemos as bases da nossa biblioteca, definindo os seguintes requisitos fundamentais:
- Estrutura confiável utilizando o padrão Singleton, garantindo consistência entre os diferentes trechos de código.
- Modo avançado de armazenamento para salvar logs em bancos de dados, permitindo histórico rastreável para análise profunda e auditoria.
- Flexibilidade de saída, permitindo salvar ou exibir os logs com praticidade, seja no console, em arquivos, no terminal ou em um banco de dados.
- Classificação por níveis de log, diferenciando mensagens informativas de alertas críticos e erros.
- Personalização do formato de saída para atender às necessidades específicas de cada desenvolvedor ou projeto.
Com essa base sólida, ficou claro que o sistema de logging que estamos desenvolvendo representa muito mais do que um simples registrador de eventos: trata-se de uma ferramenta estratégica para compreender, monitorar e otimizar o comportamento dos EAs em tempo real.
Agora, neste segundo artigo, chegamos a uma das funcionalidades-chave dessa biblioteca: a formatação dos logs. Afinal, um log eficiente não diz respeito apenas ao que é registrado, mas também a como essa informação é apresentada. Imagine receber uma mensagem confusa ou mal formatada durante um teste importante do EA. Isso pode não apenas dificultar a análise de forma desnecessária, mas também resultar na perda de informações cruciais. Queremos que cada log proporcione o nível certo de clareza e detalhamento, mantendo ao mesmo tempo a flexibilidade de personalização de acordo com as necessidades do desenvolvedor.
O que é o formato de um log?
O formato de log é a estrutura segundo a qual a informação registrada durante a execução do programa é organizada de forma simples e compreensível. Trata-se de um padrão que define como cada registro será exibido e inclui a coleta de dados essenciais, como o nível de severidade do evento, a data e hora em que ocorreu, a origem do registro e a mensagem associada contendo sua descrição.
Esse tipo de organização do log é extremamente importante para garantir sua legibilidade e utilidade. Sem formatação, os logs podem parecer confusos e fora de contexto, tornando a análise mais difícil. Imagine, por exemplo, visualizar em um log não estruturado uma entrada como esta:
DEBUG: Order sent successfully, server responded in 32msAgora compare isso com um log em que é seguido um formato estruturado, exibindo não apenas as mesmas informações, mas também fornecendo o contexto:
[2025-01-02 12:35:27] DEBUG (CTradeManager): Order sent successfully, server responded in 32ms
Essa pequena diferença pode ter um impacto significativo durante o diagnóstico de problemas, especialmente em sistemas complexos. A formatação permite transformar dados brutos em uma narrativa coerente, oferecendo ao desenvolvedor uma visão clara sobre o comportamento do sistema.
A flexibilidade também é um aspecto essencial dos formatos de log. Cada projeto possui suas próprias necessidades, e a possibilidade de adaptar o formato do log a essas demandas permite criar personalizações úteis, como destacar eventos críticos, rastrear origens ou complementar mensagens com metadados contextuais.
Estrutura básica de um programa de formatação
A estrutura básica de um programa de formatação se baseia no conceito de "placeholders", ou seja, elementos que funcionam como áreas substituíveis dentro de um template. Esses placeholders definem onde e como as informações do log serão inseridas na mensagem final de saída.
Você pode imaginar um programa de formatação como uma máquina que transforma dados brutos de um evento em log em um formato legível e personalizável. O modelo de dados alimentado pode conter valores como nível de severidade, mensagem, horário e outras informações. O programa de formatação então insere esses valores dentro de um template fornecido pelo usuário, resultando em um log formatado.
Como exemplo, considere o seguinte template:
{date_time} {levelname}: {msg}Após a substituição dos placeholders pelos valores correspondentes, a saída será algo semelhante a isto: 12/04/2025 00:25:45 DEBUG: IFR indicator successfully inserted into the chart!
A força de um programa de formatação está na sua flexibilidade. Aqui estão alguns exemplos de placeholders que adicionaremos à nossa biblioteca:
- {levelname}: representa o nível de log (por exemplo, DEBUG, ERROR ou FATAL) em formato legível para o ser humano;
- {msg}: a mensagem que descreve o evento registrado no log;
- {args}: dados adicionais que complementam o contexto da mensagem, frequentemente usados para exibir informações dinâmicas;
- {timestamp}: o carimbo de tempo em milissegundos, útil para análise de precisão;
- {date_time}: a versão legível do carimbo de tempo, exibindo data e hora em um formato padrão para leitura humana;
- {origin}: indica a origem do evento, como o módulo ou a classe de onde a entrada de log foi gerada;
- {filename} , {function} e {line}: determinam o ponto exato no código em que o log foi criado, tornando o processo de depuração mais eficiente.
A lógica de um programa de formatação é simples, mas muito eficaz. Ela permite que você crie sua própria moldura para cada mensagem de log, oferecendo ao desenvolvedor a possibilidade de exibir as informações mais relevantes em cada situação. Essa abordagem é especialmente útil em projetos onde o volume de dados é grande e uma análise rápida é essencial.
Graças aos templates personalizáveis e ao conjunto completo de placeholders, a biblioteca se torna uma ferramenta modular e poderosa. Essa modularidade garante que você possa criar registros de log ajustados exatamente às necessidades do seu aplicativo, aumentando a eficiência na interpretação e utilização das informações armazenadas.
Implementação de programas de formatação em MQL5
Agora que entendemos o que são formatos e placeholders, vamos direto ao código para compreender como isso será implementado na versão atual da nossa biblioteca. Primeiramente, criaremos uma nova pasta chamada "Formatter" dentro do caminho <Include/Logify>. Em seguida, crie dentro dela o arquivo LogifyFormatter.mqh. O caminho final ficará assim: <Include/Logify/Formatter/LogifyFormatter.mqh>. Lembrando que, ao final do artigo, anexei as versões finais dos arquivos utilizados aqui, basta baixar e usar. Veja como o gerenciador de arquivos deve se parecer: 
//+------------------------------------------------------------------+ //| LogifyFormatter.mqh | //| joaopedrodev | //| https://www.mql5.com/pt/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/pt/users/joaopedrodev" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "../LogifyModel.mqh" //+------------------------------------------------------------------+ //| class : CLogifyFormatter | //| | //| [PROPERTY] | //| Name : CLogifyFormatter | //| Heritage : No heritage | //| Description : Class responsible for formatting the log into a | //| string, replacing placeholders with their respective values. | //| | //+------------------------------------------------------------------+ class CLogifyFormatter { public: CLogifyFormatter(void); ~CLogifyFormatter(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyFormatter::CLogifyFormatter(void) { } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyFormatter::~CLogifyFormatter(void) { } //+------------------------------------------------------------------+
Declararemos os atributos privados responsáveis por armazenar as configurações de data e os formatos de log:
class CLogifyFormatter { private: //--- Stores the formats string m_date_format; string m_log_format; public: //--- Format query methods string GetDateFormat(void); string GetLogFormat(void); }; //+------------------------------------------------------------------+ //| Get date format | //+------------------------------------------------------------------+ string CLogifyFormatter::GetDateFormat(void) { return(m_date_format); } //+------------------------------------------------------------------+ //| Get the log format | //+------------------------------------------------------------------+ string CLogifyFormatter::GetLogFormat(void) { return(m_log_format); } //+------------------------------------------------------------------+
Aqui definimos:
- m_date_format: determina como as datas serão formatadas (por exemplo, "yyyy/MM/dd hh:mm:ss").
- m_log_format: define o padrão de log (por exemplo, "{timestamp} - {msg}").
- E dois outros métodos de acesso aos atributos privados.
O construtor inicializa a data e o formato do log, verificando o formato através do método CheckLogFormat, com o qual entraremos em contato em breve. Para isso, adicionei dois parâmetros ao construtor, de modo a simplificar a definição do formato correto durante a criação de uma instância da classe.
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyFormatter::CLogifyFormatter(string date_formate,string log_format) { m_date_format = date_formate; if(CheckLogFormat(log_format)) { m_log_format = log_format; } } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyFormatter::~CLogifyFormatter(void) { } //+------------------------------------------------------------------+
O destrutor (~CLogifyFormatter) não executa nenhuma ação específica, mas é útil praticar sua declaração, pois ele pode se mostrar necessário em situações futuras.
Quanto ao método CheckLogFormat, vamos agora tratar de sua implementação. Ele desempenha um papel essencial na verificação dos templates usados para a formatação dos logs. Seu objetivo é garantir que todos os placeholders dentro do template estejam devidamente estruturados e fechados antes de serem processados. Esse tipo de verificação é extremamente importante para evitar erros inesperados e assegurar a confiabilidade na geração dos logs. Vamos analisar alguns exemplos de formatos incorretos e entender suas causas:
- Placeholders não fechados: por exemplo, {timestamp} - {{msg}. Aqui temos um caractere { extra que não foi devidamente fechado. Esse tipo de erro indica uma estrutura incompleta, o que leva a falhas durante o processamento do log.
- Placeholders não abertos: em casos como {timestamp} - {msg}}, há um caractere } extra, que não corresponde a nenhum {. Assim como no exemplo anterior, isso resulta em uma estrutura de log inválida.
- Placeholders vazios: o template {timestamp} - {msg} {} contém um placeholder vazio {} que não possui uma chave associada. Cada placeholder deve conter uma referência válida, que será substituída dinamicamente; um template vazio não atende a essa exigência.
- Formato vazio: o método também considera casos de entrada inválida em que a string passada está completamente vazia. Para ser válido, o template deve conter pelo menos um caractere que sirva de base para o log formatado.
Quando o método detecta algum desses problemas, ele retorna false e exibe ao desenvolvedor mensagens detalhadas de erro. Essas mensagens ajudam a identificar e corrigir rapidamente falhas no template fornecido. Por outro lado, se o template estiver corretamente estruturado e atender a todas as regras, o método retornará true, indicando que o modelo está pronto para uso. Além de garantir precisão, esse método incentiva a adoção de boas práticas no design dos templates de log, estimulando os desenvolvedores a criar estruturas claras e consistentes. Essa abordagem não apenas aumenta a legibilidade dos logs, mas também simplifica sua manutenção e análise ao longo do tempo.
//+------------------------------------------------------------------+ //| Validate format | //+------------------------------------------------------------------+ bool CLogifyFormatter::CheckLogFormat(string log_format) { //--- Variables to track the most recent '{' opening index and the number of '{' brace openings int openIndex = -1; // Index of last '{' found int openBraces = 0; // '{' counter int len = StringLen(log_format); // String length //--- Checks if string is empty if(len == 0) { //--- Prints error message and returns false Print("Erro de formatação: sequência inesperada encontrada. Verifique o padrão de placeholders usado."); return(false); } //--- Iterate through each character of the string for(int i=0;i<len;i++) { //--- Gets the current character ushort character = StringGetCharacter(log_format,i); //--- Checks if the character is an opening '{' if(character == '{') { openBraces++; // Increments the opening counter '{' openIndex = i; // Updates the index of the last opening } //--- Checks if the character is a closing '}' else if(character == '}') { //--- If there is no matching '{' if(openBraces == 0) { //--- Prints error message and returns false Print("Erro de formatação: o caractere '}' na posição ",i," não possui um '{' correspondente."); return(false); } //--- Decreases the open count because a matching '{' was found openBraces--; //--- Extracts the contents of the placeholder (between '{' and '}') string placeHolder = StringSubstr(log_format, openIndex + 1, i - openIndex - 1); //--- Checks if placeholder is empty if(StringLen(placeHolder) == 0) { //--- Prints error message and returns false Print("Erro de formatação: placeholder vazio detectado na posição ",i,". Um nome é esperado dentro de '{...}'."); return(false); } } } //--- After traversing the entire string, check if there are still any unmatched '{'}' if(openBraces > 0) { //--- Prints error message indicating the index of the last opened '{' and returns false Print("Erro de formatação: o placeholder '{' na posição ",openIndex," não possui um '}' correspondente."); return(false); } //--- Format is correct return(true); } //+------------------------------------------------------------------+
Agora, avancemos para os dois principais métodos da classe, responsáveis pelo processo de formatação dos logs:
- FormatDate() para manipulação de datas;
- FormatLog() para criação do próprio formato do log.
Ambos os métodos desempenham papéis centrais na personalização dos dados que são registrados pela biblioteca de logs. Para aumentar a flexibilidade e tornar esses métodos mais expansíveis, eles são declarados como virtuais. Como os métodos são virtuais, o FormatDate pode ser facilmente sobrescrito em classes derivadas, adaptando-se às necessidades de cenários específicos. Por exemplo, em uma implementação personalizada, o formato de data pode ser ajustado para incluir o fuso horário ou outras informações adicionais. Essa arquitetura flexível permite que a biblioteca evolua junto com os requisitos dos projetos, garantindo sua aplicabilidade em uma ampla variedade de contextos.
O método FormatDate é responsável por converter um objeto datetime em uma string formatada, adaptada de acordo com o padrão definido em m_date_format. Nesse padrão é utilizado um sistema de placeholders, que são dinamicamente substituídos pelos elementos correspondentes da data, como ano, mês, dia, horário e assim por diante.
Essa abordagem é extremamente flexível e oferece amplas possibilidades de personalização dos formatos, atendendo aos mais diversos cenários. Por exemplo, você pode optar por exibir apenas o dia e o mês, ou complementar essas informações com o dia da semana e o horário. Abaixo está a lista dos placeholders disponíveis:
- Ano
- yy→ ano com dois dígitos (por exemplo, "25").
- yyyy → ano com quatro dígitos (por exemplo, "2025").
- Mês
- M → mês sem zero à esquerda (por exemplo, "1" para janeiro).
- MM → mês com dois dígitos (por exemplo, "01" para janeiro).
- MMM → nome abreviado do mês (por exemplo, "Jan").
- MMMM → nome completo do mês (por exemplo, "January").
- Dia
- d → dia sem zero à esquerda (por exemplo, "1").
- dd → dia com dois dígitos (por exemplo, "01").
- Dia do ano
- D → dia do ano sem zero à esquerda (por exemplo, "32" para 1º de fevereiro). DDD → dia do ano com três dígitos (por exemplo, "032").
- Dia da semana
- E → nome abreviado do dia da semana (por exemplo, "Mon").
- EEEE → nome completo do dia da semana (por exemplo, "Monday").
- Horas no formato de 24 horas
- H → hora sem zero à esquerda (por exemplo, "9").
- HH → hora com dois dígitos (por exemplo, "09").
- Horas no formato de 12 horas
- h → hora sem zero à esquerda (por exemplo, "9").
- hh → hora com dois dígitos (por exemplo, "09").
- Minutos
- m → minuto sem zero à esquerda (por exemplo, "5").Minutos
- mm → minuto com dois dígitos (por exemplo, "05").
- Segundos
- s → segundo sem zero à esquerda (por exemplo, "9").
- ss → segundo com dois dígitos (por exemplo, "09").
- AM/PM
- tt → retorna em letras minúsculas (am/pm).
- TT → retorna em letras maiúsculas (AM/PM).
Essa lógica garante uma personalização muito conveniente da exibição da data, de acordo com suas necessidades. Por exemplo, ao usar o formato "yyyy-MM-dd HH:mm:ss", o resultado seria algo como "2025-01-02 14:30:00". Já utilizando o formato "EEEE, MMM dd, yyyy", a saída aparecerá como "Tuesday, Jul 30, 2019". Essa adaptabilidade é extremamente importante para gerar logs que sejam ao mesmo tempo informativos e visualmente legíveis.
A lógica do FormatLog é baseada na StringReplace() do MQL5. Essa função realiza substituições diretas em strings, trocando todas as ocorrências de uma determinada substring por outra. No contexto do método FormatLog, placeholders como {timestamp} ou {message} são substituídos por valores reais provenientes do modelo de log. Isso transforma as instâncias do modelo, como MqlLogifyModel, em dados organizados e prontos para visualização.
A seguir está o código para a implementação dentro da classe, ao qual adicionei diversos comentários explicativos para detalhar o funcionamento o máximo possível:
//+------------------------------------------------------------------+ //| class : CLogifyFormatter | //| | //| [PROPERTY] | //| Name : CLogifyFormatter | //| Heritage : No heritage | //| Description : Class responsible for formatting the log into a | //| string, replacing placeholders with their respective values. | //| | //+------------------------------------------------------------------+ class CLogifyFormatter { //--- Date and log formatting methods virtual string FormatDate(datetime date); virtual string FormatLog(MqlLogifyModel &data); }; //+------------------------------------------------------------------+ //| Formats dates | //+------------------------------------------------------------------+ string CLogifyFormatter::FormatDate(datetime date) { string formated = m_date_format; //--- Date and time structure MqlDateTime time; TimeToStruct(date, time); //--- Array with months and days of the week in string string months_abbr[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; string months_full[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; string day_of_week_abbr[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; string day_of_week_full[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; //--- Replace year StringReplace(formated, "yyyy", IntegerToString(time.year)); StringReplace(formated, "yy", IntegerToString(time.year % 100, 2, '0')); //--- Replace month if(StringFind(formated,"MMM") < 0 && StringFind(formated,"MMMM") < 0) { StringReplace(formated, "MM", IntegerToString(time.mon, 2, '0')); StringReplace(formated, "M", IntegerToString(time.mon)); } //--- Replace day StringReplace(formated, "dd", IntegerToString(time.day, 2, '0')); StringReplace(formated, "d", IntegerToString(time.day)); //--- Replace day of year StringReplace(formated, "DDD", IntegerToString(time.day_of_year, 3, '0')); StringReplace(formated, "D", IntegerToString(time.day_of_year)); //--- Replace Replace hours (24h and 12h) StringReplace(formated, "HH", IntegerToString(time.hour, 2, '0')); StringReplace(formated, "H", IntegerToString(time.hour)); int hour_12 = time.hour % 12; if (hour_12 == 0) hour_12 = 12; StringReplace(formated, "hh", IntegerToString(hour_12, 2, '0')); StringReplace(formated, "h", IntegerToString(hour_12)); //--- Replace minutes and seconds StringReplace(formated, "mm", IntegerToString(time.min, 2, '0')); StringReplace(formated, "m", IntegerToString(time.min)); StringReplace(formated, "ss", IntegerToString(time.sec, 2, '0')); StringReplace(formated, "s", IntegerToString(time.sec)); //--- Replace AM/PM bool is_am = (time.hour < 12); StringReplace(formated, "tt", is_am? "am" : "pm"); StringReplace(formated, "TT", is_am? "AM" : "PM"); //--- Replace month StringReplace(formated, "MMMM", months_full[time.mon - 1]); StringReplace(formated, "MMM", months_abbr[time.mon - 1]); //--- Replace day of week StringReplace(formated, "EEEE", day_of_week_full[time.day_of_week]); StringReplace(formated, "E", day_of_week_abbr[time.day_of_week]); return(formated); } //+------------------------------------------------------------------+ //| Format logs | //+------------------------------------------------------------------+ string CLogifyFormatter::FormatLog(MqlLogifyModel &data) { string formated = m_log_format; //--- Replace placeholders StringReplace(formated,"{timestamp}",IntegerToString(data.timestamp)); StringReplace(formated,"{level}",IntegerToString(data.level)); StringReplace(formated,"{origin}",data.origin); StringReplace(formated,"{message}",data.message); StringReplace(formated,"{metadata}",data.metadata); return(formated); } //+------------------------------------------------------------------+
Com isso, a construção da classe está concluída, e podemos avançar para a atualização do modelo MqlLogifyModel.
Adição de dados ao MqlLogifyModel
O modelo MqlLogifyModel é um dos componentes principais da nossa biblioteca de logging, representando a estrutura base para armazenar e manipular os dados de cada evento de log. Em sua forma atual, a estrutura é definida da seguinte maneira:struct MqlLogifyModel { ulong timestamp; // Date and time of the event ENUM_LOG_LEVEL level; // Severity level string origin; // Log source string message; // Log message string metadata; // Additional information in JSON or text MqlLogifyModel::MqlLogifyModel(void) { timestamp = 0; level = LOG_LEVEL_DEBUG; origin = ""; message = ""; metadata = ""; } MqlLogifyModel::MqlLogifyModel(ulong _timestamp,ENUM_LOG_LEVEL _level,string _origin,string _message,string _metadata) { timestamp = _timestamp; level = _level; origin = _origin; message = _message; metadata = _metadata; } };
Essa versão inicial funciona bem em cenários simples, mas podemos aprimorá-la consideravelmente ao adicionar mais informações e ajustar os nomes das propriedades, tornando o uso da estrutura mais intuitivo e aderente às melhores práticas. Abaixo, discutiremos as melhorias planejadas.
Simplificação dos nomes das propriedades- O campo da mensagem será renomeado para msg. Essa alteração, embora pequena, reduz o número de caracteres ao acessar a propriedade, tornando a escrita e leitura do código mais fluida.
- O campo metadados será renomeado para args, já que esse novo nome descreve de forma mais precisa a função da propriedade: armazenar dados contextuais relacionados ao registro de log no momento de sua criação.
Novos campos adicionados
Para enriquecer os logs e possibilitar análises mais detalhadas, serão incluídos os seguintes campos:
- formatted: converte a mensagem do log conforme o formato especificado. O campo será usado para armazenar a versão final do log, já com os placeholders substituídos pelos valores reais. Essa propriedade é somente para leitura.
- levelname: designação textual do nível de log (por exemplo, "DEBUG" ou "INFO"). É útil quando o formato precisa indicar o grau de severidade.
- date_time: representa a data e a hora do evento no formato datetime, servindo como uma alternativa ao carimbo de tempo.
- filename: nome do arquivo no qual o registro do log foi criado, essencial para o rastreamento preciso da origem.
- function: nome da função que gerou o log, fornecendo mais informações contextuais sobre a origem do evento.
- line: número da linha no arquivo-fonte em que o registro de log foi gerado. Essa propriedade pode ser especialmente útil em cenários de depuração.
Esses novos campos tornam o modelo MqlLogifyModel mais robusto e preparado para atender a diferentes exigências de logging, como depuração detalhada ou integração com ferramentas externas de monitoramento. Após essas modificações, o código passa a ter a seguinte aparência:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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 MqlLogifyModel::MqlLogifyModel(void) { formated = ""; levelname = ""; msg = ""; args = ""; timestamp = 0; date_time = 0; level = LOG_LEVEL_DEBUG; origin = ""; filename = ""; function = ""; line = 0; } 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) { formated = _formated; levelname = _levelname; msg = _msg; args = _args; timestamp = _timestamp; date_time = _date_time; level = _level; origin = _origin; filename = _filename; function = _function; line = _line; } }; //+------------------------------------------------------------------+Como próximo passo, atualizaremos o método FormatLog da classe CLogifyFormatter, adicionando suporte aos placeholders correspondentes às novas propriedades. Abaixo está a versão atualizada do método, agora compatível com todos os novos atributos do modelo:
//+------------------------------------------------------------------+ //| 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)); return(formated); } //+------------------------------------------------------------------+
No método FormatLog, o valor de {date_time} é substituído pela saída formatada gerada pelo FormatDate, ou seja, esse placeholder é trocado pela data de acordo com o formato previamente definido.
Aplicação dos programas de formatação aos logs
Vamos agora garantir que nossa classe CLogify consiga utilizar o programa responsável pela formatação das entradas de log. Para isso, importamos a classe responsável e adicionamos um atributo destinado ao seu armazenamento:
#include "LogifyModel.mqh" #include "Formatter/LogifyFormatter.mqh"
O próximo passo será adicionar o atributo m_formatter à classe CLogify, para armazenar a instância do programa de formatação, além de criar métodos para sua configuração e acesso. Isso permitirá reutilizar o programa de formatação em diferentes partes do sistema:
//+------------------------------------------------------------------+ //| class : CLogify | //| | //| [PROPERTY] | //| Name : Logify | //| Heritage : No heritage | //| Description : Core class for log management. | //| | //+------------------------------------------------------------------+ class CLogify { private: CLogifyFormatter *m_formatter; public: //--- Get/Set object formatter void SetFormatter(CLogifyFormatter *format); CLogifyFormatter *GetFormatter(void); }; //+------------------------------------------------------------------+ //| Set object formatter | //+------------------------------------------------------------------+ void CLogify::SetFormatter(CLogifyFormatter *format) { m_formatter = GetPointer(format); } //+------------------------------------------------------------------+ //| Get object formatter | //+------------------------------------------------------------------+ CLogifyFormatter *CLogify::GetFormatter(void) { return(m_formatter); } //+------------------------------------------------------------------+
Os métodos de logging existentes atualmente possuem um conjunto limitado de parâmetros, como carimbo de tempo, nível de log, mensagem, origem e metadados. Vamos aprimorar também essa estrutura, adicionando novos parâmetros que descrevem o contexto do log, a saber:
- filename: nome do arquivo em que o log foi chamado.
- function: nome da função na qual o log foi chamado.
- line: linha de código que gerou o log.
Além disso, para tornar a assinatura do método mais intuitiva, modificaremos os nomes dos parâmetros. Veja um exemplo da alteração do método principal (Append):
bool CLogify::Append(ulong timestamp, ENUM_LOG_LEVEL level, string message, string origin = "", string metadata = "");
Método modificado
bool CLogify::Append(ENUM_LOG_LEVEL level, string msg, string origin = "", string args = "", string filename = "", string function = "", int line = 0);
Agora nossa nova implementação se apresenta da seguinte forma:
//+------------------------------------------------------------------+ //| 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) { //--- If the formatter is not configured, the log will not be recorded. if(m_formatter == NULL) { return(false); } //--- Textual name of the log level string levelStr = ""; switch(level) { case LOG_LEVEL_DEBUG: levelStr = "DEBUG"; break; case LOG_LEVEL_INFOR: levelStr = "INFOR"; 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); //--- Printing the formatted log Print(m_formatter.FormatLog(data)); return(true); } //+------------------------------------------------------------------+
Observe que estou exibindo o valor retornado pela função FormatLog, que representa o objeto de dados.
Agora que nosso método principal Append já trabalha com parâmetros detalhados, podemos ajustar ou adicionar outros métodos específicos para cada nível de log (Debug, Info, Alert, Error e Fatal). Esses métodos funcionam como atalhos convenientes que definem automaticamente o nível de log, permitindo ainda o uso de todos os demais parâmetros.
//+------------------------------------------------------------------+ //| Debug level message | //+------------------------------------------------------------------+ bool CLogify::Debug(string msg, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_DEBUG,msg,origin,args,filename,function,line)); } //+------------------------------------------------------------------+ //| Infor level message | //+------------------------------------------------------------------+ bool CLogify::Infor(string msg, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_INFOR,msg,origin,args,filename,function,line)); } //+------------------------------------------------------------------+ //| Alert level message | //+------------------------------------------------------------------+ bool CLogify::Alert(string msg, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_ALERT,msg,origin,args,filename,function,line)); } //+------------------------------------------------------------------+ //| Error level message | //+------------------------------------------------------------------+ bool CLogify::Error(string msg, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_ERROR,msg,origin,args,filename,function,line)); } //+------------------------------------------------------------------+ //| Fatal level message | //+------------------------------------------------------------------+ bool CLogify::Fatal(string msg, string origin = "", string args = "",string filename="",string function="",int line=0) { return(this.Append(LOG_LEVEL_FATAL,msg,origin,args,filename,function,line)); } //+------------------------------------------------------------------+
Exemplo prático
Vou demonstrar como utilizar a classe CLogify com um formato personalizado para os logs. Começaremos com exemplos simples e, em seguida, os tornaremos mais complexos para mostrar a flexibilidade da solução.
1. Configuração básica do log
Para este exemplo, utilizaremos o arquivo de teste LogifyTest.mq5, criado na primeira parte do artigo. A configuração inicial consiste em criar uma instância da classe CLogify e definir um formato básico para as mensagens de log. O código é mostrado a seguir:
//+------------------------------------------------------------------+ //| Import CLogify | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Configure log format logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","[{levelname}] {date_time} => {msg}")); //--- Log a simple message logify.Debug("Application initialized successfully."); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Quando o código é executado, o formato dos logs gerados será algo semelhante a isto:
[DEBUG] 07:25:32 => Application initialized successfully.
Note que está sendo usado um formato simplificado de tempo (hh:mm:ss), e a estrutura da mensagem reflete o nível de log e o horário atual. Este é o exemplo mais básico de utilização do CLogify.
2. Adicionando detalhes e definindo a origem
Agora ampliaremos o exemplo incluindo informações como a origem do registro e parâmetros adicionais. Isso é especialmente útil em sistemas mais complexos, onde os logs precisam indicar qual parte do sistema gerou a mensagem.
int OnInit() { //--- Configure log format logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","[{levelname}] {date_time} ({origin}) => {msg} {args}")); //--- Log a simple message logify.Debug("Connection established with the server.", "Network"); logify.Alert("Configuration file not found!", "Config", "Attempt 1 of 3"); return(INIT_SUCCEEDED); }Esse código resultará na seguinte saída:
[DEBUG] 07:26:18 (Network) => Connection established with the server. [ALERT] 07:26:19 (Config) => Configuration file not found! Attempt 1 of 3
Aqui adicionamos parâmetros de origem e argumentos contextuais para tornar a mensagem do log mais informativa.
3. Uso de metadados avançados
Em sistemas mais robustos, muitas vezes é necessário identificar o arquivo, a função e a linha em que o log foi gerado. Vamos modificar o exemplo, acrescentando essas informações ao log:
int OnInit() { //--- Configure log format logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","[{levelname}] {date_time} ({origin}) => {msg} (File: {filename}, Line: {line})")); //--- Log a simple message logify.Error("Error accessing database.", "Database", "", __FILE__, __FUNCTION__, __LINE__); return(INIT_SUCCEEDED); }Ao executar o código acima, obteremos:
[ERROR] 07:27:15 (Database) => Error accessing database. (File: LogifyTest.mq5, Line: 25)
Agora temos informações detalhadas que permitem rastrear exatamente onde no código ocorreu o erro.
4. Personalização de formatos e integração em sistemas maiores
Como exemplo final, demonstraremos como personalizar formatos e gerar diferentes tipos de mensagens de log dentro de um loop que simula o processo de execução de um sistema maior:
int OnInit() { //--- Configure log format logify.SetFormatter(new CLogifyFormatter("yyyy.MM.dd hh:mm:ss","{date_time} [{levelname}] {msg} - {origin} ({filename}:{line})")); //--- Cycle simulating various system operations for(int i=0; i<3; i++) { logify.Debug("Operation in progress...", "TaskProcessor", "", __FILE__, __FUNCTION__, __LINE__); if(i == 1) { logify.Alert("Possible inconsistency detected.", "TaskValidator", "", __FILE__, __FUNCTION__, __LINE__); } if(i == 2) { logify.Fatal("Critical error, purchase order not executed correctly!", "Core", "", __FILE__, __FUNCTION__, __LINE__); } } return(INIT_SUCCEEDED); }A execução resultará em uma saída semelhante a esta:
2025.01.03 07:25:32 [DEBUG] Operation in progress... - TaskProcessor (LogifyTest.mq5:25) 2025.01.03 07:25:32 [DEBUG] Operation in progress... - TaskProcessor (LogifyTest.mq5:25) 2025.01.03 07:25:32 [ALERT] Possible inconsistency detected. - TaskValidator (LogifyTest.mq5:28) 2025.01.03 07:25:32 [DEBUG] Operation in progress... - TaskProcessor (LogifyTest.mq5:25) 2025.01.03 07:25:32 [FATAL] Critical error, purchase order not executed correctly! - Core (LogifyTest.mq5:32)
Nesse exemplo, é simulada uma sequência real de operações, em que as mensagens são escalonadas de acordo com o nível de gravidade da situação. O formato do log é altamente detalhado e exibe o horário completo, o nível de log, a mensagem e o local no código.
Considerações finais
Neste artigo, exploramos em detalhes a personalização e o uso de diferentes formatos de logs em MQL5 utilizando a biblioteca Logify. Começamos com uma introdução explicando o conceito de formato de log e a importância dessa prática para cenários de monitoramento e depuração.
Em seguida, analisamos a estrutura básica de um programa de formatação, que é o principal instrumento de personalização dos logs, e vimos como sua implementação em MQL5 torna os registros mais claros e acessíveis. Com base nisso, mostramos o processo de implementação desses programas de formatação, destacando as possibilidades de customização e a inclusão de dados adicionais no modelo MqlLogifyModel.
À medida que avançamos, abordamos o processo de adição de informações contextuais extras ao log, tornando-o mais informativo, por exemplo, incluindo a origem exata da mensagem (arquivo, função e linha de código). Também discutimos como configurar esses programas de formatação e aplicá-los aos registros, garantindo que o log resultante atenda às necessidades específicas de qualquer projeto.
Por fim, apresentamos um exemplo prático demonstrando como implementar diferentes níveis de complexidade nos logs, desde configurações básicas até sistemas robustos, com registros detalhados e altamente estruturados. Nesse exemplo, reunimos todos os conceitos estudados, conectando teoria e prática em um contexto real de sistemas desenvolvidos em MQL5.
Abaixo é exibido um diagrama que ilustra a versão atual da biblioteca:

Todo o código utilizado neste artigo está anexado a seguir. Apresentamos também uma tabela com a descrição de cada arquivo que compõe a biblioteca:
| Nome do arquivo | Descrição |
|---|---|
| Experts/Logify/LogifyTest.mq5 | Arquivo onde testamos as funcionalidades da biblioteca, contendo um exemplo prático de uso. |
| Include/Logify/Formatter/LogifyFormatter.mqh | Classe responsável pelo formato dos registros de log, substituindo os placeholders pelos valores correspondentes. |
| Include/Logify/Logify.mqh | Classe principal para gerenciamento dos logs, integrando níveis, modelos e formatação. |
| Include/Logify/LogifyLevel.mqh | Arquivo que define os níveis de logging da biblioteca Logify, oferecendo controle mais detalhado. |
| Include/Logify/LogifyModel.mqh | Estrutura que representa o modelo dos registros de log, incluindo informações como nível, mensagem, carimbo de tempo e contexto. |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16833
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.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 52): Oscilador Accelerator
Aplicação de métodos de ensemble para tarefas de classificação em MQL5
MQL5 Trading Toolkit (Parte 5): Expansão da biblioteca EX5 para gerenciamento do histórico com funções do último ordem pendente executada
Construa EAs auto-otimizáveis em MQL5 (Parte 3): Acompanhamento dinâmico de tendência e retorno à média
- 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