Pesquisar

domingo, 14 de junho de 2020

Criação de Logs em aplicações Delphi e um pouco sobre Seções Crítica (TCriticalSection)

Bom dia a todos...

Tenho observado que alguns desenvolvedores ainda possuem dúvidas na criação de logs em seus aplicativos.

Neste post, objetivo mostrar como é extremamente fácil a criação desse recurso em aplicações delphi.
Embora os exemples sejam referentes a VCL, não existe um motivo prático para que os mesmos não funcionem em FMX (tanto em android, iOS ou qualquer outra tecnologia que a Embarcadero deva disponibilizar).

Bom, mãos a obra!

Primeiramente clone o meu repositório de exemplos la do GitHub (https://github.com/maicodalri/delphi_samples), mais precisamente a pasta "ThreadAndFileTextLog"!

Este exemplo aborta tanto a criação de Logs quando o uso de Threads (provavelmente a minha próxima postagem sera relacionado a elas).

Existem literalmente centenas de modos de escrevermos logs em aplicações, neste exemplo vou abordar a criação de logs em arquivos texto, pois acredito que seja um dos mais simples.

Eu gosto de organizar meus códigos em arquivos e classes estáticas separados, para que possam ser utilizado em outras aplicações, compartilhando o código. Por esse motivo na unit "MyUtils.FileTextLogp.pas" existe uma classe estática chamada  "TFileTextLog "!

Ela é a responsável por escrever o log no arquivo. Um dos benefícios de utilizar classes estáticas é que podemos organizar o código de modo a entender na prática como tudo funciona.

Já que a classe possui apenas 3 métodos, vamos agora comentar cada um deles:

class constructor ClassCreate;

Esse é o construtor estático da classe, observem que no início da declaração existe a palavra reservada "class", que indica é isso é o método estático da classe e não do objeto (como se trata de uma classe estática, não é necessário instanciá-la para usá-la).

Observem também que o nome ClassCreate não precisa ser este, assim como um construtor normal, ele pode ser absolutamente qualquer coisa, poderia ser "class constructor Xurumela" que funcionaria do mesmo modo, mas eu deixo ele como ClassCreate por questão de entendimento e organização.

Lembrando que construtores de classes são executados mesmo antes do "begin" la do ".dpr", ou seja, antes do código da aplicação iniciar. Isso é importante caso necessitemos realizar algum processo para utilizar em nossas classes estáticas.

Neste nosso exemplo, é executado apenas um comnado: 
"TFileTextLog.FCriticalSection:= TCriticalSection.Create;"

A criação de uma seção crítica na nossa variável da classe "TFileTextLog.FCriticalSection". Daqui a pouco explico um pouco mais sobre isso.

class destructor ClassDestroy;

O destrutor é exatamente o contrário do nosso "class create", é o código que deve ser executado após o encerramento da nossa aplicação, para que não haja vazamentos de memória.
De novo, eu poderia ter escolhido qualquer nome, mas deixei o nome de "ClassDestroy" para melhor organização e entendimento.

Novamente, neste trecho apenas liberamos o objeto já criado no "class construtor":
"TFileTextLog.FCriticalSection.Free;"

Outro fato importante referente aos construtores e destrutores de classes é que eles ocorrem antes do "initialization" e "finalization" de todas as units! E também ocorrem na ordem em que são declarados no arquivo do projeto (.dpr).

class procedure WriteLog(const lText: string);

Aqui fica a cereja do bolo! É onde de fato ocorre a gravação das informações seja la onde for mais interessaste para vocês, com eu ja havia dito, neste exemplos vamos criar um log em arquivo texto.

Existem basicamente 2 métodos de Ler/Escrever em arquivos texto no delphi de modo muito fácil, utilizando o objeto StringList ou escrevendo diretamente no arquivo via SO.

Pela velocidade, visto que esse log deve ser rápido e muito leve, optei por não utilizar o objeto StringlList, assumindo para mim mesmo a responsabilidade de travar o arquivo no SO e libera-lo quando terminar de utiliza-lo.

Vamos ao código:

 var  
  lFileLogName: string;  
  lFile: TextFile;  
 begin  

  //Entrado na seção cítica 
  TFileTextLog.FCriticalSection.Enter;  

  try //Bloco protegido

    //Definindo o nome do arquivo que sera gravado, sempre atribuo o arquivo de log
    // o mesmo nome do EXE só que com a extensão LOG
    lFileLogName:= ChangeFileExt(ParamStr(0), '.log');  

    //Vinculo a variável lFile ao nome do arquivo, basicamente eu obtenho um ID (handle)
    // do SO para este arquivo
    AssignFile(lFile, lFileLogName);
   
    //Crio outro bloco protegido para fazer a liberação do arquivo para o SO
    try
      //Verifico se o arquivo ja existe 
    if not FileExists(lFileLogName) then
     Rewrite(lFile) //Se não existe, então cria e abre o arquivo para edição
    else  
     Append(lFile); //Senão apenas abro o arquivo que ja existe para edução

    //Simplesmente escrevo a linha desejada no final do arquivo
    WriteLn(lFile, FormatDateTime('dd/mm/yyyy hh:mm:ss:zzz', Now) + ' => ' + lText);

    finally  

      //Fecho o aquivo, informo ao SO que não sera mais utilizado (desbloqueio ele) 
    CloseFile(lFile);

    end;  

  finally

   //Libero a seção crítica
   TFileTextLog.FCriticalSection.Release;  

  end;  

 end;
Para utilizarmos o método basta escrevermos:

TFileTextLog.WriteLog('Informação que desejamos gravar');

Isso mesmo, sem a necessidade de criarmos objetos, como é uma classe estática, basta eu simplesmente escrever o nome da classe e chamar o métodos.

Um pouco sobre Seções Críticas

Neste exemplo, como puderam perceber, utilizei uma classe nativa do delphi chamada TCriticalSection da unit System.SyncObjs.

Basicamente, uma seção crítica funcionam como um portão onde o processamento só pode entrar no bloco protegido se o processamento anterior já concluiu sua execução.

Neste nosso exemplo, como utilizamos Thread, não ha como garantir que se uma Thread chamar o nosso método WriteLog sem que outra Thread no mesmo momento já esteja escrevendo o Log.

Imagine que quando abrimos o arquivo para edição com o comando "Append" ou "Rewrite", outra Thread também pode tentar realizar o mesmo processo, a segunda Thread recebera um aviso do SO dizendo que o arquivo já esta em uso, e o delphi ira nos reportar uma exceção (Exception).

A função da seção crítica garante que apenas um processo ira ocorrer no bloco protegido por fez, ou seja, o arquivo só sera aberto para escrita se outro processo já o fechou.

Claro, que o exemplo aqui é utilizado com a abertura de um arquivo texto, mas pode ser referente a qualquer acesso a recurso do sistema que não possa ser interrompido, como por exemplo a consulta em um banco de dados, ou a leitura de alguma porta física do equipamento, absolutamente qualquer coisa neste sentido.

Outro ponto importante sobre seções críticas é que SEMPRE o bloco protegido deve estar entre um TRY e FINALLY, pois caso ocorra algum problema não previsto, a seção crítica ficar literalmente pendurada, e nenhum outro processamento terá acesso ao código protegido, na prática isso dará a impressão de que a aplicação ficou travada.

Algo parecido que fiz com a abertura do arquivo, logo a pós a abertura do arquivo, existe um TRY para proteger o código e depois de utilizado existe um FINALLY, garantindo que não importa o que acontecer, o arquivo, sera liberado para o SO (CloseFile).

Espero que os comentários sejam suficientes para que todos entendam o código, qualquer dúvida, por favor não deixem de comentar que eu assim que possível responda.

Espero que essas informações seja úteis a vocês.

Nenhum comentário:

Postar um comentário