Registro em log e rastreamento em aplicações .NET

Concluído

À medida que você continua desenvolvendo seu aplicativo e ele se torna mais complexo, convém aplicar diagnósticos de depuração adicionais ao seu aplicativo.

O rastreamento é uma maneira de monitorar a execução do seu aplicativo enquanto ele está em execução. Você pode adicionar instrumentação de rastreamento e depuração ao seu aplicativo .NET ao desenvolvê-lo. Você pode usar essa instrumentação enquanto desenvolve o aplicativo e depois de implantá-lo.

Esta técnica simples é surpreendentemente poderosa. Você pode usá-lo em situações onde você precisa de mais do que um depurador:

  • Problemas que ocorrem durante longos períodos de tempo podem ser difíceis de depurar com um depurador tradicional. Os logs permitem uma revisão post-mortem detalhada que abrange longos períodos de tempo. Por outro lado, os depuradores estão limitados à análise em tempo real.
  • Aplicações multithreaded e distribuídas são frequentemente difíceis de corrigir. Anexar um depurador tende a alterar os comportamentos. Você pode analisar logs detalhados conforme necessário para entender sistemas complexos.
  • Problemas em aplicativos distribuídos podem surgir de uma interação complexa entre muitos componentes. Pode não ser razoável conectar um depurador a todas as partes do sistema.
  • Muitos serviços não devem ser interrompidos. Anexar um depurador muitas vezes provoca erros de tempo limite.
  • Os problemas nem sempre são previstos. O registo em log e o rastreamento são projetados para ter baixa sobrecarga, permitindo que os programas registrem constantemente caso ocorra um problema.

Gravar informações em janelas de saída

Até este ponto, temos usado o console para exibir informações para o usuário do aplicativo. Há outros tipos de aplicativos criados com .NET que têm interfaces de usuário, como aplicativos móveis, da Web e de desktop, e não há console visível. Nesses aplicativos, System.Console registra mensagens "nos bastidores". Essas mensagens podem aparecer em uma janela de saída no Visual Studio ou Visual Studio Code. Eles também podem ser enviados para um log do sistema, como o logcatdo Android. Como resultado, você deve levar muito em consideração quando usar System.Console.WriteLine em um aplicativo que não seja de console.

É aqui que você pode usar System.Diagnostics.Debug e System.Diagnostics.Trace além de System.Console. Tanto Debug quanto Trace fazem parte de System.Diagnostics e só gravarão em logs quando um escutador apropriado estiver ligado.

A escolha de qual API de estilo de impressão usar é sua. As principais diferenças são:

  • System.Console
    • Sempre ativado e sempre grava na consola.
    • Útil para informações que o seu cliente pode precisar ver no lançamento.
    • Por ser a abordagem mais simples, geralmente é usada para depuração temporária ad-hoc. Esse código de depuração geralmente nunca é verificado no controle do código-fonte.
  • System.Diagnostics.Trace
    • Ativado apenas quando TRACE estiver definido.
    • Grava em ouvintes anexados, por padrão, o DefaultTraceListener.
    • Use essa API ao criar logs que serão habilitados na maioria das compilações.
  • System.Diagnostics.Debug
    • Ativado apenas quando DEBUG estiver definido (quando estiver no modo de depuração).
    • Grava num depurador ligado.
    • Utilize esta API ao criar registos que serão ativados somente em compilações de debug.
Console.WriteLine("This message is readable by the end user.");
Trace.WriteLine("This is a trace message when tracing the app.");
Debug.WriteLine("This is a debug message just for developers.");

Ao projetar sua estratégia de rastreamento e depuração, pense em como você deseja que a saída pareça. Várias instruções de escrita preenchidas com informações não relacionadas criam um registo difícil de ler. Por outro lado, usar WriteLine para dispor instruções relacionadas em linhas separadas pode tornar mais difícil distinguir quais informações pertencem umas às outras. Em geral, use várias instruções Write quando quiser combinar informações de várias fontes para criar uma única mensagem informativa. Use a instrução WriteLine quando quiser criar uma única mensagem completa.

Debug.Write("Debug - ");
Debug.WriteLine("This is a full line.");
Debug.WriteLine("This is another full line.");

Este resultado é do registo anterior com Debug:

Debug - This is a full line.
This is another full line.

Definir constantes TRACE e DEBUG

Por padrão, quando uma aplicação está a ser executada em modo de depuração, a constante DEBUG é definida. Você pode controlar isso adicionando uma entrada DefineConstants no arquivo de projeto em um grupo de propriedades. Aqui está um exemplo de ativação do TRACE para configurações Debug e Release, além de DEBUG para configurações Debug.

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DefineConstants>TRACE</DefineConstants>
</PropertyGroup>

Ao usar Trace quando não estiver anexado ao depurador, você precisará configurar um ouvinte de rastreamento, como dotnet-trace.

Rastreio condicional

Além de métodos simples de Write e WriteLine, há também a capacidade de adicionar condições com WriteIf e WriteLineIf. Como exemplo, a lógica a seguir verifica se a contagem é zero e, em seguida, grava uma mensagem de depuração:

if(count == 0)
{
    Debug.WriteLine("The count is 0 and this may cause an exception.");
}

Você pode reescrever isso em uma única linha de código:

Debug.WriteLineIf(count == 0, "The count is 0 and this may cause an exception.");

Você também pode usar essas condições com Trace e com sinalizadores que você define em seu aplicativo:

bool errorFlag = false;  
System.Diagnostics.Trace.WriteIf(errorFlag, "Error in AppendData procedure.");  
System.Diagnostics.Debug.WriteIf(errorFlag, "Transaction abandoned.");  
System.Diagnostics.Trace.Write("Invalid value for data request");

Verificar se existem determinadas condições

Uma asserção, ou instrução Assert, testa uma condição que você especifica como um argumento para a instrução Assert. Se a condição for avaliada como true, nenhuma ação ocorrerá. Se a condição for avaliada como false, a asserção falhará. Se estiveres a executar com uma compilação de depuração, o teu programa entra no modo de interrupção.

Você pode usar o método Assert a partir de Debug ou Trace, que estão no namespace System.Diagnostics. Debug métodos de classe não estão incluídos numa versão de lançamento do seu programa, portanto, eles não aumentam o tamanho nem reduzem a velocidade do seu código final.

Use o método System.Diagnostics.Debug.Assert livremente para testar condições que devem ser verdadeiras se o código estiver correto. Por exemplo, suponha que você tenha escrito uma função de divisão de inteiros. Pelas regras da matemática, o divisor nunca pode ser zero. Você pode testar essa condição usando uma asserção:

int IntegerDivide(int dividend, int divisor)
{
    Debug.Assert(divisor != 0, $"{nameof(divisor)} is 0 and will cause an exception.");

    return dividend / divisor;
}

Quando você executa esse código no depurador, a instrução de asserção é avaliada. No entanto, a comparação não é feita na versão de lançamento, portanto, não há sobrecarga adicional.

Observação

Ao usar System.Diagnostics.Debug.Assert, certifique-se de que qualquer código dentro Assert não altere os resultados do programa se Assert for removido. Caso contrário, você pode acidentalmente introduzir um bug que só aparece na versão de lançamento do seu programa. Tenha especial cuidado com asserções que contenham chamadas de função ou procedimento.

Usar Debug e Trace do namespace System.Diagnostics é uma ótima maneira de fornecer contexto adicional quando você executa e depura seu aplicativo.