In questo articolo vedremo come aggiungere il supporto a OpenTelemetry in un’applicazione .NET per implementare tracciamento distribuito, metriche e logging. Partiremo da zero fino ad avere un’applicazione con telemetria di base configurata.
Per approfondire cos’è OpenTelemetry, i suoi componenti e perché utilizzarlo, puoi leggere l’articolo introduttivo su OpenTelemetry.
Il codice sorgente con un’esempio completo lo potete trovare nel repository GitHub.
Creazione del Progetto
Come esempio andremo a fare alcune semplici API utilizzando le minimal API in dotnet 8. Per iniziare, creiamo un nuovo progetto web:
dotnet new web
Setup Iniziale
Prima di tutto, installiamo il pacchetto necessario per OpenTelemetry:
dotnet add package OpenTelemetry.Extensions.Hosting
Apriamo il Program.cs
e aggiungiamo OpenTelemetry ai servizi:
builder.Services.AddOpenTelemetry();
Componenti Fondamentali di OpenTelemetry
Prima di procedere con l’implementazione, è importante comprendere due componenti chiave di OpenTelemetry: Resource e SDK Configuration.
Resource
Una Resource in OpenTelemetry è un set di attributi che definisce l’ambiente l’ambiente nel quale viene raccolta la telemetria. Nel nostro caso, è la nostra applicazione .NET.
SDK Configuration
L’SDK di OpenTelemetry è il componente centrale che gestisce la raccolta dei dati telemetrici…
👉 Vuoi approfondire l'architettura di OpenTelemetry e capire cos'è l'SDK? Leggi l'articolo OpenTelemetry dall'interno: Componenti e Funzionamento.
Implementazione del Tracciamento
Per iniziare a tracciare le operazioni della nostra applicazione, installiamo l’exporter di base:
dotnet add package OpenTelemetry.Exporter.Console
Configuriamo il tracciamento con l’output su console, utile in fase demo per vedere i primi dati:
builder.Services.AddOpenTelemetry()
.WithTracing(trace => trace
.AddConsoleExporter());
Nel corso dell’articolo andremo a cambiare l’output per essere pronti ad utilizzarlo sia per sviluppo che per produzione.
Aggiunta dell’Instrumentazione ASP.NET Core
Per ottenere dati significativi, aggiungiamo l’instrumentazione di ASP.NET Core:
builder.Services.AddOpenTelemetry()
.WithTracing(trace => trace
.AddAspNetCoreInstrumentation()
.AddConsoleExporter();
Configurazione delle Risorse
È importante identificare correttamente il servizio e la sua versione:
var serviceName = builder.Configuration["ServiceName"] ?? "MyService";
var serviceVersion = builder.Configuration["ServiceVersion"] ?? "1.0.0"; // only for demo purposes
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(serviceName, serviceVersion)
.AddAttributes(new Dictionary<string, object>
{
["deployment.environment"] = builder.Environment.EnvironmentName,
["host.name"] = Environment.MachineName,
["service.instance.id"] = Guid.NewGuid().ToString()
}))
.WithTracing(/* configurazione precedente */);
Implementazione delle Metriche
Aggiungiamo il supporto per le metriche utilizzando la stessa instrumentazione ASP.NET Core:
builder.Services.AddOpenTelemetry()
.ConfigureResource(/* configurazione precedente */)
.WithTracing(/* configurazione precedente */)
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddConsoleExporter());
Comprendere le Metriche
L’instrumentazione ASP.NET Core fornisce automaticamente diverse metriche utili. Analizziamo le più importanti:
http.server.request.duration
Questa metrica fornisce statistiche dettagliate sulla durata delle richieste HTTP:
Metric Name: http.server.request.duration
Unit: s
Value: Sum: 0,23764910 Count: 8 Min: 0,001442 Max: 0,183147
I bucket dell’istogramma mostrano la distribuzione delle richieste per durata:
(-Infinity,0,005]
: 3 richieste molto veloci (< 5ms)(0,005,0,01]
: 3 richieste veloci (5-10ms)(0,01,0,025]
: 1 richiesta media (10-25ms)(0,1,0,25]
: 1 richiesta lenta (100-250ms)
Questa distribuzione aiuta a identificare outlier e problemi di performance.
aspnetcore.routing.match_attempts
Questa metrica traccia l’efficienza del routing:
Metric Name: aspnetcore.routing.match_attempts
Unit: {match_attempt}
Value: 8
Gli attributi chiave includono:
aspnetcore.routing.is_fallback
: indica se è stato usato il fallback routingaspnetcore.routing.match_status
: risultato del matching (“success” o “failure”)
Un numero elevato di tentativi o fallimenti potrebbe indicare problemi nella configurazione delle route.
Implementazione dei Log
OpenTelemetry si integra perfettamente con il sistema di logging di .NET. Per ottenere il massimo beneficio, è importante utilizzare log strutturati che permettono di aggiungere contesto e metadati ai log in modo standardizzato.
var builder = WebApplication.CreateBuilder(args);
var serviceName = builder.Configuration["ServiceName"] ?? "MyService";
var serviceVersion = builder.Configuration["ServiceVersion"] ?? "1.0.0"; // only for demo purposes
builder.Services.AddOpenTelemetry()
.ConfigureResource(/* configurazione precedente */)
.WithTracing(/* configurazione precedente */)
.WithMetrics(/* configurazione precedente */)
.WithLogging(logger => logger
.AddConsoleExporter());
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Esempio completo
Gli exporter sono i componenti che inviano i dati telemetrici a sistemi esterni. In questo esempio utilizziamo sia il Console Exporter (utile in fase di sviluppo) che l’OTLP Exporter (per ambienti di produzione). L’OTLP Exporter permette di inviare i dati a qualsiasi sistema compatibile con il protocollo OpenTelemetry, come Azure Monitor, Datadog, New Relic o un OpenTelemetry Collector. Utilizzando l’OTLP Exporter, il codice rimane indipendente dal vendor di destinazione, permettendo di cambiare il backend di monitoring senza modificare l’applicazione. L’OTLP Exporter supporta due protocolli di trasporto: gRPC (più efficiente e consigliato per la produzione) e HTTP/Protobuf (utile quando gRPC non è disponibile).
Installiamo il pacchetto necessario:
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
Vediamo ora un esempio completo di configurazione che include tracciamento, metriche, logging e l’OTLP exporter:
var builder = WebApplication.CreateBuilder(args);
var serviceName = builder.Configuration["ServiceName"] ?? "MyService";
var serviceVersion = builder.Configuration["ServiceVersion"] ?? "1.0.0"; // only for demo purposes
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(serviceName, serviceVersion)
.AddAttributes(new Dictionary<string, object>
{
["deployment.environment"] = builder.Environment.EnvironmentName,
["host.name"] = Environment.MachineName,
["service.instance.id"] = Guid.NewGuid().ToString()
}))
.WithTracing(trace => trace
.AddAspNetCoreInstrumentation()
.AddConsoleExporter()
.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri("http://localhost:4317"); // only for demo purposes
exporter.Protocol = OtlpExportProtocol.Grpc;
}))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddConsoleExporter()
.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri("http://localhost:4317"); // only for demo purposes
exporter.Protocol = OtlpExportProtocol.Grpc;
}))
.WithLogging(logger => logger
.AddConsoleExporter()
.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri("http://localhost:4317"); // only for demo purposes
exporter.Protocol = OtlpExportProtocol.Grpc;
}));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/orders/{orderId}", async (string orderId, ILogger<Program> logger) =>
{
try
{
logger.LogInformation("Processing order request for {OrderId}", orderId);
await Task.Delay(100); // simulate processing
return Results.Ok(new { OrderId = orderId, Status = "Processed" });
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to process order {OrderId}", orderId);
return Results.StatusCode(500);
}
});
app.Run();
Conclusioni
Con la configurazione implementata, la nostra applicazione ora genera telemetria completa che include:
- Tracce: informazioni dettagliate su ogni richiesta HTTP, inclusi tempi di risposta e metadati
- Metriche: statistiche sulle prestazioni dell’applicazione, come durata delle richieste e tentativi di routing
- Log: log strutturati con context tracking e correlazione automatica con le tracce
OpenTelemetry in .NET richiede pochi passaggi e offre un’infrastruttura robusta per il monitoraggio delle applicazioni. L’utilizzo dell’OTLP Exporter ci permette di essere indipendenti dal vendor di monitoring scelto, facilitando future migrazioni o integrazioni con nuovi strumenti.
Questa serie continua con altri articoli che approfondiscono aspetti avanzati come il tracciamento distribuito, baggage, log strutturati e metriche personalizzate. Potete trovare il codice completo con tutti gli esempi sul repository GitHub.