AltHeroes Bot v2
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}