English Русский 中文 Español Deutsch 日本語
preview
Dominando Registros de Log (Parte 4): Salvando logs em arquivos

Dominando Registros de Log (Parte 4): Salvando logs em arquivos

MetaTrader 5Exemplos |
36 1
joaopedrodev
joaopedrodev

Introdução

No primeiro artigo desta série, Dominando Registros de Log (Parte 1): Conceitos Fundamentais e Primeiros Passos em MQL5, iniciamos a criação de uma biblioteca de logs personalizada para o desenvolvimento de Expert Advisors (EAs). No primeiro artigo desta série, Dominando Registros de Log (Parte 1): Conceitos Fundamentais e Primeiros Passos em MQL5, iniciamos a criação de uma biblioteca de logs personalizada para o desenvolvimento de Expert Advisors (EAs).

Para recapitular os principais pontos abordados, estabelecemos a base da nossa biblioteca definindo os seguintes requisitos fundamentais:

  1. Estrutura robusta utilizando o padrão Singleton, garantindo consistência entre os componentes do código.
  2. Persistência avançada para armazenamento de logs em bancos de dados, fornecendo histórico rastreável para auditorias e análises aprofundadas.
  3. Flexibilidade nas saídas, permitindo que os logs sejam armazenados ou exibidos de forma conveniente, seja no console, em arquivos, no terminal ou em um banco de dados.
  4. Classificação por níveis de log, diferenciando mensagens informativas de alertas críticos e erros.
  5. Personalização do formato de saída, para atender às necessidades específicas de cada desenvolvedor ou projeto.

Com essa base bem estabelecida, ficou claro que o framework de logging que estamos desenvolvendo será muito mais do que um simples registro de eventos; ele será uma ferramenta estratégica para compreender, monitorar e otimizar o comportamento dos EAs em tempo real.

Até agora, exploramos os conceitos básicos de logs, aprendemos como formatá-los e entendemos como os handlers controlam o destino das mensagens. Mas onde armazenamos esses logs para referência futura? Agora, neste quarto artigo, vamos analisar mais de perto o processo de salvar logs em arquivos. Vamos começar então!


Por que salvar logs em arquivos?

Salvar logs em arquivos é uma prática essencial para qualquer sistema que valorize robustez e manutenção eficiente. Imagine o seguinte cenário: seu Expert Advisor está em execução há dias e, de repente, ocorre um erro inesperado. Como você pode entender o que aconteceu? Sem registros permanentes, seria como tentar resolver um quebra-cabeça sem ter todas as peças.

Arquivos de log não são apenas uma forma de armazenar mensagens. Eles representam a memória do sistema. Aqui estão os principais motivos para adotá-los:

  1. Persistência e Histórico

    Os logs salvos em arquivos permanecem disponíveis mesmo após a execução do programa. Isso permite consultas históricas para verificar desempenho, compreender comportamentos passados e identificar padrões ao longo do tempo.

  2. Auditoria e Transparência

    Em projetos críticos, como no mercado financeiro, manter um histórico detalhado é essencial para auditorias ou justificativas de decisões automatizadas. Um log bem armazenado pode ser sua maior defesa em caso de questionamentos.

  3. Diagnóstico e Depuração

    Com arquivos de log, você pode rastrear erros específicos, monitorar eventos críticos e analisar cada etapa da execução do sistema.

  4. Flexibilidade de Acesso

    Diferentemente dos logs exibidos no console ou no terminal, os arquivos podem ser acessados remotamente ou compartilhados com equipes, gerando uma visão compartilhada e detalhada dos eventos importantes.

  5. Automação e Integração

    Os arquivos podem ser lidos e analisados por ferramentas automatizadas, enviando alertas para problemas críticos ou criando relatórios detalhados de desempenho.

Ao salvar logs em arquivos, você transforma um recurso simples em uma ferramenta de gestão, acompanhamento e melhoria. Não preciso me aprofundar muito aqui justificando a importância de salvar esses dados em um arquivo; vamos direto ao ponto no próximo tópico, entendendo como implementar esse recurso de forma eficiente em nossa biblioteca.

Antes de ir diretamente ao código, é importante definir as funcionalidades que o handler de arquivos deve oferecer. Abaixo, detalhei cada um dos requisitos necessários:

  • Personalização de Diretório, Nome e Tipo de Arquivo

    Permitir que os usuários configurem:

    • O diretório onde os logs serão armazenados.
    • O nome dos arquivos de log, garantindo maior controle e organização.
    • O formato do arquivo de saída, com suporte para .log , .txt e .json .
  • Configuração de Codificação

    Suportar diferentes tipos de codificação para arquivos de log, como:

    • UTF-8 (padrão recomendado).
    • UTF-7 , Página de Código ANSI (ACP) ou outros, conforme necessário.
  • Relato de Erros da Biblioteca

    A biblioteca deve incluir um sistema para identificar e relatar erros em sua própria execução:

    • Mensagens de erro exibidas diretamente no console do terminal.
    • Informações claras para facilitar o diagnóstico e a resolução de problemas.


