using Microsoft.AspNetCore.Builder;using Microsoft.AspNetCore.Diagnostics.HealthChecks;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Diagnostics.HealthChecks;using Microsoft.Extensions.Logging;using OpenTelemetry;using OpenTelemetry.Metrics;using OpenTelemetry.Trace;
namespace Microsoft.Extensions.Hosting;
public static class Extensions{ private const string HealthEndpointPath = "/health"; private const string AlivenessEndpointPath = "/alive";
/// <summary> /// Adds common Aspire services: OpenTelemetry, health checks, /// service discovery, and HTTP resilience. /// </summary> public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder { builder.ConfigureOpenTelemetry(); builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http => { // Resilience: retries, circuit breaker, timeouts http.AddStandardResilienceHandler();
// Service discovery: resolve service names to addresses http.AddServiceDiscovery(); });
return builder; }
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder { // Logging builder.Logging.AddOpenTelemetry(logging => { logging.IncludeFormattedMessage = true; logging.IncludeScopes = true; });
builder.Services.AddOpenTelemetry() // Metrics .WithMetrics(metrics => { metrics .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation(); }) // Tracing .WithTracing(tracing => { tracing .AddSource(builder.Environment.ApplicationName) .AddAspNetCoreInstrumentation(options => // Exclude health checks from traces options.Filter = context => !context.Request.Path.StartsWithSegments(HealthEndpointPath) && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath)) .AddHttpClientInstrumentation(); });
builder.AddOpenTelemetryExporters();
return builder; }
private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder { // Use OTLP exporter if endpoint is configured (Aspire Dashboard, Jaeger, etc.) var useOtlp = !string.IsNullOrWhiteSpace( builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlp) { builder.Services.AddOpenTelemetry().UseOtlpExporter(); }
return builder; }
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder { builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return builder; }
/// <summary> /// Maps health check endpoints. Call after UseRouting(). /// </summary> public static WebApplication MapDefaultEndpoints(this WebApplication app) { // Only expose in development - see security note below if (app.Environment.IsDevelopment()) { // Readiness: all health checks must pass app.MapHealthChecks(HealthEndpointPath);
// Liveness: only "live" tagged checks app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") }); }
return app; }}