11using System . Threading . RateLimiting ;
2- using Azure . Monitor . OpenTelemetry . AspNetCore ;
32using EssentialCSharp . Chat . Common . Extensions ;
43using EssentialCSharp . Web . Areas . Identity . Data ;
54using EssentialCSharp . Web . Areas . Identity . Services . PasswordValidators ;
1413using Microsoft . AspNetCore . Identity ;
1514using Microsoft . AspNetCore . Identity . UI . Services ;
1615using Microsoft . AspNetCore . RateLimiting ;
16+ using Azure . Monitor . OpenTelemetry . AspNetCore ;
17+ using Microsoft . AspNetCore . Diagnostics . HealthChecks ;
1718using Microsoft . EntityFrameworkCore ;
19+ using Microsoft . Extensions . Diagnostics . HealthChecks ;
20+ using OpenTelemetry ;
21+ using OpenTelemetry . Metrics ;
22+ using OpenTelemetry . Trace ;
1823
1924namespace EssentialCSharp . Web ;
2025
@@ -24,6 +29,47 @@ private static void Main(string[] args)
2429 {
2530 WebApplicationBuilder builder = WebApplication . CreateBuilder ( args ) ;
2631
32+ // Health checks (liveness/readiness probes for ACA and standalone hosting)
33+ builder . Services . AddHealthChecks ( )
34+ . AddCheck ( "self" , ( ) => HealthCheckResult . Healthy ( ) , [ "live" ] ) ;
35+
36+ // OpenTelemetry — Aspire injects OTEL_EXPORTER_OTLP_ENDPOINT when hosted.
37+ // Azure Monitor is enabled in production when APPLICATIONINSIGHTS_CONNECTION_STRING is set.
38+ bool useAzureMonitor = ! string . IsNullOrEmpty ( builder . Configuration [ "APPLICATIONINSIGHTS_CONNECTION_STRING" ] ) ;
39+ builder . Logging . AddOpenTelemetry ( logging =>
40+ {
41+ logging . IncludeFormattedMessage = true ;
42+ logging . IncludeScopes = true ;
43+ } ) ;
44+ builder . Services . AddOpenTelemetry ( )
45+ . WithMetrics ( metrics => metrics
46+ . AddAspNetCoreInstrumentation ( )
47+ . AddHttpClientInstrumentation ( )
48+ . AddRuntimeInstrumentation ( ) )
49+ . WithTracing ( tracing =>
50+ {
51+ tracing . AddSource ( builder . Environment . ApplicationName ) ;
52+ if ( ! useAzureMonitor )
53+ {
54+ tracing
55+ . AddAspNetCoreInstrumentation ( t =>
56+ t . Filter = ctx =>
57+ ! ctx . Request . Path . StartsWithSegments ( "/health" )
58+ && ! ctx . Request . Path . StartsWithSegments ( "/alive" ) )
59+ . AddHttpClientInstrumentation ( )
60+ . AddSqlClientInstrumentation ( ) ;
61+ }
62+ } ) ;
63+ if ( ! string . IsNullOrWhiteSpace ( builder . Configuration [ "OTEL_EXPORTER_OTLP_ENDPOINT" ] ) )
64+ builder . Services . AddOpenTelemetry ( ) . UseOtlpExporter ( ) ;
65+ if ( useAzureMonitor )
66+ builder . Services . AddOpenTelemetry ( ) . UseAzureMonitor ( ) ;
67+
68+ // HttpClient defaults — standard retry/circuit breaker for all named clients.
69+ builder . Services . ConfigureHttpClientDefaults ( http => http . AddStandardResilienceHandler ( ) ) ;
70+
71+
72+
2773 builder . Services . Configure < ForwardedHeadersOptions > ( options =>
2874 {
2975 options . ForwardedHeaders =
@@ -39,37 +85,12 @@ private static void Main(string[] args)
3985 ConfigurationManager configuration = builder . Configuration ;
4086 string connectionString = builder . Configuration . GetConnectionString ( "EssentialCSharpWebContextConnection" ) ?? throw new InvalidOperationException ( "Connection string 'EssentialCSharpWebContextConnection' not found." ) ;
4187
42- builder . Logging . AddConsole ( ) ;
43- builder . Services . AddHealthChecks ( ) ;
44-
4588 // Create a logger that's accessible throughout the entire method
4689 var loggerFactory = LoggerFactory . Create ( loggingBuilder =>
4790 loggingBuilder . AddConsole ( ) . SetMinimumLevel ( LogLevel . Information ) ) ;
4891 var initialLogger = loggerFactory . CreateLogger < Program > ( ) ;
4992
50- if ( ! builder . Environment . IsDevelopment ( ) )
51- {
52- // Configure Azure Application Insights with OpenTelemetry only if connection string is available
53- var appInsightsConnectionString = builder . Configuration . GetConnectionString ( "ApplicationInsights" )
54- ?? builder . Configuration [ "ApplicationInsights:ConnectionString" ] ;
55-
56- if ( ! string . IsNullOrEmpty ( appInsightsConnectionString ) )
57- {
58- builder . Services . AddOpenTelemetry ( ) . UseAzureMonitor (
59- options =>
60- {
61- options . ConnectionString = appInsightsConnectionString ;
62- } ) ;
63- builder . Services . AddApplicationInsightsTelemetry ( ) ;
64- builder . Services . AddServiceProfiler ( ) ;
65- }
66- else
67- {
68- initialLogger . LogWarning ( "Application Insights connection string not found. Telemetry collection will be disabled." ) ;
69- }
70- }
71-
72- builder . Services . AddDbContext < EssentialCSharpWebContext > ( options => options . UseSqlServer ( connectionString ) ) ;
93+ builder . Services . AddDbContext < EssentialCSharpWebContext > ( options => options . UseSqlServer ( connectionString , sql => sql . EnableRetryOnFailure ( 5 ) ) ) ;
7394 builder . Services . AddDefaultIdentity < EssentialCSharpWebUser > ( options =>
7495 {
7596 // Password settings
@@ -289,7 +310,11 @@ await context.HttpContext.Response.WriteAsync(
289310 app . UseForwardedHeaders ( ) ;
290311 }
291312
292- app . MapHealthChecks ( "/healthz" ) ;
313+ app . MapHealthChecks ( "/health" ) ;
314+ app . MapHealthChecks ( "/alive" , new HealthCheckOptions
315+ {
316+ Predicate = r => r . Tags . Contains ( "live" )
317+ } ) ;
293318
294319 app . UseHttpsRedirection ( ) ;
295320 app . UseStaticFiles ( ) ;
0 commit comments