Trabalhando com arquivos em MQL5

Em MQL5, lidar com arquivos exige uma compreensão básica de como a linguagem trata essas operações. Se você quiser realmente se aprofundar nas operações detalhadas de leitura, gravação e uso de flags, não posso deixar de recomendar a leitura do artigo Noções Básicas de Programação em MQL5: Arquivos por Dmitry Fedoseev. Ele oferece uma visão geral completa e detalhada do tema, de uma forma que transforma a complexidade em algo claro, sem perder profundidade.

Mas o que buscamos aqui é algo um pouco mais direto e objetivo. Não vamos nos perder em detalhes minuciosos, porque minha missão é ensinar o essencial: abrir, manipular e fechar arquivos de maneira simples e prática.

  1. Entendendo os Diretórios de Arquivos em MQL5 Em MQL5, todos os arquivos manipulados pelas funções padrão são automaticamente armazenados na pasta MQL5/Files, que está localizada dentro do diretório de instalação do terminal. Isso significa que, ao trabalhar com arquivos em MQL5, você só precisa especificar o caminho relativo a partir dessa pasta base, sem a necessidade de incluir o caminho completo. Por exemplo, ao salvar em logs/expert.log , o caminho completo será:

    <b0>Criando e Abrindo Arquivos</b0> A função para abrir ou criar arquivos é <a1>FileOpen</a1>.

  1. Criando e Abrindo Arquivos A função para abrir ou criar arquivos é FileOpen. Ela exige como argumento obrigatório o caminho do arquivo (após MQL5/Files ) e algumas flags que determinam como o arquivo será tratado. As flags que utilizaremos são:

    • FILE_READ: Permite abrir o arquivo para leitura.
    • FILE_WRITE: Permite abrir o arquivo para escrita.
    • FILE_ANSI: Especifica que o conteúdo será gravado utilizando strings no formato ANSI (cada caractere ocupa um byte).

    Um recurso útil do MQL5 é que, ao combinar FILE_READ e FILE_WRITE , ele cria automaticamente o arquivo caso ele não exista. Isso elimina a necessidade de verificações manuais de existência.

  2. Fechando o arquivo Por fim, ao finalizar as operações com o arquivo, utilize a função FileClose() para encerrar o processamento com o arquivo.

Aqui está um exemplo prático de como abrir (ou criar) e fechar um arquivo em MQL5:

int OnInit()
  {
   //--- Open the file and store the handler
   int handle_file = FileOpen("logs\\expert.log", FILE_READ|FILE_WRITE|FILE_ANSI, '\t', CP_UTF8);
   
   //--- If opening fails, display an error in the terminal log
   if(handle_file == INVALID_HANDLE)
     {
      Print("[ERROR] Unable to open log file. Certifique-se de que o diretório existe e tem permissão de escrita. (Code: "+IntegerToString(GetLastError())+")");
      return(INIT_FAILED);
     }
   
   //--- Close file
   FileClose(handle_file);
   
   return(INIT_SUCCEEDED);
  }

Agora que abrimos o arquivo, é hora de aprender como gravar nele.

  1. Posicionando o ponteiro de escrita: Antes de escrever, precisamos definir onde os dados serão inseridos. Utilizamos a função FileSeek() para posicionar o ponteiro de escrita no final do arquivo. Isso evita a sobrescrita do conteúdo existente.
  2. Gravando dados: O método FileWrite() grava strings no arquivo. Não há necessidade de usar “\n” para quebrar a linha. Ao utilizar esse método, na próxima vez que os dados forem gravados, eles serão automaticamente escritos em outra linha, garantindo melhor organização.

Veja como fazer isso na prática:

int OnInit()
  {
   //--- Open the file and store the handler
   int handle_file = FileOpen("logs\\expert.log", FILE_READ|FILE_WRITE|FILE_ANSI, '\t', CP_UTF8);
   
   //--- If opening fails, display an error in the terminal log
   if(handle_file == INVALID_HANDLE)
     {
      Print("[ERROR] Unable to open log file. Certifique-se de que o diretório existe e tem permissão de escrita. (Code: "+IntegerToString(GetLastError())+")");
      return(INIT_FAILED);
     }
   
   //--- Move the writing pointer
   FileSeek(handle_file, 0, SEEK_END);
   
   //--- Writes the content inside the file
   FileWrite(handle_file, "[2025-01-02 12:35:27] DEBUG (CTradeManager): Order sent successfully, server responded in 32ms");
   
   //--- Close file
   FileClose(handle_file);
   
   return(INIT_SUCCEEDED);
  }

Após executar o código, você verá um arquivo criado na pasta Files. O caminho completo será algo como:

<Terminal folder>/MQL5/Files/logs/expert.log

Se você abrir o arquivo, verá exatamente o que escrevemos:

[2025-01-02 12:35:27] DEBUG (CTradeManager): Order sent successfully, server responded in 32ms

Agora que aprendemos a manipular arquivos de forma bem simples em MQL5, vamos adicionar esse trabalho à classe handler responsável por salvar arquivos, CLogifyHandlerFile .


