at main 5.5 kB view raw
1using Microsoft.AspNetCore.Builder; 2using Microsoft.AspNetCore.Diagnostics.HealthChecks; 3using Microsoft.Extensions.DependencyInjection; 4using Microsoft.Extensions.Diagnostics.HealthChecks; 5using Microsoft.Extensions.Hosting; 6using Microsoft.Extensions.Logging; 7using OpenTelemetry; 8using OpenTelemetry.Metrics; 9using OpenTelemetry.Trace; 10using Serilog; 11using Serilog.Events; 12 13namespace AltBot.ServiceDefaults; 14 15// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. 16// This project should be referenced by each service project in your solution. 17// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults 18public static class StartupExtensions 19{ 20 private const string HealthEndpointPath = "/health"; 21 private const string AlivenessEndpointPath = "/alive"; 22 23 public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 24 { 25 builder.ConfigureSerilog(); 26 27 builder.ConfigureOpenTelemetry(); 28 29 builder.AddDefaultHealthChecks(); 30 31 builder.Services.AddExceptionHandler<HaltExceptionHandler>(); 32 builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); 33 34 builder.Services.AddServiceDiscovery(); 35 36 builder.Services.ConfigureHttpClientDefaults(http => 37 { 38 // Turn on resilience by default 39 http.AddStandardResilienceHandler(); 40 41 // Turn on service discovery by default 42 http.AddServiceDiscovery(); 43 }); 44 45 return builder; 46 } 47 48 private static TBuilder ConfigureSerilog<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 49 { 50 // Create Serilog logger configuration 51 var loggerConfiguration = new LoggerConfiguration() 52 .ReadFrom.Configuration(builder.Configuration) 53 .MinimumLevel.Information() 54 .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) 55 .MinimumLevel.Override("System", LogEventLevel.Warning) 56 .Enrich.FromLogContext() 57 .Enrich.WithEnvironmentName() 58 .Enrich.WithThreadId(); 59 60 // Add console sink in development 61 if (builder.Environment.IsDevelopment()) 62 { 63 loggerConfiguration 64 .WriteTo.Console() 65 .WriteTo.Debug(); 66 } 67 else 68 { 69 loggerConfiguration 70 .WriteTo.Console(); 71 } 72 73 // Create logger 74 Log.Logger = loggerConfiguration.CreateLogger(); 75 76 // Configure application to use Serilog 77 builder.Logging.AddSerilog(dispose: true); 78 79 return builder; 80 } 81 82 public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 83 { 84 builder.Logging.AddOpenTelemetry(logging => 85 { 86 logging.IncludeFormattedMessage = true; 87 logging.IncludeScopes = true; 88 }); 89 90 builder.Services.AddOpenTelemetry() 91 .WithMetrics(metrics => 92 { 93 metrics.AddAspNetCoreInstrumentation() 94 .AddHttpClientInstrumentation() 95 .AddRuntimeInstrumentation(); 96 }) 97 .WithTracing(tracing => 98 { 99 tracing.AddSource(builder.Environment.ApplicationName) 100 .AddAspNetCoreInstrumentation(tracing => 101 // Exclude health check requests from tracing 102 tracing.Filter = context => 103 !context.Request.Path.StartsWithSegments(HealthEndpointPath) 104 && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) 105 ) 106 .AddHttpClientInstrumentation(); 107 }); 108 109 builder.AddOpenTelemetryExporters(); 110 111 return builder; 112 } 113 114 private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 115 { 116 var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); 117 118 if (useOtlpExporter) 119 { 120 builder.Services.AddOpenTelemetry().UseOtlpExporter(); 121 } 122 123 return builder; 124 } 125 126 public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 127 { 128 builder.Services.AddHealthChecks() 129 // Add a default liveness check to ensure app is responsive 130 .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); 131 132 return builder; 133 } 134 135 public static WebApplication MapDefaultEndpoints(this WebApplication app) 136 { 137 // Adding health checks endpoints to applications in non-development environments has security implications. 138 // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. 139 if (app.Environment.IsDevelopment()) 140 { 141 // All health checks must pass for app to be considered ready to accept traffic after starting 142 app.MapHealthChecks(HealthEndpointPath); 143 144 // Only health checks tagged with the "live" tag must pass for app to be considered alive 145 app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions 146 { 147 Predicate = r => r.Tags.Contains("live") 148 }); 149 } 150 151 return app; 152 } 153}