monitoraggio attività

in ASPNET Core, Informatica

Monitoraggio Integrità – ASPNET Core – Parte 2

Reading Time: 3 minutes

Dopo il post precedente dove abbiamo creato una semplice classe per la verifica dei ping, procediamo con il suo refactoring per risolvere i punti lasciati in sospeso. In particolare:

  • il nome dell’host da verificare e la soglia del timeout dovrebbe essere passata come parametro
  • la tipologia di messaggio che viene ritornata (Healthy and Unhealthy) risulta un pò troppo sintetico. Sarebbe meglio ottenere anche un messaggio un pò più dettagliato
  • la risposta che viene ritornata è di tipo testo, se vogliamo integrare il servizio per l’accesso remoto (es. da un applicazione angular) sarebbe meglio inviare un messaggio in formato json

Partiamo quindi con le modifiche al codice. Il primo steps riguarda il passaggio dei parametri alla nuova classe e la creazione del messaggio di output.

Passaggio Parametri e creazione del messagggio di output

Iniziamo quindi con il refactoring del codice:

namespace HealthCheck
{
public class ICMPHealthCheck : IHealthCheck
 {
     private string Host { get; set; }
     private int Timeout { get; set; }
     
     public ICMPHealthCheck(string host, int timeout)
      {
        Host = host;
        Timeout = timeout;
      }
      
      public async Task<HealthCheckResult> CheckHealthAsync(
      HealthCheckContext context,
      CancellationToken cancellationToken = default)
      {
      try
        {
          using (var ping = new Ping())
            {
               var reply = await ping.SendPingAsync(Host);
               switch (reply.Status)
                {
                  case IPStatus.Success:
                       va msg = String.Format("IMCP to {0} took {1} ms.", 
                        Host, reply.RoundtripTime);
                        return (reply.RoundtripTime > Timeout)
                        ? HealthCheckResult.Degraded(msg)
                        : HealthCheckResult.Healthy(msg);

                   default:
                   var err = String.Format("IMCP to {0} failed: 
                   {1}",Host,reply.Status);
                  return HealthCheckResult.Unhealthy(err);
            }
          }
        }
       catch (Exception e)
       {
          var err = String.Format("IMCP to {0} failed: {1}",Host,e.Message);
          return HealthCheckResult.Unhealthy(err);
       }
    }
  }
}

E’ stato introdotto lo switch per la gestione del codice di ritorno dalla chiamata asincrona al ping e viene ritornato un messaggio descrittivo del Success o dell’errore riscontrato. L’altra modifica riguarda l’introduzione di un costruttore che accetta in ingresso due parametri : l’url dell’host da verificare ed il valore di timeout oltre il quale il risultato del ping è da ritenere degradato. A questo punto, è necessario modificare i parametri del middleware all’interno del metodo ConfigureServices di Startup.cs. La modifica si rende necessaria perchè dobbiamo passare i parametri al costruttore della classe ICMPHealthCheck:

public void ConfigureServices(IServiceCollection services)
{
       /// ...existing code...
       services.AddHealthChecks()
      .AddCheck("ICMP_01", new ICMPHealthCheck("www.dotnettortona.net",100))
      .AddCheck("ICMP_02", new ICMPHealthCheck("amerlin.keantex.com",100));
}

Abbiamo aggiunto due nuovi URL da testare ed il relativo valore del timeout. Siccome stiamo effettuando test in cascata il risultato potrebbe non essere quello aspettato: il valore finale del test è il risultato dell’ OR booleano dei risultati del test. Questo significa che se almeno uno dei test fallisce, il risulato sarà Unhealthy. Questo problema può essere risolto, restituendo un json con tutti i dettagli dei test eseguiti per singolo host. Il prossimo step sarà proprio quello di modificare la nostra classe per creare il messaggio strutturato per l’output.

Creazione dell’output in formato Json

Il package Microsoft.AspNetCore.Diagnostics.HealthChecks include HealthCheckOptions che consente di creare una classe all’interno della quale definire le opzioni di HealthCheck. Nel nostro caso crearemo una classe che eredita proprio da questa classe per definire la generazione del messaggio di output.

namespace HealthCheck
{
    public class CustomHealthCheckOptions : HealthCheckOptions
      {
         public CustomHealthCheckOptions() : base()
          {
            var jsonSerializerOptions = new JsonSerializerOptions()
             {
               WriteIndented = true
             };
               ResponseWriter = async (c, r) => {
               c.Response.ContentType =
               MediaTypeNames.Application.Json;
               c.Response.StatusCode = StatusCodes.Status200OK;
               var result = JsonSerializer.Serialize(new
               {
                checks = r.Entries.Select(e => new
                 {
                  name = e.Key,
                  responseTime = 
                    e.Value.Duration.TotalMilliseconds,
                    status = e.Value.Status.ToString(),
                    description = e.Value.Description
                 }),
                 totalStatus = r.Status, 
                 totalResponseTime = r.TotalDuration.TotalMilliseconds }, jsonSerializerOptions);
                 await c.Response.WriteAsync(result);
                 };
             }
          }
        }

Il codice è piuttosto semplice: abbiamo definito le opzioni che dovranno essere utilizzate dal serializzatore Json, ed abbiamo definito il tipo di risposta che vogliamo ottenere. In particolare, ogni singolo check restituirà un oggetto del tipo:

  • name: contenente il nome del servizio che viene testato
  • responseTime: contenente il tempo di risposta ottenuto dal check
  • status: lo stato del check del servizio
  • description: una descrizione del servizio testato

inoltre, in aggiunta all’output per singolo check, ho aggiunto due proprietà :

  • totalStatus: che contiene l’OR booleano degli stati che sono stati ottenuti. In pratica Healthy se sono tutti nello stato di Healthy, Unhealthy se esiste almeno un servizio nello stato di Unhealthy, Degraded in tutti gli altri casi
  • totalResponseTime: la durata totale di tutti i check

Ho inserito questi due ultimi valori perchè possono essere utili nel caso in cui si volesse analizzare tutti lo stato complessivo del check, senza dover effettuare il parsing di tutte le risposte.

A questo punto siamo pronti per sostituire il messaggio di output con quello nel nuovo formato. Ancora una volta dobbiamo modificare Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment
env)
{
           ...
            app.UseHealthChecks("/hc", new CustomHealthCheckOptions());
           ...
}

in questo modo abbiamo aggiunto al nostro endpoint la classe che definisce il nuovo formato. Proviamo ad eseguire l’applicazione e a collegarci sull’endpoint ed otteremo:

Il Json ottenuto può essere utilizzato da un qualsiasi client che lo parsificherà per gestire ogni singolo dato.