Criando as configurações da classe CLogifyHandlerFile

Agora, vamos detalhar como podemos configurar essa classe para lidar de forma eficiente com a rotação de arquivos mencionada na seção de requisitos. Mas o que exatamente significa “rotação de arquivos”? Deixe-me explicar com mais detalhes. Rotação é uma prática essencial para evitar aquele cenário caótico em que um único arquivo de log cresce indefinidamente, evitando o acúmulo excessivo de dados em um único arquivo de log, o que pode dificultar análises posteriores, transformando os logs em um verdadeiro pesadelo lento, difícil de lidar e quase impossível de decifrar.

Imagine este cenário: um Expert Advisor em execução por semanas ou meses, registrando cada evento, erro ou notificação no mesmo arquivo. Em pouco tempo, esse log começa a atingir tamanhos consideráveis, tornando a leitura e interpretação das informações bastante complexas. É aí que a rotação entra em cena. Ela nos permite dividir essas informações em partes menores e organizadas, tornando tudo muito mais fácil de ler e analisar.

As duas formas mais comuns de fazer isso são:

  1. Por tamanho: Você define um limite de tamanho, geralmente em megabytes (MB), para o arquivo de log. Quando esse limite é atingido, um novo arquivo é criado automaticamente e o ciclo recomeça. Essa abordagem é muito prática quando o foco está em controlar o crescimento dos logs, sem a necessidade de seguir um calendário. Assim que o arquivo atual atinge o limite de tamanho (em megabytes), ocorre o seguinte fluxo: o arquivo de log atual é renomeado, recebendo um índice, como “log1.log”. Os arquivos existentes no diretório também são renumerados, como “log1.log” passando a se chamar “log2.log”. Se o número de arquivos atingir o máximo permitido, os arquivos mais antigos são excluídos. Essa abordagem é útil para limitar tanto o espaço ocupado pelos logs quanto a quantidade de arquivos salvos.
  2. Por data: Nesse caso, um novo arquivo de log é criado todos os dias. Cada um contém a data de criação em seu nome, por exemplo log_2025-01-19.log , o que resolve grande parte da dor de cabeça de organizar logs. Essa abordagem é perfeita quando você precisa analisar um dia específico, sem se perder em um único arquivo gigantesco. Esse é o método que mais utilizo ao salvar os logs dos meus Expert Advisors, pois tudo fica mais limpo, direto e fácil de navegar.

Além disso, também é possível limitar o número de arquivos de log armazenados. Esse controle é muito importante para evitar o acúmulo desnecessário de logs antigos. Imagine que você configure o sistema para manter os 30 arquivos mais recentes; quando o 31º surgir, o sistema descarta automaticamente o mais antigo, evitando o acúmulo de logs muito antigos no disco e mantendo apenas os mais recentes.

Outro detalhe crucial é o uso de um cache. Em vez de gravar cada mensagem diretamente no arquivo assim que ela chega, as mensagens são armazenadas temporariamente no cache. Quando o cache atinge um limite definido, todo o conteúdo é despejado no arquivo de uma só vez. Isso resulta em menos operações de leitura e gravação em disco, melhor desempenho e maior vida útil dos dispositivos de armazenamento.

Agora que entendemos o conceito de rotação de arquivos, vamos criar uma estrutura chamada MqlLogifyHandleFileConfig para armazenar todas as configurações da classe CLogifyHandlerFile. Essa estrutura será responsável por manter os parâmetros que definem como os logs serão gerenciados.

A primeira parte da estrutura envolverá a definição de enums para os tipos de rotação e extensões de arquivo a serem utilizadas:

//+------------------------------------------------------------------+
//| ENUMS for log rotation and file extension                        |
//+------------------------------------------------------------------+
enum ENUM_LOG_ROTATION_MODE
  {
   LOG_ROTATION_MODE_NONE = 0,       // No rotation
   LOG_ROTATION_MODE_DATE,           // Rotate based on date
   LOG_ROTATION_MODE_SIZE,           // Rotate based on file size
  };
enum ENUM_LOG_FILE_EXTENSION
  {
   LOG_FILE_EXTENSION_TXT = 0,       // .txt file
   LOG_FILE_EXTENSION_LOG,           // .log file
   LOG_FILE_EXTENSION_JSON,          // .json file
  };

A própria estrutura MqlLogifyHandleFileConfig conterá os seguintes parâmetros:

  • directory: Diretório onde os arquivos de log serão armazenados.
  • base_filename: Nome base do arquivo, sem a extensão.
  • file_extension: Tipo de extensão do arquivo de log (como .txt, .log ou .json).
  • rotation_mode: Modo de rotação de arquivos.
  • messages_per_flush: Número de mensagens de log a serem armazenadas em cache antes de serem gravadas no arquivo.
  • codepage: Codificação utilizada para os arquivos de log (como UTF-8 ou ANSI).
  • max_file_size_mb: Tamanho máximo de cada arquivo de log, caso a rotação seja baseada em tamanho.
  • max_file_count: Número máximo de arquivos de log a manter antes de excluir os mais antigos.

