Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,49 @@ jobs:
Write-Warning "Warmup selhal: $($_.Exception.Message)"
}
}

- name: Ověření zdraví aplikace
shell: powershell
env:
HEALTH_URL: ${{ vars.HEALTH_URL }}
run: |
$ErrorActionPreference = 'Stop'
if (-not $env:HEALTH_URL) { throw 'Chybí proměnná HEALTH_URL (vars.HEALTH_URL).' }

$maxAttempts = 5
$delaySec = 6
$lastError = $null
$lastBody = $null

for ($i = 1; $i -le $maxAttempts; $i++) {
try {
$resp = Invoke-WebRequest -Uri $env:HEALTH_URL -UseBasicParsing -TimeoutSec 10
if ($resp.StatusCode -eq 200) {
Write-Host "Health check OK (pokus $i, status 200)."
Write-Host $resp.Content
exit 0
}
$lastError = "HTTP $($resp.StatusCode)"
$lastBody = $resp.Content
} catch {
$webResp = $_.Exception.Response
if ($webResp) {
$lastError = "HTTP $([int]$webResp.StatusCode)"
try {
$reader = New-Object System.IO.StreamReader($webResp.GetResponseStream())
$lastBody = $reader.ReadToEnd()
} catch { }
} else {
$lastError = $_.Exception.Message
}
}

if ($i -lt $maxAttempts) {
Write-Host "Pokus ${i}/${maxAttempts}: $lastError. Zkousim znovu za $delaySec s..."
Start-Sleep -Seconds $delaySec
}
}

Write-Host "::error::Health check selhal po $maxAttempts pokusech. Posledni chyba: $lastError"
if ($lastBody) { Write-Host "Telo posledni odpovedi:`n$lastBody" }
exit 1
2 changes: 2 additions & 0 deletions OdectyMVC/DataLayer/RabbitMQProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class RabbitMQProvider : IDisposable
private readonly bool connected = false;
private bool first = true;

public bool IsConnected => connected && connection?.IsOpen == true;

public RabbitMQProvider(IOptions<RabbitMQSettings> options)
{
try
Expand Down
14 changes: 14 additions & 0 deletions OdectyMVC/HealthChecks/GaugeFileHealthCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace OdectyMVC.HealthChecks;

public class GaugeFileHealthCheck : IHealthCheck
{
private const string FileName = "GaugeList.json";

public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken = default)
=> Task.FromResult(File.Exists(FileName)
? HealthCheckResult.Healthy($"{FileName} is present")
: HealthCheckResult.Unhealthy($"{FileName} is missing"));
}
17 changes: 17 additions & 0 deletions OdectyMVC/HealthChecks/RabbitMQHealthCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using OdectyMVC.DataLayer;

namespace OdectyMVC.HealthChecks;

public class RabbitMQHealthCheck : IHealthCheck
{
private readonly RabbitMQProvider provider;

public RabbitMQHealthCheck(RabbitMQProvider provider) => this.provider = provider;

public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken = default)
=> Task.FromResult(provider.IsConnected
? HealthCheckResult.Healthy("RabbitMQ connection is open")
: HealthCheckResult.Unhealthy("RabbitMQ connection is not available"));
}
11 changes: 8 additions & 3 deletions OdectyMVC/OdectyMVC.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.9" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.14.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.10.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.10.0" />
<PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
</ItemGroup>
<PropertyGroup>
Expand Down
56 changes: 55 additions & 1 deletion OdectyMVC/Program.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,55 @@
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;
using OdectyMVC;
using OdectyMVC.Application;
using OdectyMVC.Contracts;
using OdectyMVC.DataLayer;
using OdectyMVC.HealthChecks;
using OdectyMVC.Middleware;
using OdectyMVC.Options;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog;
using Serilog.Sinks.OpenTelemetry;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

const string serviceName = "OdectyMVC";
var otlpEndpoint = builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"] ?? "http://localhost:4317";

builder.Host.UseSerilog((ctx, lc) => lc
.ReadFrom.Configuration(ctx.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.OpenTelemetry(opts =>
{
opts.Endpoint = otlpEndpoint;
opts.Protocol = OtlpProtocol.Grpc;
opts.ResourceAttributes = new Dictionary<string, object>
{
["service.name"] = serviceName
};
}));

builder.Services.AddOpenTelemetry()
.ConfigureResource(r => r.AddService(serviceName))
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("RabbitMQ.Client.Publisher", "RabbitMQ.Client.Subscriber")
.AddOtlpExporter())
.WithMetrics(m => m
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter());

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.Configure<RabbitMQSettings>(builder.Configuration.GetSection("RabbitMQSettings"));
Expand All @@ -25,7 +64,10 @@
builder.Services.AddSingleton<IMessageQueue, MessageQueue>();
builder.Services.AddSingleton<RabbitMQProvider>();
builder.Services.AddHostedService<IncomeMessageBackgroundService>();
builder.Logging.AddEventLog(conf => conf.SourceName = "OdectyMVC");

builder.Services.AddHealthChecks()
.AddCheck<RabbitMQHealthCheck>("rabbitmq", tags: new[] { "ready" })
.AddCheck<GaugeFileHealthCheck>("gauge-file", tags: new[] { "ready" });

#if !DEBUG
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
Expand All @@ -48,7 +90,7 @@
{
OnTokenValidated = context =>
{
var email = context.Principal.FindFirst("preferred_username")?.Value

Check warning on line 93 in OdectyMVC/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
?? context.Principal.FindFirst(ClaimTypes.Email)?.Value;
if (string.IsNullOrEmpty(email) || !allowedEmails.Contains(email.ToLowerInvariant()))
{
Expand Down Expand Up @@ -107,6 +149,18 @@
app.UseAuthorization();
#endif

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
}).AllowAnonymous();

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
}).AllowAnonymous();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{value?}");
Expand Down
Loading