Testes com a classe TelemetryClient

No artigo anterior eu mostrei o uso da classe TelemetryClient para catalogar informações customizadas. O fato de utilizar a classe em sua configuração padrão, fará com que as informações sejam enviadas para o serviço (na nuvem) mesmo que você ainda esteja em ambiente de testes. Talvez isso não seja o que estamos querendo, pois espalhamos pelo código os pontos que são cruciais para enviar as informações, mas talvez só faça sentido quando estive em ambiente de produção.

A classe TelemetryClient confia nas configurações que são disponibilizadas pela classe TelemetryConfiguration. Ela, por sua vez, possui uma propriedade estática chamada Active que retorna as configurações globais (para a aplicação) do Application Insights. A partir daí podemos recorrer a propriedade DisableTelemetry que quando definida como true, não enviará as informações ao serviço. Para utiliza-la podemos também condicionar à diretiva de DEBUG para somente desabilitar enquanto estivermos em desenvolvimento:

#if DEBUG
            TelemetryConfiguration.Active.DisableTelemetry = true;
#endif

Ainda com o exemplo utilizado no artigo anterior, não estávamos nos preocupando com os testes unitários. É uma preocupação que também devemos ter para não reportar os eventos quando estamos rodando os testes. Apesar da opção acima resolver, as vezes podemos querer validar se o nosso código está ou não reportando quando necessário e se as informações que foram levadas estão de acordo com a nossa expectativa.

A classe de configuração também nos permite configurar o canal de comunicação, e com isso customizar (sobrescrever) onde e como queremos armazenar os logs. Só que antes de visualizarmos o código necessário para alcançar isso, vamos entender a dependência entre a classe TelemetryClient e o restante da configuração.

A classe TelemetryClient possui dois overloads, onde um deles espera a instância da classe TelemetryConfiguration, que se for omitida, o Application Insights utiliza o valor fornecido pela propriedade Active. A classe TelemetryConfiguration possui uma propriedade chamada TelemetryChannel, onde podemos customizar o canal de comunicação através da implementação da interface ITelemetryChannel. Para o exemplo, vou optar por armazenar os logs em uma coleção interna, conforme é possível ver no código abaixo:

public class TestChannel : ITelemetryChannel
{
    private readonly IList<ITelemetry> itens;

    public TestChannel()
    {
        this.itens = new List<ITelemetry>();
    }

    public void Send(ITelemetry item)
    {
        this.itens.Add(item);
    }

    public IList<ITelemetry> Itens
    {
        get
        {
            return this.itens;
        }
    }

    //outros membros, omitidos por questão de espaço
}

Depois da classe implementada, precisamos criar a instância e associa-la à propriedade da configuração para que ela passe a ser utilizada quando chamarmos os métodos para catalogar as informações (Track*). O código que foi criado para calcular o frete não foi alterado em nada. Mesmo que a configuração tenha sido realizada externamente, a classe TelemetryClient irá utilizar esta classe via TelemetryConfiguration (via propriedade Active). O teste ficaria da seguinte forma:

[TestClass]
public class CalculadoraDeFrete
{
    private TestChannel channel;

    [TestInitialize]
    public void Inicializar()
    {
        this.channel = new TestChannel();
        TelemetryConfiguration.Active.TelemetryChannel = channel;
    }

    [TestMethod]
    public void DeveCalcularFreteParaValinhos()
    {
        var cep = “13273-047”;
        var valorEsperado = 12.38M;

        var calculadora = new CalculadoraDeFrete();
        var resultado = calculadora.Calcular(cep);

        Assert.AreEqual(valorEsperado, resultado);

        var evento = this.channel.Itens.Single() as EventTelemetry;

        Assert.AreEqual(“CalculoDeFrete”, evento.Name);
        Assert.AreEqual(cep, evento.Properties[“Cep”]);
        Assert.AreEqual(resultado.ToString(), evento.Properties[“Resultado”]);
    }
}

ApplicationInsights – Utilizando a classe TelemetryClient

A Microsoft está disponibilizando um serviço de telemetria de aplicações chamado de Application Insights. É um serviço que roda no Azure (nuvem) e que permite integrar nas aplicações que desenvolvemos para que seja possível extrair informações, armazenar e, consequentemente, analisar os dados gerados para tentar identificar algum problema de performance ou de qualquer outra natureza.

Dependendo do tipo de aplicações onde este serviço é instalado, ele utiliza o recurso exposto pela aplicação para interceptar a requisição e catalogar as informações. Por exemplo, se este serviço é instalado em uma aplicação ASP.NET, então o módulo (IHttpModule) ApplicationInsightsHttpModule é incluído para interceptar as requisições que chegam à aplicação; já para aplicações ASP.NET 5, acopla-se através do método UseApplicationInsightsRequestTelemetry. Em ambos os casos, automaticamente o Application Insights começa a receber informações sobre as requisições que chegam para a aplicação, duração da requisição, cabeçalho, etc.

A identificação da aplicação para o serviço é uma chave chamada instrumentation key (GUID) que é gerada pelo Azure, e deve ser configurada na aplicação para que o Application Insights possa enviar ao serviço e ele, por sua vez, saiba qual aplicação está gerando os dados. E, novamente, dependendo da tecnologia, o arquivo utilizado para armazenar a chave é o ApplicationInsights.config para os projetos ASP.NET tradicionais ou aplicações Windows, e o config.json para o ASP.NET 5.

[ApplicationInsights.config]
<?xml version=”1.0″ encoding=”utf-8″?>
<ApplicationInsights>
  …
  <InstrumentationKey>bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed</InstrumentationKey>
</ApplicationInsights>