Além dos construtores e destrutores, adicionarei métodos auxiliares à estrutura para configurar cada um dos modos de rotação, projetados para tornar o processo de configuração mais prático e, acima de tudo, confiável. Esses métodos não existem apenas por elegância, eles garantem que nenhum detalhe crítico seja ignorado durante a configuração.

Por exemplo, se o modo de rotação estiver definido como por data ( LOG_ROTATION_MODE_DATE ), tentar configurar o atributo max_file_size_mb não faz sentido algum, afinal, esse parâmetro só é relevante no modo por tamanho ( LOG_ROTATION_MODE_SIZE ). O papel desses métodos é evitar inconsistências como essa, protegendo o sistema contra configurações inválidas.

Se, por acaso, um parâmetro essencial não for especificado, o sistema entra em ação. Ele pode preencher automaticamente um valor padrão, emitindo um aviso ao desenvolvedor, garantindo assim que o fluxo seja robusto e sem margem para surpresas desagradáveis.

Os métodos auxiliares que implementaremos são:

  • CreateNoRotationConfig(): Configuração para ausência de rotação de arquivos (todos os logs vão para o mesmo arquivo, sem rotação).
  • CreateDateRotationConfig(): Configuração para rotação baseada em data.
  • CreateSizeRotationConfig(): Configuração para rotação baseada no tamanho do arquivo.
  • ValidateConfig(): Método que valida se todas as configurações estão corretas e prontas para uso. (este é um método que será utilizado automaticamente pela classe e não pelo desenvolvedor que utilizará a biblioteca)

Aqui está a implementação completa da estrutura:

//+------------------------------------------------------------------+
//| Struct: MqlLogifyHandleFileConfig                                |
//+------------------------------------------------------------------+
struct MqlLogifyHandleFileConfig
  {
   string directory;                         // Directory for log files
   string base_filename;                     // Base file name
   ENUM_LOG_FILE_EXTENSION file_extension;   // File extension type
   ENUM_LOG_ROTATION_MODE rotation_mode;     // Rotation mode
   int messages_per_flush;                   // Messages before flushing
   uint codepage;                            // Encoding (e.g., UTF-8, ANSI)
   ulong max_file_size_mb;                   // Max file size in MB for rotation
   int max_file_count;                       // Max number of files before deletion
   
   //--- Default constructor
   MqlLogifyHandleFileConfig(void)
     {
      directory = "logs";                    // Default directory
      base_filename = "expert";              // Default base name
      file_extension = LOG_FILE_EXTENSION_LOG;// Default to .log extension
      rotation_mode = LOG_ROTATION_MODE_SIZE;// Default size-based rotation
      messages_per_flush = 100;              // Default flush threshold
      codepage = CP_UTF8;                    // Default UTF-8 encoding
      max_file_size_mb = 5;                  // Default max file size in MB
      max_file_count = 10;                   // Default max file count
     }

   //--- Destructor
   ~MqlLogifyHandleFileConfig(void)
     {
     }

   //--- Create configuration for no rotation
   void CreateNoRotationConfig(string base_name="expert", string dir="logs", ENUM_LOG_FILE_EXTENSION extension=LOG_FILE_EXTENSION_LOG, int msg_per_flush=100, uint cp=CP_UTF8)
     {
      directory = dir;
      base_filename = base_name;
      file_extension = extension;
      rotation_mode = LOG_ROTATION_MODE_NONE;
      messages_per_flush = msg_per_flush;
      codepage = cp;
     }

   //--- Create configuration for date-based rotation
   void CreateDateRotationConfig(string base_name="expert", string dir="logs", ENUM_LOG_FILE_EXTENSION extension=LOG_FILE_EXTENSION_LOG, int max_files=10, int msg_per_flush=100, uint cp=CP_UTF8)
     {
      directory = dir;
      base_filename = base_name;
      file_extension = extension;
      rotation_mode = LOG_ROTATION_MODE_DATE;
      messages_per_flush = msg_per_flush;
      codepage = cp;
      max_file_count = max_files;
     }

   //--- Create configuration for size-based rotation
   void CreateSizeRotationConfig(string base_name="expert", string dir="logs", ENUM_LOG_FILE_EXTENSION extension=LOG_FILE_EXTENSION_LOG, ulong max_size=5, int max_files=10, int msg_per_flush=100, uint cp=CP_UTF8)
     {
      directory = dir;
      base_filename = base_name;
      file_extension = extension;
      rotation_mode = LOG_ROTATION_MODE_SIZE;
      messages_per_flush = msg_per_flush;
      codepage = cp;
      max_file_size_mb = max_size;
      max_file_count = max_files;
     }
   
   //--- Validate configuration
   bool ValidateConfig(string &error_message)
     {
      //--- Saves the return value
      bool is_valid = true;
      
      //--- Check if the directory is not empty
      if(directory == "")
        {
         directory = "logs";
         error_message = "The directory cannot be empty.";
         is_valid = false;
        }
      
      //--- Check if the base filename is not empty
      if(base_filename == "")
        {
         base_filename = "expert";
         error_message = "The base filename cannot be empty.";
         is_valid = false;
        }
      
      //--- Check if the number of messages per flush is positive
      if(messages_per_flush <= 0)
        {
         messages_per_flush = 100;
         error_message = "The number of messages per flush must be greater than zero.";
         is_valid = false;
        }
      
      //--- Check if the codepage is valid (verify against expected values)
      if(codepage != CP_ACP
      && codepage != CP_MACCP
      && codepage != CP_OEMCP
      && codepage != CP_SYMBOL
      && codepage != CP_THREAD_ACP
      && codepage != CP_UTF7
      && codepage != CP_UTF8)
        {
         codepage = CP_UTF8;
         error_message = "The specified codepage is invalid.";
         is_valid = false;
        }
      
      //--- Validate limits for size-based rotation
      if(rotation_mode == LOG_ROTATION_MODE_SIZE)
        {
         if(max_file_size_mb <= 0)
           {
            max_file_size_mb = 5;
            error_message = "The maximum file size (in MB) must be greater than zero.";
            is_valid = false;
           }
         if(max_file_count <= 0)
           {
            max_file_count = 10;
            error_message = "The maximum number of files must be greater than zero.";
            is_valid = false;
           }
        }
      
      //--- Validate limits for date-based rotation
      if(rotation_mode == LOG_ROTATION_MODE_DATE)
        {
         if(max_file_count <= 0)
           {
            max_file_count = 10;
            error_message = "The maximum number of files for date-based rotation must be greater than zero.";
            is_valid = false;
           }
        }
   
      //--- No errors found
      error_message = "";
      return(is_valid);
     }
  };
