
Última atualização em 9 de outubro de 2024 por Willian Tuttoilmondo
Como efetuar registros de log em aplicações Delphi de forma segura
Uma das maiores dificuldades em se desenvolver aplicações Datasnap, como a que desenvolvemos aqui, é a complexidade para efetuar tarefas simples, como o debug. Aplicações RESTful desenvolvidas como módulos do Apache ou IIS não podem ser debugadas tão facilmente, o que nos obriga a efetuar o registro de log em aplicações Delphi desenvolvidas desta forma para podermos acompanhar seu processamento. Muitas vezes, há uma alta concorrência para acesso a recursos, como threads executando requisições em paralelo, o que inviabiliza o uso simples de uma rotina de abertura, escrita e salvamento do arquivo.
Em ambientes como esses, o melhor é utilizar estruturas que façam o controle do acesso ao arquivo de log. Uma delas é o TMultiReadExclusiveWriteSynchronizer, disponibilizada há muitos anos em versões antigas do Delphi. Essa estrutura “semaforiza” o acesso a recursos como variáveis, ponteiros ou recursos do sistema operacional, evitando o conflito entre leituras e escritas simultâneas por threads concorrentes dentro da mesma aplicação.
Mas antes de implementarmos nossa semaforização, vamos começar pelo nosso registro de log.
Abre, escreve e fecha. Fácil!
Para o efetivo registro de log em aplicações Delphi é necessário que esse processo seja simples, rápido e não gere transtornos ao desenvolvedor. Para isso, vamos desenvolver uma classe que nos ajudará a escrever o log sem muita complicação. E como gostamos de uma arquitetura mais bem elaborada, essa classe terá seu próprio singleton, o qual estará disponível para uso em qualquer parte da aplicação. Se você não lembra o que é um singleton, uma leitura sobre padrões de projeto em Delphi pode ajudar.
Ao contrário dos demais artigos, não construiremos a classe passo-a-passo, mas já trarei uma classe pronta, explicando apenas suas funcionalidades e particularidades ao longo do artigo. Ela, em si, é muito simples, por isso não a construiremos. Com ela, veremos que o registro de log em aplicações Delphi é algo realmente muito simples. Então, vamos lá!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | unit Logs.Register; interface uses System.Classes, System.SysUtils; type TLog = class public class function Instance: TLog; virtual; function Load(const ALogFile: string; const AStrings: TStrings): Boolean; virtual; function WriteLog(const ALogFile, AMessage: string; const ACanCreate: Boolean = True; const AClearBeforeWrite: Boolean = False; const AUseDateTimeInLog: Boolean = True): Boolean; end; implementation var FInstance: TLog; { TLog } class function TLog.Instance: TLog; begin Result := FInstance; end; function TLog.Load(const ALogFile: string; const AStrings: TStrings): Boolean; var stgLog: TStrings; begin Result := False; if not (Assigned(AStrings) and FileExists(ALogFile)) then begin Exit; end; stgLog := TStringList.Create; try stgLog.LoadFromFile(ALogFile); if CompareText(AStrings.Text, stgLog.Text) <> 0 then begin AStrings.Text := stgLog.Text; end; Result := True; finally FreeAndNil(stgLog); end; end; function TLog.WriteLog(const ALogFile, AMessage: string; const ACanCreate, AClearBeforeWrite, AUseDateTimeInLog: Boolean): Boolean; var filLog: TextFile; strLog: string; strLogDir: string; const LOG_MESSAGE = '%s - %s'; DATE_FORMAT = 'YYYY-MM-DD HH:NN:SS'; begin Result := False; strLogDir := IncludeTrailingPathDelimiter(ExtractFilePath(ALogFile)); if not (FileExists(ALogFile) or ACanCreate) then begin Exit; end; if not (DirectoryExists(strLogDir) or ForceDirectories(strLogDir)) then begin Exit; end; AssignFile(filLog, ALogFile); case FileExists(ALogFile) and (not AClearBeforeWrite) of True : Append(filLog); False: Rewrite(filLog); end; case AUseDateTimeInLog of True : strLog := Format(LOG_MESSAGE, [FormatDateTime(DATE_FORMAT, Now), AMessage]); False: strLog := AMessage; end; Writeln(filLog, strLog); CloseFile(filLog); Result := True; end; initialization FInstance := TLog.Create; finalization FreeAndNil(FInstance); end. |
Como podemos observar, a classe possui apenas três métodos:
- Instance: método estático que retorna a instância do singleton ativo na aplicação;
- Load: método que carrega o conteúdo do log em um TStrings e;
- WriteLog: método que escreve a entrada de log em seu respectivo arquivo.
Por ser estático e já termos visto como funciona um singleton, o método Instance não será revisto. Como já falamos disso anteriormente, se você se sentir perdido, basta voltar aqui e reler sobre o assunto.
Lendo o arquivo de log em aplicações Delphi
Se ao método Instance não dedicamos um tempo pois já discutimos como ele funciona anteriormente, já o método Load merece um pouco mais de atenção. Nele temos os seguintes parâmetros:
- ALogFile: string contendo o caminho completo para o arquivo de log ;
- AStrings: TStrings que receberá o conteúdo do arquivo de log.
Como seu retorno é um boolean, ele indica se conseguiu – resultado True – ou não – resultado False – acessar e ler o arquivo. Ou seja, além de ler – ou não – o arquivo de log, o método indica se houve ou não erro em sua tentativa de leitura. Vamos dar uma olhada nele.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | function TLog.Load(const ALogFile: string; const AStrings: TStrings): Boolean; var stgLog: TStrings; begin Result := False; if not (Assigned(AStrings) and FileExists(ALogFile)) then begin Exit; end; stgLog := TStringList.Create; try stgLog.LoadFromFile(ALogFile); if CompareText(AStrings.Text, stgLog.Text) <> 0 then begin AStrings.Text := stgLog.Text; end; Result := True; finally stgLog.DisposeOf; end; end; |
Como vimos, já no início, definimos o retorno padrão como False, já que, em caso de qualquer erro na leitura de log em aplicações Delphi, o método interromperá seu processamento. Um desses erros é verificar se o parâmetro AStrings está assinalado ou se o arquivo de log efetivamente existe. Caso um ou outro não corresponda ao esperado, fazemos a saída do método, retornando o valor padrão.
Você deve ter notado que a sintaxe da condição é um pouco diferente. Ao invés de if (not Assigned(AStrings)) or (not FileExists(ALogFile)) then, usamos if not (Assigned(AStrings) and FileExists(ALogFile)) then. Isso se deve a um princípio da lógica matemática chamado Teorema de De Morgan, que me diz que a soma de duas negações é igual à negação da multiplicação dos mesmos dois termos ((¬A + ¬B) = ¬(A * B)) e vice-versa. Acreditem em mim, isso, em termos computacionais, é bem mais rápido para ser executado.
Em seguida, criamos um TStringList, o qual lerá o conteúdo do arquivo. Em seguida, dentro de um bloco try..finally, carregamos o conteúdo do arquivo de log no TStringList através do método LoadFromFile. Já com CompareText comparamos o conteúdo do TStrings fornecido como parâmetro com o conteúdo do TStringList recém carregado. Qualquer valor diferente de zero indica que os conteúdos são diferentes e, se esse for o caso, o TStrings recebe o conteúdo do TStringList.
Por fim, atribuímos o valor True ao resultado, uma vez que a leitura foi bem sucedida. Por fim, na seção finally, dispensamos a instância do TStringList e encerramos o método.
Gravando registros de log em aplicações Delphi
Assim como analisamos o método Load, vamos analisar o método WriteLog, que é o método responsável pelo registro de log em aplicações Delphi. Para ele, foram estabelecidos os seguintes métodos:
- ALogFile: caminho completo para o arquivo de log;
- AMessage: a mensagem que se deseja registrar no log;
- ACanCreate: flag que indica se a classe pode criar o arquivo de log se ele não existir previamente (o valor padrão é True);
- AClearBeforeWrite: flag que indica se a classe deve “limpar” o arquivo antes de gravar o novo registro (o valor padrão é False) e;
- AUseDateTimeInLog: flag que indica se o log deve ou não ser registrado com a data e hora no registro (o valor padrão é True).
Assim como o método Load, seu retorno é um boolean, ele indica se conseguiu – resultado True – ou não – resultado False – acessar e gravar o registro de log no arquivo. Então, vamos dar uma olhada nele e ver como ele funciona.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | function TLog.WriteLog(const ALogFile, AMessage: string; const ACanCreate, AClearBeforeWrite, AUseDateTimeInLog: Boolean): Boolean; var filLog: TextFile; strLog: string; strLogDir: string; const LOG_MESSAGE = '%s - %s'; DATE_FORMAT = 'YYYY-MM-DD HH:NN:SS'; begin Result := False; strLogDir := IncludeTrailingPathDelimiter(ExtractFilePath(ALogFile)); if not (FileExists(ALogFile) or ACanCreate) then begin Exit; end; if not (DirectoryExists(strLogDir) or ForceDirectories(strLogDir)) then begin Exit; end; AssignFile(filLog, ALogFile); case FileExists(ALogFile) and (not AClearBeforeWrite) of True : Append(filLog); False: Rewrite(filLog); end; case AUseDateTimeInLog of True : strLog := Format(LOG_MESSAGE, [FormatDateTime(DATE_FORMAT, Now), AMessage]); False: strLog := AMessage; end; Writeln(filLog, strLog); CloseFile(filLog); Result := True; end; |
Para um efetivo registro de log em aplicações Delphi devemos ter em mente duas coisas: as operações complexas não devem ser executadas pelo desenvolvedor que deseja registrar o log e ele deve ser padronizado. Sendo assim, abrir o arquivo, decidir se reescreve ou só adiciona o novo registro, salvar e fechar o arquivo de log, assim como o formato que a linha deve ter, são atribuições com as quais o programador não vai precisar se preocupar.
Ao analisarmos o método, varemos que ele já toma essas decisões conforme os parâmetros fornecidos – esses sim sob responsabilidade do desenvolvedor – e efetua o registro do log no arquivo designado. Logo no início vemos que o método define o formato que a linha terá através da constante LOG_MESSAGE e o formato de data e hora que o arquivo utilizará através da constante DATE_FORMAT.
Assim como o método Load, o valor padrão de retorno é False, sendo alterado para True se tudo correr como o esperado. No primeiro passo, precisamos obter o caminho do arquivo, e isso é feito com o método ExtractFilePath. Além disso, precisamos incluir um delimitador de caminho ao fim da string obtida. Como esse delimitador depende da plataforma onde a aplicação estiver rodando – Windows, Linux, MacOS, Android ou iOS – e, por isso, não é o mesmo, usamos o método IncludeTrailingPathDelimiter para essa tarefa.
Com o caminho para o arquivo obtido, podemos testar se o diretório em questão existe, se o próprio arquivo já existe e se, caso não exista, podemos criá-lo. Como usamos De Morgan aqui, a condição pode parecer confusa, mas ela me diz que caso o arquivo não exista e não seja permitido criar um novo arquivo (parâmetro ACanCreate), o método deve encerrar seu processamento. Como não alteramos o resultado padrão, ele será False.
Se não saímos, vamos verificar se o diretório de caminho existe e, caso não existindo, se podemos criá-lo. Se não for possível criar o diretório através do método ForceDirectories – o qual cria todo o caminho indicado em seu parâmetro -, o método também sairá sem alterar o resultado padrão.
Para iniciarmos a escrita do arquivo, utilizamos a forma mais conhecida e difundida no meio Delphi: a estrutura TextFile. Usando o método AssignFile, nós atribuímos à variável do tipo TextFile o caminho do arquivo a ser criado/escrito. Assim já deixamos a estrutura pronta para receber as informações que queremos registrar.
Em seguida, nos certificamos se o arquivo existe realmente e se devemos “limpá-lo” antes de efetuar o registro (parâmetro ACleanBeforeWrite). Caso o arquivo não exista ou se devemos reescrevê-lo, utilizamos o método Rewrite, responsável por criar um novo stream para o arquivo. Caso contrário, usamos o método Append, que posiciona o cursor ao fim do stream já existente.
Antes de efetivamente registrarmos a entrada de log no arquivo, precisamos formatar a mensagem que vamos registrar. Através do parâmetro AUseDateTimeInLog determinamos se a mensagem conterá, em seu início, a data e a hora da entrada do registro no log. Por padrão, o valor para esse parâmetro é True, indicando que o registro conterá data e hora. Contudo, se mudarmos esse parâmetro para False, apenas a mensagem será registrada. Isso é extremamente útil quando precisamos registrar um objeto JSON ou instrução SQL no arquivo de log.
Pro fim, efetuamos o registro da mensagem no arquivo através do método WriteLn, o qual inclui o registro de log e uma quebra de linha ao final do registro, e fechamos o arquivo com o método CloseFile, o qual encerra o stream do arquivo em memória e faz sua persistência em disco.
Fazendo o registro de log em aplicações Delphi com alta concorrência
Aplicações com alta concorrência são aquelas em que duas ou mais threads trabalham em paralelo. Independente do que cada thread faça individualmente, eventualmente elas podem necessitar de acesso a uma mesma área reservada de memória da aplicação como, por exemplo, uma variável e, como todos sabemos, threads não gostam de compartilhar recursos. Pensando nisso, a estrutura TMultiReadExclusiveWriteSynchronizer foi implementada para garantir que recursos compartilhados pudessem ser “semaforizados”.
Partindo desse princípio, o registro de log em aplicações Delphi com alta concorrência deve ser feito através dessa estrutura. Assim como fizemos com a classe TLog, vamos criar uma classe de semáforo para o registro de log. Ela também conterá um singleton, mas não será dela e sim do TMultiReadExclusiveWriteSynchronizer criado para a semaforização. Sem mais delongas, vamos a ela.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | unit Logs.Semaphor; interface uses System.SysUtils, System.Classes; type TLogSemaphor = class public class function ReadLog(const ALogFile: string; const AStrings: TStrings): Boolean; virtual; class function WriteLog(const ALogFile, AMessage: string; const ACanCreate: Boolean = True; const AClearBeforeWrite: Boolean = False; const AUseDateTimeInLog: Boolean = True): Boolean; end; implementation uses Logs.Register; var FSemaphor: TMultiReadExclusiveWriteSynchronizer; { TLogSemaphor } class function TLogSemaphor.ReadLog(const ALogFile: string; const AStrings: TStrings): Boolean; begin FSemaphor.BeginRead; Result := TLog.Instance.Load(ALogFile, AStrings); FSemaphor.EndRead; end; class function TLogSemaphor.WriteLog(const ALogFile, AMessage: string; const ACanCreate, AClearBeforeWrite, AUseDateTimeInLog: Boolean): Boolean; begin FSemaphor.BeginWrite; Result := TLog.Instance.WriteLog(ALogFile, AMessage, ACanCreate, AClearBeforeWrite, AUseDateTimeInLog); FSemaphor.EndWrite; end; initialization FSemaphor := TMultiReadExclusiveWriteSynchronizer.Create; finalization FreeAndNil(FSemaphor); end. |
Nessa classe, como podemos notar, existem apenas dois métodos estáticos: ReadLog e WriteLog. Seus nomes são amigáveis o suficiente para entendermos o que cada um deles faz e, se prestarmos um pouco mais de atenção, veremos que os métodos possuem os mesmos parâmetros que os métodos Load e WriteLog – onde até o nome do método é o mesmo – constantes na classe TLog. Isso, claro, tem uma razão: simplificar o acesso à instância do singleton que criamos com o a classe TLog.
Lendo o arquivo de log em aplicações Delphi – agora com semaforização
Como já vimos lá em cima, a classe TLog pode fazer a leitura do arquivo de log criado pela aplicação. Como os parâmetros são os mesmos utilizados pelo método Load, não vamos revisá-los aqui, já que eles têm o mesmo nome, os mesmos tipos e as mesmas funções já descritas. Mas, mesmo assim, vamos discutir seu funcionamento.
1 2 3 4 5 6 | class function TLogSemaphor.ReadLog(const ALogFile: string; const AStrings: TStrings): Boolean; begin FSemaphor.BeginRead; Result := TLog.Instance.Load(ALogFile, AStrings); FSemaphor.EndRead; end; |
Por se tratar de um método estático, não há necessidade de criarmos uma instância da classe TLogSemaphor para acessarmos o método ReadLog. Para tal, uma simples chamada TLogSemaphor.ReadLog bastará. E podemos assumir, também, que, assim como os parâmetros são idênticos, o retorno também é, já que a variável Result recebe o retorno da função Load da classe TLog, invocada através de seu singleton (TLog.Instance.Load(ALogFile, AStrings)).
Mas, antes disso, temos uma instrução para que o semáforo FSemaphor – criado na seção initialization da unit – sinalize que iniciará a leitura do arquivo (FSemaphor.BeginRead). Isso impede que, no momento em que o singleton de TLog esteja sendo usado para gravar uma entrada de log no arquivo, essa operação conflite com o acesso para a leitura. Efetuada a leitura, é hora de sinalizarmos que terminamos o acesso ao arquivo e que terminamos sua leitura (FSemaphor.EndRead). Simples, fácil e rápido.
Gravando registros de log em aplicações Delphi – agora com semaforização
Assim como o método ReadLog utiliza o método Load da classe TLog para a leitura do arquivo de log, o método WriteLog usa seu homônimo na classe TLog para a escrita dos registros de log. Como já discutimos seu uso lá em cima, não vamos discutí-lo novamente. Já seu funcionamento pode ser discutido. E é o que faremos agora.
1 2 3 4 5 6 | class function TLogSemaphor.WriteLog(const ALogFile, AMessage: string; const ACanCreate, AClearBeforeWrite, AUseDateTimeInLog: Boolean): Boolean; begin FSemaphor.BeginWrite; Result := TLog.Instance.WriteLog(ALogFile, AMessage, ACanCreate, AClearBeforeWrite, AUseDateTimeInLog); FSemaphor.EndWrite; end; |
É possível ver que a estrutura é basicamente a mesma utilizada pelo método ReadLog, inclusive com a variável Result recebendo o resultado da chamada ao método WriteLog da classe TLog. Porém, dessa vez, ao invés de utilizarmos as instruções BeginRead e EndRead do semáforo, utilizaremos os métodos BeginWrite e EndWrite, o que nos garante exclusividade no acesso ao singleton de TLog quando formos fazer a entrada do log em seu respectivo arquivo. Assim, quando threads concorrentes tentarem fazer o registro de log em aplicações Delphi com alta concorrência, nenhuma thread interferirá no processo de registro de log de outra thread, ou mesmo na thread que efetua a leitura do arquivo.
Ah, se você tiver alguma dúvida sobre o uso da estrutura TMultiReadExclusiveWriteSynchronizer, no começo do artigo tem um link para a documentação oficial da Embarcadero.
Legal, mas como eu uso isso?
Então… Até agora nós aprendemos a como efetuar o registro de log em aplicações Delphi, inclusive as que têm alta concorrência. Tudo é lindo mas, como sempre, como faço para usar isso tudo em uma aplicação? A resposta é bem simples. Vejamos o código abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses Logs.Semaphor; {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin TLogSemaphor.WriteLog('C:\Temp\Log\MeuLog.log', 'Criando o formulário.'); end; procedure TForm1.FormDestroy(Sender: TObject); begin TLogSemaphor.WriteLog('C:\Temp\Log\MeuLog.log', 'Destruindo o formulário.'); end; end. |
Criando uma aplicação VCL com a sua instalação do Delphi, nos eventos OnCreate e OnDestroy do formulário vamos incluir as linhas que determinam a gravação das ações de criação e destruição do formulário. Assim, quando a aplicação criar e destruir o formulário Form1, seus eventos OnCreate e OnDestroy registrarão essa operações no log, conforme mostrado abaixo.

Beleza. Mas e a leitura?
Como já vimos como fazemos o registro de log em aplicações Delphi, vamos aprender como podemos ler o arquivo. Para isso, utilizando o mesmo projeto que já criamos, vamos adicionar um componente TTimer, um componente TMemo e mais alguns componentes TButton para escrever entradas diferentes no arquivo de log.
Com isso, nosso código ficará semelhante a esse:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls; type TForm1 = class(TForm) mmoLog: TMemo; btnAction1: TButton; btnAction2: TButton; btnAction3: TButton; btnAction4: TButton; btnAction5: TButton; tmrLog: TTimer; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure btnAction1Click(Sender: TObject); procedure tmrLogTimer(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses Logs.Semaphor; const LOG_FILE = 'C:\Temp\Log\MeuLog.log'; {$R *.dfm} procedure TForm1.btnAction1Click(Sender: TObject); begin if not (Sender is TButton) then begin Exit; end; TLogSemaphor.WriteLog(LOG_FILE, 'Pressionado o botão "' + (Sender as TButton).Caption + '".'); end; procedure TForm1.FormCreate(Sender: TObject); begin TLogSemaphor.WriteLog(LOG_FILE, 'Criando o formulário.'); TLogSemaphor.WriteLog(LOG_FILE, 'Limpando o conteúdo do componente mmoLog.'); mmoLog.Lines.Clear; end; procedure TForm1.FormDestroy(Sender: TObject); begin TLogSemaphor.WriteLog(LOG_FILE, 'Destruindo o formulário.'); end; procedure TForm1.tmrLogTimer(Sender: TObject); begin tmrLog.Enabled := False; TLogSemaphor.ReadLog(LOG_FILE, mmoLog.Lines); tmrLog.Enabled := True; end; end. |
Com a implementação acima, somos capazes de, ao mesmo tempo em que lemos o arquivo – configuramos nosso componente TTimer para um intervalo de 250 ms – e atribuímos seu conteúdo a um componente TMemo – através de sua propriedade Lines -, podemos efetuar o registro das operações que fazemos na aplicação através dos componentes TButton adicionados ao formulário. Sendo assim, o resultado final de tudo isso é o mostrado abaixo.

Caso você queira reproduzir o resultado recriando este formulário, lembre-se de atribuir ao evento OnClick de todos os botões o mesmo método, no caso btnAction1Click.
E só com isso eu já consigo efetuar o registro de log em aplicações Delphi legadas?
Como essa estrutura foi desenvolvida utilizando recursos presentes no Delphi há muitas, muitas eras, é perfeitamente possível utilizá-las em aplicações desenvolvidas em versões que suportem a estrutura TMultiReadExclusiveWriteSynchronizer.
Contudo, atenção: as seções uses nesta estrutura usam declarações com namespaces, disponíveis em versões superiores à 2010. Para utilização em versões anteriores, a adequação dos nomes das units, removendo os namespaces, é necessária.
Ah, e como já é costume, aqui você pode baixar o pacote com a estrutura necessária para implementar o registro – e leitura – de log em aplicações Delphi.
Um abraço e até a próxima.
Faça um comentário