Rotina básica para captura de exceções

DAVID CANDIDO DA SILVA

DAVID CANDIDO DA SILVA Publicado 16/11/2017 


Olá
Estávamos compartilhando algumas rotinas para capturar o máximo de informações de uma exceção ocorrida na aplicação, de forma que facilite o rastreamento e a correção do erro. Este compartilhamento resultou no artigo de hoje, no qual apresento a criação de uma rotina básica – porém, útil – para captura de exceções em uma aplicação.

 

Em algumas ocasiões, temos dificuldade em reproduzir os erros que acontecem em produção em nossas aplicações, muitas vezes pela massa de dados, configuração do ambiente ou simplesmente por conta de uma sequência específica de passos que provocam a exceção. Gravar um vídeo com a ocorrência do erro é uma alternativa, no entanto, nem sempre viável. É possível que o usuário não consiga refazer os passos que causou a exceção, ou as informações contidas no próprio vídeo não sejam suficientes para identificar o problema.

Em vista desse contexto, elaborar uma rotina para captura de exceções é uma solução bem coerente. De modo geral, a rotina atua como um listener, monitorando as exceções que ocorrem no sistema e gravando-as em um arquivo de texto ou em uma tabela do banco de dados para posterior análise da equipe de desenvolvimento.
Atualmente, com o Delphi, o desenvolvedor pode optar pela instalação de excelentes ferramentas para essa finalidade, como o EurekaLog ou madExcept, que fornecem uma série de recursos para captura de exceções. Porém, alguns desenvolvedores preferem criar suas próprias rotinas para reduzir ou evitar a instalação de componentes de terceiros. Este artigo é para estes desenvolvedores!
Codificaremos, a seguir, uma rotina simples que armazenará as seguintes informações ao ocorrer uma exceção:

  • Data e hora
  • Mensagem da exceção
  • Classe da exceção
  • Nome do formulário aberto
  • Nome da Unit
  • Nome do controle visual focado
  • Nome do usuário
  • Versão do Windows
  • Imagem do formulário

Com todas essas informações, o rastreamento torna-se bem mais fácil, não? 

Acho importante iniciarmos pelos métodos que retornam as três últimas informações, já que exigem uma codificação extra. O primeiro deles, que retorna o nome do usuário, é bem simples:

function TForm1.ObterNomeUsuario: string;
var
  Size: DWord;
begin
  // retorna o login do usuário do sistema operacional
  Size := 1024;
  SetLength(result, Size);
  GetUserName(PChar(result), Size);
  SetLength(result, Size - 1);
end;

 

Para obter a versão do Windows, podemos trabalhar com a classe TOSVersionInfo nativa do Delphi, analisando os resultados do número de build do sistema operacional:

function TForm1.ObterVersaoWindows: string;
begin
  case System.SysUtils.Win32MajorVersion of
    5:
      case System.SysUtils.Win32MinorVersion of
        1: result := 'Windows XP';
      end;
    6:
      case System.SysUtils.Win32MinorVersion of
        0: result := 'Windows Vista';
        1: result := 'Windows 7';
        2: result := 'Windows 8';
        3: result := 'Windows 8.1';
      end;
    10:
      case System.SysUtils.Win32MinorVersion of
        0: result := 'Windows 10';
      end;
  end;
end;

 

A captura da imagem do formulário, por sua vez, também é uma rotina pequena, responsável por associar a imagem dentro de um objeto do tipo TBitmap. No entanto, para que o tamanho do arquivo da imagem fique menor, recomendo a gravação do arquivo em formato JPEG utilizando um objeto da classe TJpegImage, conforme abaixo:

uses
  Vcl.Imaging.jpeg;
 
procedure TForm1.GravarImagemFormulario(const NomeArquivo: string);
var
  Bitmap: TBitmap;
  JPEG: TJpegImage;
begin
  Bitmap := TBitmap.Create;
  JPEG := TJpegImage.Create;
  try
    Bitmap.Assign(Formulario.GetFormImage);
    JPEG.Assign(Bitmap);
    JPEG.SaveToFile(Format('%s\%s.jpg', [GetCurrentDir, NomeArquivo]));
  finally
    JPEG.Free;
    Bitmap.Free;
  end;
end;

 

Com as três funções acima codificadas, partiremos para a parte principal. Adicione um componente TApplicationEvents, da paleta Additional, no formulário principal do sistema.

Componente TApplicationEvents

Por fim, codificaremos o evento OnException do componente para “interceptar” a exceção e obter os dados da aplicação naquele momento.
Observe que vamos declarar uma variável chamada “DataHora”. Usaremos essa variável como nome dos arquivos das imagens, de forma que o desenvolvedor possa descobrir de qual exceção cada imagem está relacionada.

var
  CaminhoArquivoLog: string;
  ArquivoLog: TextFile;
  DataHora: string;