//+------------------------------------------------------------------+

Um detalhe interessante que quero destacar aqui é como a função ValidateConfig() funciona. Ao analisar essa função, observe algo interessante: quando ela detecta um erro em algum valor de configuração, não é como se retornasse imediatamente false, indicando que algo deu errado. Ela age primeiro, tomando medidas corretivas para resolver o problema automaticamente, tudo isso antes de retornar um resultado definitivo.

Primeiro, ela redefine o valor inválido, retornando-o ao seu valor padrão. Isso, de certa forma, “corrige” temporariamente a configuração, sem deixar que o erro impeça o processo do programa de seguir seu fluxo. Em seguida, para não deixar a situação sem explicação, a função atribui uma mensagem detalhada, indicando claramente onde o erro ocorreu e o que precisa ser ajustado. E, por fim, a função marca a variável chamada is_valid como false , sinalizando que algo deu errado. Somente ao final, após tomar todas essas medidas, ela retorna essa variável, com o status final, que informará se a configuração passou e é válida ou não.

Mas o que torna isso ainda mais interessante é a forma como a função lida com múltiplos erros. Se houver mais de um valor incorreto ao mesmo tempo, ela não se concentra em corrigir apenas o primeiro erro encontrado, deixando os outros para depois. Pelo contrário, ela trata todos de uma vez, corrigindo tudo simultaneamente. Ao final, a função retorna a mensagem explicando qual foi o último erro corrigido, garantindo que nada fique de fora.

Esse tipo de abordagem é valioso e auxilia o trabalho do desenvolvedor. Durante o desenvolvimento de um sistema, é comum que alguns valores sejam definidos incorretamente ou por engano. A beleza aqui é que a função possui uma camada extra de segurança, corrigindo erros automaticamente, sem esperar que o programador os perceba um a um. Afinal, pequenos erros, se não tratados, podem causar falhas maiores — como, por exemplo, a falha ao salvar registros de log. Essa automação no tratamento de erros que criei acaba evitando que pequenas falhas interrompam o funcionamento do sistema, ajudando a manter tudo em execução.


Implementando a classe CLogifyHandlerFile

Temos a classe que já foi criada no artigo anterior, apenas faremos modificações para torná-la funcional. Aqui, detalharei cada ajuste realizado para garantir que você entenda como tudo funciona.

No escopo privado da classe, adicionamos variáveis e alguns métodos auxiliares importantes:

  1. Configuração: Criamos uma variável m_config do tipo MqlLogifyHandleFileConfig para armazenar as configurações relacionadas ao sistema de logging.
  2. Também implementei os métodos SetConfig() e GetConfig() para definir e acessar as configurações da classe.

Aqui está a estrutura inicial da classe, com as definições e métodos básicos:

//+------------------------------------------------------------------+
//| class : CLogifyHandlerFile                                       |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CLogifyHandlerFile                                 |
//| Heritage    : CLogifyHandler                                     |
//| Description : Log handler, inserts data into file, supports      |
//| rotation modes.                                                  |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyHandlerFile : public CLogifyHandler
  {
private:
   //--- Config
   MqlLogifyHandleFileConfig m_config;
   
public:
   //--- Configuration management
   void              SetConfig(MqlLogifyHandleFileConfig &config);
   MqlLogifyHandleFileConfig GetConfig(void);
  };
