Back-End

14 mar, 2019

ASP.NET Core + Entity Framework Core: monitoramento descomplicado via Application Insights

Publicidade

É inegável a evolução pela qual passou o Entity Framework Core desde suas primeiras versões, com inúmeras melhorias buscando o acesso a bases relacionais com uma maior performance.

Ainda assim o monitoramento envolvendo o acesso a dados em projetos que dependem desta tecnologia seguirá como uma atividade vital, fornecendo, com isso, insumos para a evolução contínua dos sistemas considerados.

E como podemos realizar tal monitoramento de maneira efetiva em uma aplicação ASP.NET Core, porém, com um mínimo esforço de codificação?

Uma boa resposta a essa pergunta seria a adoção do Application Insights, um serviço de monitoramento de aplicações Web que integra o Microsoft Azure.

Embora se trate de uma solução na nuvem, o Application Insights pode ser configurado também para utilização em soluções on-premise ou, até mesmo, em sites hospedados em outras alternativas de Cloud Computing.

Já abordei anteriormente o uso do Application Insights com ASP.NET Core em uma apresentação online do Canal .NET (juntamente com outros serviços do Azure). A gravação deste evento pode ser assistida gratuitamente no YouTube:

Na apresentação a seguir:

E também no seguinte artigo, onde eu detalhei inclusive os procedimentos necessários para ativar o Application Insights em uma aplicação ASP.NET Core (os ajustes necessários foram omitidos neste artigo para efeitos de simplificação):

O projeto utilizado para os testes descritos neste artigo já está no GitHub (trata-se de uma API REST criada com o ASP.NET Core 2.2 e que utiliza uma base do SQL Server/Azure SQL):

Na listagem a seguir está a implementação da classe BolsasController, na qual é possível observar o uso do Entity Framework Core através de uma consulta LINQ:

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using APIIndicadores.Data;
using APIIndicadores.Models;

namespace APIIndicadores.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BolsasController : ControllerBase
    {
        [HttpGet]
        public List<BolsaValores> Get(
            [FromServices]ApplicationDbContext context)
        {
            return (from b in context.BolsasValores
                    select b).ToList();
        }
    }
}

E quais seriam os ajustes requeridos para o monitoramento do que acontece neste Controller em termos de acesso a um banco de dados SQL?

Na classe BolsasController não será necessária nenhuma alteração! Ao ativar o Application Insights, o mesmo será executado trazendo pouco impacto à performance geral das operações monitoradas, logando por default informações sobre a execução de instruções envolvendo o uso de um provider ADO.NET (o próprio Entity Framework Core foi construído a partir do ADO.NET).

É óbvio que o package que possibilita a integração do ASP.NET Core com o Application Insights será adicionado à aplicação:

Uma chave de instrumentação (Instrumentation Key) deverá ser informada ao arquivo appsettings.json:

{
  "ApplicationInsights": {
    "InstrumentationKey": "CHAVE INSTRUMENTAÇÃO"
  },
  "ConnectionStrings": {
    "BaseIndicadores": "Data Source=?;Initial Catalog=?;User Id=?;Password=?;",
    "CacheRedis": "STRING REDIS"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

E o método AddApplicationInsightsTelemetry será acionado em ConfigureServices na classe Startup, de forma a ativar o uso do Application Insights no projeto considerado:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Mime;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using APIIndicadores.Data;
using Newtonsoft.Json;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace APIIndicadores
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            // Configurando o uso do Entity Framework Core
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("BaseIndicadores")));

            // Ativando o uso de cache via Redis
            services.AddDistributedRedisCache(options =>
            {
                options.Configuration =
                    Configuration.GetConnectionString("CacheRedis");
                options.InstanceName = "Cache-APIIndicadores-";
            });

            // Verificando a disponibilidade dos bancos de dados
            // da aplicação através de Health Checks
            services.AddHealthChecks()
                .AddSqlServer(Configuration.GetConnectionString("BaseIndicadores"), name: "baseSql")
                .AddRedis(Configuration.GetConnectionString("CacheRedis"), name: "cacheRedis");

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            // Ativando o Application Insights
            services.AddApplicationInsightsTelemetry(Configuration);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            // Ativando o middlweare de Health Check
            app.UseHealthChecks("/status",
               new HealthCheckOptions()
               {
                   ResponseWriter = async (context, report) =>
                   {
                       var result = JsonConvert.SerializeObject(
                           new
                           {
                               statusApplication = report.Status.ToString(),
                               healthChecks = report.Entries.Select(e => new
                               {
                                   check = e.Key,
                                   ErrorMessage = e.Value.Exception?.Message,
                                   status = Enum.GetName(typeof(HealthStatus), e.Value.Status)
                               })
                           });
                       context.Response.ContentType = MediaTypeNames.Application.Json;
                       await context.Response.WriteAsync(result);
                   }
                });

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Para o monitoramento das instruções geradas por meio do Entity Framework Core acessar o item Performance do recurso do Application Insights vinculado a um projeto:

Selecionar, então, uma operação na qual aconteçam acessos a uma base de dados relacional (GET Bolsas/Get neste exemplo):

Acessar os exemplos de amostragem (Samples) em Take action:

Selecionar uma das operações registradas:

Na imagem a seguir temos o detalhamento da operação escolhida:

Ao marcar a dependência groffesql:

Serão apresentados detalhes da mesma (tipo, duração em milissegundos), com destaque para o comando SQL gerado durante o acesso ao banco de dados via Entity Framework Core (tudo isso sem a necessidade de escrever uma linha de código para logging dessas informações):

Consultas também podem ser executadas sobre os logs, com isso acontecendo por meio da função Analytics:

A seguir temos um exemplo de consulta, a qual filtrará as operações de acesso a dados consultando dados de bolsas de valores a partir das dependências registradas e que tenham levado um tempo de processamento superior a cinco milissegundos:

dependencies
| limit 1000
| where operation_Name == "GET Bolsas/Get" and duration > 5
| project timestamp, data, duration 

O resultado da execução dessa instrução está na próxima imagem:

Ao analisar o código dessa consulta logo se percebe que o mesmo não segue o padrão SQL. De fato as consultas a dados do Application Insights seguem uma especificação própria, sendo que o link a seguir traz um guia de referência sobre como escrever tais queries:

Referências