Registro em log e rastreamento em aplicativos .NET

Concluído

À medida que você continua desenvolvendo seu aplicativo e ele fica mais complexo, talvez seja necessário aplicar mais diagnósticos de depuração 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 aplicativo .NET ao desenvolvê-lo. Você pode usar essa instrumentação enquanto estiver desenvolvendo o aplicativo e depois de implantá-lo.

Essa técnica simples é surpreendentemente poderosa. Ela pode ser usada em situações que exigem mais do que um depurador:

  • Problemas que ocorrem por longos períodos podem ser difíceis de depurar com um depurador tradicional. Os logs permitem uma análise posterior detalhada, abrangendo longos períodos. Por outro lado, os depuradores são limitados a análises em tempo real.
  • Aplicativos distribuídos e com multithread costumam ser difíceis de depurar. A anexação de um depurador tende a modificar comportamentos. Logs detalhados podem ser analisados, conforme necessário, para entender sistemas complexos.
  • Problemas em aplicativos distribuídos podem surgir de uma interação complexa entre vários componentes. Talvez não seja razoável conectar um depurador a todas as partes do sistema.
  • Muitos serviços não devem ser interrompidos. A anexação de um depurador geralmente causa falhas de tempo limite.
  • Os problemas nem sempre são previstos. O registro em log e o rastreamento são projetados para baixa sobrecarga, de modo que os programas possam ser gravados caso ocorra um problema.

Gravar informações em janelas de saída

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

Essa situação é onde você pode usar System.Diagnostics.Debug e System.Diagnostics.Trace além de System.Console. Ambos Debug e Trace fazem parte System.Diagnostics e só gravam em logs quando um ouvinte apropriado é anexado.

A escolha da API de estilo de impressão a ser usada cabe a você. As principais diferenças são:

  • System.Console
    • Está sempre habilitado e sempre grava no console.
    • Útil para informações que seu cliente talvez precise ver na versão.
    • Como é a abordagem mais simples, System.Console geralmente é usada para depuração temporária ad hoc. Geralmente, esse código de depuração nunca é submetido a check-in no controle do código-fonte.
  • System.Diagnostics.Trace
    • Habilitado somente quando TRACE é definido.
    • Grava em Ouvintes anexados, por padrão, o DefaultTraceListener.
    • Use essa API ao criar logs que você planeja habilitar na maioria dos builds.
  • System.Diagnostics.Debug
    • Habilitado somente quando DEBUG é definido (no modo de depuração).
    • Grava em um depurador anexado.
    • Use essa API ao criar logs que você planeja habilitar somente em builds de depuração.
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ê quer que seja a saída. Várias instruções de gravação preenchidas com informações não relacionadas criam um log difícil de ler. Por outro lado, usar WriteLine para colocar instruções relacionadas em linhas separadas pode dificultar a distinção entre as informações. Em geral, use várias instruções Write quando quiser combinar informações de diversas fontes para criar uma única mensagem informativa. Use a instrução WriteLine ao criar uma única mensagem completa.

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

Esta saída é do registro em log anterior com Debug:

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

Definir as constantes TRACE e DEBUG

Por padrão, quando um aplicativo é executado sob depuração, a constante DEBUG é definida. Você pode controlar essa definição adicionando uma DefineConstants entrada no arquivo de projeto em um grupo de propriedades. Confira um exemplo de como ativar TRACE para configurações de Debug e de Release, além de DEBUG para configurações de Debug.

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

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

Rastreamento condicional

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

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

É possível reescrevê-la em uma única linha de código:

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

Também é possível usar estas condições com Trace e com os sinalizadores definidos no 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 para true, nenhuma ação ocorrerá. Se a condição for avaliada como false, haverá falha de asserção. Se você estiver executando um build de depuração, seu programa entrará no modo de interrupção.

É possível usar o método Assert de Debug ou Trace, que estão no namespace System.Diagnostics. Os métodos da classe Debug não estão incluídos em uma versão de lançamento do programa, portanto, não aumentam o tamanho nem reduzem a velocidade do código da versão.

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

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 assert é avaliada. Mas na versão de lançamento, a comparação não é feita, portanto, não há sobrecarga extra.

Observação

Ao usar System.Diagnostics.Debug.Assert, verifique se o código dentro de Assert não alterará os resultados do programa se Assert for removido. Caso contrário, você pode introduzir acidentalmente um bug que aparece apenas na versão de lançamento do seu programa. Tenha um cuidado especial com as instruções assert que contêm chamadas de função ou de procedimento.

Como você pode ver, usar Debug e Trace usar o System.Diagnostics namespace é uma ótima maneira de fornecer um contexto importante quando você executa e depura seu aplicativo.