//+------------------------------------------------------------------+
//| Set configuration                                                |
//+------------------------------------------------------------------+
void CLogifyHandlerFile::SetConfig(MqlLogifyHandleFileConfig &config)
  {
   m_config = config;
   
   //--- Validade
   string err_msg = "";
   if(!m_config.ValidateConfig(err_msg))
     {
      Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: "+err_msg);
     }
  }
//+------------------------------------------------------------------+
//| Get configuration                                                |
//+------------------------------------------------------------------+
MqlLogifyHandleFileConfig CLogifyHandlerFile::GetConfig(void)
  {
   return(m_config);
  }
//+------------------------------------------------------------------+

Listarei os métodos auxiliares e explicarei com mais detalhes como eles funcionam. Implementei três métodos úteis que serão utilizados no gerenciamento de arquivos:

  1. LogFileExtensionToStr(): Converte o valor do enum ENUM_LOG_FILE_EXTENSION em uma string que representa a extensão do arquivo. O enum define os valores possíveis para o tipo de arquivo, como .log , .txt e .json .

    //+------------------------------------------------------------------+
    //| Convert log file extension enum to string                        |
    //+------------------------------------------------------------------+
    string CLogifyHandlerFile::LogFileExtensionToStr(ENUM_LOG_FILE_EXTENSION file_extension)
      {
       switch(file_extension)
         {
          case LOG_FILE_EXTENSION_LOG:
            return(".log");
          case LOG_FILE_EXTENSION_TXT:
            return(".txt");
          case LOG_FILE_EXTENSION_JSON:
            return(".json");
         }
       //--- Default return
       return(".txt");
      }
    //+------------------------------------------------------------------+

  2. LogPath(): Essa função é responsável por gerar o caminho completo do arquivo de log com base nas configurações atuais da classe. Primeiro, ela converte a extensão de arquivo configurada utilizando a função LogFileExtensionToStr(). Em seguida, verifica o tipo de rotação configurado. Se a rotação for baseada no tamanho do arquivo ou se não houver rotação, ela retorna apenas o nome do arquivo no diretório configurado. Se a rotação for baseada na data, ela inclui a data atual (formato YYYY-MM-DD) como prefixo no nome do arquivo.

    //+------------------------------------------------------------------+
    //| Generate log file path based on config                           |
    //+------------------------------------------------------------------+
    string CLogifyHandlerFile::LogPath(void)
      {
       string file_extension = this.LogFileExtensionToStr(m_config.file_extension);
       string base_name = m_config.base_filename + file_extension;
       
       if(m_config.rotation_mode == LOG_ROTATION_MODE_SIZE || m_config.rotation_mode == LOG_ROTATION_MODE_NONE)
         {
          return(m_config.directory + "\\\\" + base_name);
         }
       else if(m_config.rotation_mode == LOG_ROTATION_MODE_DATE)
         {
          MqlDateTime date;
          TimeCurrent(date);
          string date_str = IntegerToString(date.year) + "-" + IntegerToString(date.mon, 2, '0') + "-" + IntegerToString(date.day, 2, '0');
          base_name = date_str + (m_config.base_filename != "" ? "-" + m_config.base_filename : "") + file_extension;
          return(m_config.directory + "\\\\" + base_name);
         }
       
       //--- Default return
       return(base_name);
      }
    //+------------------------------------------------------------------+

O método Emit() é responsável por gravar as mensagens de log em um arquivo. No código atual, ele apenas exibe os logs no console do terminal. Vamos melhorar isso para que ele abra o arquivo de log, adicione uma nova linha com os dados formatados e feche o arquivo após a gravação. Se o arquivo não puder ser aberto, uma mensagem de erro será exibida no console.

void CLogifyHandlerFile::Emit(MqlLogifyModel &data)
  {
   //--- Checks if the configured level allows
   if(data.level >= this.GetLevel())
     {
      //--- Get the full path of the file
      string log_path = this.LogPath();
      
      //--- Open file
      ResetLastError();
                  int handle_file = FileOpen(log_path, FILE_READ|FILE_WRITE|FILE_ANSI, '\t', m_config.codepage);
                  if(handle_file == INVALID_HANDLE)
                    {
                     Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: Unable to open log file '"+log_path+"'. Certifique-se de que o diretório existe e tem permissão de escrita. (Code: "+IntegerToString(GetLastError())+")");
                     return;
                    }
      
      //--- Write
      FileSeek(handle_file, 0, SEEK_END);
      FileWrite(handle_file, data.formated);
      
      //--- Close file
      FileClose(handle_file);
     }
  }

Portanto, temos a versão mais simples da classe adicionando os logs a um arquivo; vamos realizar alguns testes simples para verificar se o básico está funcionando corretamente.


Primeiro teste com arquivos