[config.json]
{
  “ApplicationInsights”: {
    “InstrumentationKey”: “bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed”
  },
  …
  }
}

Mesmo que ocorra essa mágica, o Application Insights fornece uma API que podemos recorrer para customizar os dados que são enviados para o serviço. Isso pode ser feito através da classe TelemetryClient, que expõe alguns métodos específicos ou de mais baixo nível para customizar diversas informações que podem ser levadas para o serviço. Para fazer uso desta classe, primeiramente precisamos utilizar o Nuget para adicionar o seguinte pacote:

Install-Package Microsoft.ApplicationInsights

É importante dizer que é possível fazer uso desta classe em projetos que não sejam do tipo ASP.NET. Como exemplo, vou utilizar uma aplicação Console para catalogar os mais diversos tipos de informações no Application Insights. No caso de projetos ASP.NET, já há uma opção durante a criação do projeto para habilita-lo, assim como é possível vermos na imagem abaixo, e a instalação explícita do pacote não é necessária.

>

Independente da forma como você adiciona o suporte para o Application Insights na aplicação, a classe TelemetryClient pode ser utilizada. Como já era de se esperar, esta classe fornece uma propriedade chamada InstrumentationKey, que é onde devemos configurar a chave gerado pelo Azure, mas em casos de projetos do tipo Windows, podemos criar o arquivo ApplicationInsights.config e lá ter o elemento InstrumentationKey, o que facilita a alteração sem precisar recompilar a aplicação.

var tc = new TelemetryClient()
{
    InstrumentationKey = “bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed”
};

A partir da instância da classe TelemetryClient criada, temos alguns métodos para enviar informações para o serviço. O Application Insights possui diversos tipos de informações (“categorias”) que possamos enviar os dados: Trace, Request, Page View, Expcetion, Dependency e Custom Event. Para cada uma destas informações, há um método correspondente: TrackTrace, TrackRequest, TrackPageView, TrackException, TrackDependency e TrackEvent.

Para exemplificar, considere o código abaixo. Estamos utilizando o método TrackEvent para indicar ao serviço que trata-se de um evento que vamos gerar diversas entradas e que corresponde à uma atividade de rotina da nossa aplicação. Além do nome, também podemos, opcionalmente, informar um dicionário de dados contendo propriedades que queremos anexar aquele evento. No exemplo, estou optando por incluir o parâmetro, o resultado e o tempo que levou para realizar o cálculo.

public class CalculadoraDeFrete
{
    private static TelemetryClient tc = new TelemetryClient()
    {
        InstrumentationKey = “bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed”
    };

    public static decimal Calcular(string cep)
    {
        var sw = Stopwatch.StartNew();

        //cálculo do frete
        var resultado = 12.38M;

        tc.TrackEvent(
            “CalculoDeFrete”,
            new Dictionary<string, string>()
            {
                { “Cep”, cep },
                { “Resultado”, resultado.ToString() },
                { “Tempo”, sw.Elapsed.ToString() }
            });
        tc.Flush();

        return resultado;
    }
}

Ainda em relação as propriedades que podemos anexar ao log no momento em que o evento ocorre, podemos também definir valores globais que serão enviados em todas as requisições, independentemente se está catalogando um evento, uma exceção, ou qualquer outra informação. E para isso, basta recorrer ao dicionário que está exposto através do contexto da classe TelemetryClient:

static CalculadoraDeFrete()
{
    tc.Context.Properties.Add(“Usuario”, “Israel Aece”);
}

Internamente a classe mantém um buffer com as entradas e periodicamente envia as requisições para o serviço. Utilizamos o método Flush para adiantar esse processo. Se consultarmos o portal, já podemos visualizar o evento adicionado e as respectivas propriedades que enviamos (incluindo a propriedade global Usuario). A partir deste nome do evento, eles sempre serão agrupados para uma melhor visualização e análise.

Como foi comentado acima, também é possível realizar o log de uma exceção. Para isso, podemos envolver o código inseguro em um bloco try/catch e utilizar o método TrackException. Estou optando por utilizar a versão mais simples do método, mas há parâmetros opcionais que permitem incluir um dicionário com valores customizados, assim como fizemos no código acima.

public static decimal Calcular(string cep)
{
    var resultado = 0M;

    try
    {
        throw new ArgumentException(“O CEP informado está fora da área de cobertura da transportadora.”);
        //cálculo do frete
    }
    catch (Exception e)
    {
        tc.TrackException(e);
        tc.Flush();
        throw;
    }

    return resultado;
}

Agora se atualizarmos o portal do Azure, irmos até a seção de Failures, o contador foi incrementado e é possível visualizar o erro que ocorreu, incluindo além da mensagem, toda a stack trace, útil para nós desenvolvedores, identificar onde o problema exatamente aconteceu.

Como é um exemplo simplista, estou fazendo o tratando localizado da exceção. Dependendo da estratégia de tratamento de erros da aplicação, é possível centralizar o envio das informações do erro em um ponto global, e dependendo da tecnologia utilizada, você pode recorrer ao suporte que ela dá para isso. Em aplicações ASP.NET tradicionais, você pode concentrar isso no evento Application_Error no arquivo Global.asax, no ASP.NET MVC em um filtro, no ASP.NET Web API no ExceptionLogger ou, no WCF, através da interface IErrorHandler.

Como podemos notar, o Application Insights eleva o “simples log de aplicações” para um outro nível. Podemos gerar diversas informações e temos a certeza que por trás existe uma grande infraestrutura para armazenamento e processamento delas. Terceirizando isso nos permite ainda mais focar no que de fato importa: o desenvolvimento da regra de negócio para qual a aplicação está sendo construída.