begin
  // Obtém o caminho do arquivo de log
  CaminhoArquivoLog := GetCurrentDir + '\LogExcecoes.txt';
    // Associa o arquivo à variável "ArquivoLog"
  AssignFile(ArquivoLog, CaminhoArquivoLog);
    // Se o arquivo existir, abre para edição,
  // Caso contrário, cria o arquivo
  if FileExists(CaminhoArquivoLog) then
    Append(ArquivoLog)
  else
    ReWrite(ArquivoLog);
    // Obtém a data e hora atual para usar como "identificador" da imagem
  DataHora := FormatDateTime('dd-mm-yyyy_hh-nn-ss', Now);
    // Chama o método de captura da imagem do formulário
  GravarImagemFormulario(DataHora);
    // Escreve os dados no arquivo de log
  WriteLn(ArquivoLog, 'Data/Hora.......: ' + DateTimeToStr(Now));
  WriteLn(ArquivoLog, 'Mensagem........: ' + E.Message);
  WriteLn(ArquivoLog, 'Classe Exceção..: ' + E.ClassName);
  WriteLn(ArquivoLog, 'Formulário......: ' + Screen.ActiveForm.Name);
  WriteLn(ArquivoLog, 'Unit............: ' + Sender.UnitName);
  WriteLn(ArquivoLog, 'Controle Visual.: ' + Screen.ActiveControl.Name);
  WriteLn(ArquivoLog, 'Usuário.........: ' + ObterNomeUsuario);
  WriteLn(ArquivoLog, 'Versão Windows..: ' + ObterVersaoWindows);
  WriteLn(ArquivoLog, StringOfChar('-', 70));
    // Fecha o arquivo
  CloseFile(ArquivoLog);
end;

 

É isso aí! A nossa rotina de captura de exceções está pronta!
Para testá-la, adicione um botão na tela e provoque uma exceção, como uma conversão incorreta:

StrToInt('A');

 

Em seguida, navegue até a pasta da aplicação e verifique que dois arquivos foram criados: “LogExcecao.txt” e o arquivo de imagem. Para cada exceção subsequente, a aplicação criará uma nova imagem e atualizará o arquivo de log com os dados da nova exceção. Legal, hein?
Apenas uma observação: para que os testes sejam mais efetivos, até mesmo em função da captura correta da imagem da tela, execute a aplicação fora do Delphi, como se estivesse em produção.

Bom, embora toda essa codificação já seja o suficiente, trago ainda algumas sugestões que possam ser úteis:

1) Mostre uma mensagem após gravar a exceção em log
Você deve ter notado que, ao ocorrer uma exceção, os arquivos são criados, mas nenhuma mensagem é exibida ao usuário, já que “interceptamos” a exceção. Esse comportamento, claro, não é o ideal, já que o usuário pode julgar que a aplicação está funcionando normalmente. Portanto, no final do evento OnException, recomendo adicionar uma mensagem para alertar o usuário de que houve um evento inesperado:

var
  StringBuilder: TStringBuilder;
begin     {...}
    StringBuilder := TStringBuilder.Create;
  try
    // Exibe a mensagem para o usuário
    StringBuilder       .AppendLine('Ocorreu um erro na aplicação.')
      .AppendLine('O problema será analisado pelos desenvolvedores.')
      .AppendLine(EmptyStr)
      .AppendLine('Descrição técnica:')
      .AppendLine(E.Message);
      MessageDlg(StringBuilder.ToString, mtWarning, [mbOK], 0);
  finally
    StringBuilder.Free;
  end;
end;

 

2) Envie um e-mail periodicamente contendo o arquivo de log e as imagens
Eu adicionei essa opção em um dos meus projetos. Dado um período, que pode ser diário, semanal, mensal ou sob demanda, a aplicação envia um e-mail para o meu endereço com o log e as imagens compactadas em um único arquivo. Com isso em mãos, consigo identificar os erros e aplicar as correções com mais agilidade, às vezes de forma até antecipada.
Para codificar essa funcionalidade, pode-se criar uma rotina de envio de e-mails com o TIdSMTP e dispará-la em uma Thread paralela com o TTask para não “travar” o usuário.

uses
  System.Threading;
 
var
  EnvioEmail: ITask;
begin
  EnvioEmail := TTask.Create(
    procedure
    begin
      EnviarEmailComLogs;
    end);
  EnvioEmail.Start;
end;

 

3) Grave as exceções em uma tabela do banco de dados
Uma boa alternativa é gravar as exceções em uma tabela do banco de dados ao invés de um arquivo de log. Neste caso, o desenvolvedor poderá desenhar uma tela para visualização das exceções em uma Grid, semelhante ao que o Daniel Serafim desenvolveu em seu projeto (boa, Daniel!):

Exemplo de visualização de exceções registradas no banco de dados

 4) Crie uma classe para atuar como wrapper da exceção
Os códigos desse artigo são apenas demonstrações. Caso você realmente considere agregá-los à sua aplicação, crie uma classe específica para escrever os dados no arquivo de log, capturar as imagens e, opcionalmente, enviar e-mails. Lembre-se do princípio da responsabilidade única!

 

Surgiu alguma dúvida nos códigos? Baixe o projeto de exemplo deste artigo desenvolvido em Delphi Tokyo no link abaixo e estude as codificações!

Download do projeto de exemplo de captura de exceções

 

Grande abraço, pessoal!

 

Voltar ao topo