Utilizaremos o arquivo de teste que já usamos nos exemplos anteriores, LogifyTest.mqh . O objetivo é configurar o sistema de logging para salvar registros em arquivos, utilizando a classe base CLogify e o handler de arquivos que acabamos de implementar.

  1. Criamos uma variável do tipo MqlLogifyHandleFileConfig para armazenar as configurações específicas do handler de arquivos.
  2. Configuramos o handler para utilizar o formato e as regras desejadas, como rotação de arquivos por tamanho.
  3. Adicionamos esse handler à classe base CLogify.
  4. Configuramos um formatter para determinar como cada registro será exibido no arquivo.

Veja o código completo:

//+------------------------------------------------------------------+
//| Import CLogify                                                   |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Configs
   MqlLogifyHandleFileConfig m_config;
   m_config.CreateSizeRotationConfig("expert","logs",LOG_FILE_EXTENSION_LOG,5,5,10);
   
   //--- Handler File
   CLogifyHandlerFile *handler_file = new CLogifyHandlerFile();
   handler_file.SetConfig(m_config);
   handler_file.SetLevel(LOG_LEVEL_DEBUG);
   
   //--- Add handler in base class
   logify.AddHandler(handler_file);
   logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","{date_time} [{levelname}] {msg}"));
   
   //--- Using logs
   logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14");
   logify.Infor("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1");
   logify.Alert("Stop Loss adjusted to breakeven level", "Risk Management", "Order ID: 12345678");
   logify.Error("Failed to send sell order", "Order Management", "Reason: Insufficient balance");
   logify.Fatal("Failed to initialize EA: Invalid settings", "Initialization", "Missing or incorrect parameters");
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Ao executar o código acima, um novo arquivo de log será criado no diretório configurado ( logs ). Ele pode ser visualizado no navegador de arquivos.

Ao abrir o arquivo no Bloco de Notas ou em qualquer editor de texto, veremos o conteúdo gerado pelas mensagens de teste:

Antes de avançar para as melhorias, vou realizar um teste de desempenho para entender o quanto isso impacta a performance, de modo que tenhamos uma referência para comparação posterior. Dentro da função OnTick() vou adicionar um registro ao log, de forma que, a cada novo tick, o arquivo de log seja aberto, escrito e fechado.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Logs
   logify.Debug("Debug Message");
  }
//+------------------------------------------------------------------+

Utilizarei o testador de estratégias para realizar este teste, mesmo no backtest o sistema de criação de arquivos funciona normalmente, porém os arquivos são salvos em outra pasta; mais adiante mostrarei como acessá-la. O teste será realizado com as seguintes configurações:

Considerando a modelagem “OHLC para 1 minuto”, no símbolo EURUSD, com 7 dias de teste, foram necessários 5 minutos e 11 segundos para concluir o teste, considerando que a cada tick um novo registro de log é gerado e salvo imediatamente no arquivo.


Testando com arquivos JSON

Por fim, quero mostrar o uso de arquivos de log em JSON na prática, pois eles podem ser úteis em alguns cenários específicos. Para salvar em JSON, basta alterar o tipo de arquivo nas configurações e definir um formatter válido para o formato JSON; aqui está um exemplo de implementação:

//+------------------------------------------------------------------+
//| Import CLogify                                                   |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Configs
   MqlLogifyHandleFileConfig m_config;
   m_config.CreateSizeRotationConfig("expert","logs",LOG_FILE_EXTENSION_JSON,5,5,10);
   
   //--- Handler File
   CLogifyHandlerFile *handler_file = new CLogifyHandlerFile();
   handler_file.SetConfig(m_config);
   handler_file.SetLevel(LOG_LEVEL_DEBUG);
   
   //--- Add handler in base class
   logify.AddHandler(handler_file);
   logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","{\\"datetime\\":\\"{date_time}\\", \\"level\\":\\"{levelname}\\", \\"msg\\":\\"{msg}\\"}"));
   
   //--- Using logs
   logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14");
   logify.Infor("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1");
   logify.Alert("Stop Loss adjusted to breakeven level", "Risk Management", "Order ID: 12345678");
   logify.Error("Failed to send sell order", "Order Management", "Reason: Insufficient balance");
   logify.Fatal("Failed to initialize EA: Invalid settings", "Initialization", "Missing or incorrect parameters");
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Com as mesmas mensagens de log, este é o resultado do arquivo após executar o expert no gráfico:

{"datetime":"08:24:10", "level":"DEBUG", "msg":"RSI indicator value calculated: 72.56"}
{"datetime":"08:24:10", "level":"INFOR", "msg":"Buy order sent successfully"}
{"datetime":"08:24:10", "level":"ALERT", "msg":"Stop Loss adjusted to breakeven level"}
{"datetime":"08:24:10", "level":"ERROR", "msg":"Failed to send sell order"}
{"datetime":"08:24:10", "level":"FATAL", "msg":"Failed to initialize EA: Invalid settings"}


Conclusão

Neste artigo, apresentamos um guia prático e detalhado sobre como realizar operações básicas com arquivos: abrir, manipular o conteúdo e, por fim, fechar o arquivo de forma simples. Também discuti a importância de configurar a estrutura do “handler”; por meio dessa configuração, é possível adaptar diversas características, como o tipo de arquivo a ser utilizado (por exemplo, texto, log ou até json) e o diretório no qual o arquivo será salvo, tornando a biblioteca muito flexível.

Além disso, realizamos melhorias específicas na classe chamada CLogifyHandlerFile. Essas alterações tornaram possível registrar cada mensagem diretamente em um arquivo de log. Após essa implementação, como parte do estudo, também realizei um teste de desempenho para medir a eficiência da solução. Utilizamos um cenário específico, no qual o sistema simulou a execução de uma estratégia de negociação no ativo EURUSD por um período de uma semana. Durante esse teste, um registro de log foi gerado a cada novo “tick” do mercado. Esse processo é extremamente intensivo, pois cada variação no preço do ativo exige que uma nova linha seja salva no arquivo.

O resultado final foi registrado: todo o processo levou 5 minutos e 11 segundos para ser concluído. Esse resultado servirá como ponto de referência para o próximo artigo, no qual implementaremos um sistema de cache (memória temporária). O objetivo do cache é armazenar temporariamente os registros, eliminando a necessidade de acessar o arquivo constantemente e melhorando o desempenho geral.

Fique atento ao próximo artigo, onde exploraremos técnicas ainda mais avançadas para aumentar a eficiência e o desempenho do sistema. Até lá!

Nome do Arquivo
Descrição
Experts/Logify/LogiftTest.mq5
Arquivo onde testamos os recursos da biblioteca, contendo um exemplo prático
Include/Logify/Formatter/LogifyFormatter.mqh
Classe responsável por formatar os registros de log, substituindo placeholders por valores específicos
Include/Logify/Handlers/LogifyHandler.mqh
Classe base para gerenciar manipuladores de logs, incluindo configuração de níveis e envio de logs.
Include/Logify/Handlers/LogifyHandlerConsole.mqh
Handler de log que envia logs formatados diretamente para o console do terminal no MetaTrader
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
Handler de log que envia logs formatados para um banco de dados (atualmente contém apenas uma impressão, mas em breve salvaremos em um banco sqlite real)
Include/Logify/Handlers/LogifyHandlerFile.mqh
Handler de log que envia logs formatados para um arquivo
Include/Logify/Logify.mqh
Classe central para gerenciamento de logs, integrando níveis, modelos e formatação
Include/Logify/LogifyLevel.mqh
Arquivo que define os níveis de log da biblioteca Logify, permitindo controle detalhado
Include/Logify/LogifyModel.mqh
Estrutura que modela os registros de log, incluindo detalhes como nível, mensagem, timestamp e contexto

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

Arquivos anexados |
Logify0Part40.zip (14.21 KB)
Últimos Comentários | Ir para discussão (1)
Alpha Dolcy
Alpha Dolcy | 29 jan. 2025 em 13:29
Parece ser uma busca que vale a pena, especialmente para testes de retorno e otimização.
A Estratégia de Negociação do Inverse Fair Value Gap A Estratégia de Negociação do Inverse Fair Value Gap
Um inverse fair value gap (IFVG) ocorre quando o preço retorna a um fair value gap previamente identificado e, em vez de apresentar a reação esperada de suporte ou resistência, falha em respeitá-lo. Essa falha pode sinalizar uma possível mudança na direção do mercado e oferecer uma vantagem contrária de negociação. Neste artigo, vou apresentar minha abordagem desenvolvida por mim para quantificar e utilizar o inverse fair value gap como uma estratégia para expert advisors do MetaTrader 5.
Construindo Expert Advisors Auto-Otimizáveis em MQL5 (Parte 4): Dimensionamento Dinâmico de Posição Construindo Expert Advisors Auto-Otimizáveis em MQL5 (Parte 4): Dimensionamento Dinâmico de Posição
Empregar com sucesso o trading algorítmico exige aprendizado contínuo e interdisciplinar. No entanto, a gama infinita de possibilidades pode consumir anos de esforço sem gerar resultados tangíveis. Para lidar com isso, propomos uma estrutura que introduz complexidade de forma gradual, permitindo que os traders refinem suas estratégias de maneira iterativa, em vez de dedicar tempo indefinido a resultados incertos.
Mecanismos de gating em aprendizado por ensemble Mecanismos de gating em aprendizado por ensemble
Neste artigo, continuamos nossa exploração de modelos ensemble discutindo o conceito de gates, especificamente como eles podem ser úteis na combinação das saídas dos modelos para aprimorar a precisão das previsões ou a generalização do modelo.
Modelos ocultos de Markov em sistemas de trading com aprendizado de máquina Modelos ocultos de Markov em sistemas de trading com aprendizado de máquina
Os modelos ocultos de Markov (HMM) representam uma classe poderosa de modelos probabilísticos, destinados à análise de dados sequenciais, nos quais os eventos observáveis dependem de alguma sequência de estados não observáveis (ocultos), que formam um processo de Markov. As principais suposições dos HMM incluem a propriedade de Markov para os estados ocultos, o que significa que a probabilidade de transição para o próximo estado depende apenas do estado atual, e a independência das observações, desde que o estado oculto atual seja conhecido.