From 8352d341dcb5e68a93acfd72eeda4d83c21a6af2 Mon Sep 17 00:00:00 2001 From: Zefek Date: Fri, 15 May 2026 21:22:38 +0200 Subject: [PATCH 1/4] Telemetrie --- OdectyMVC/OdectyMVC.csproj | 10 +++++++--- OdectyMVC/Program.cs | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/OdectyMVC/OdectyMVC.csproj b/OdectyMVC/OdectyMVC.csproj index 8b2e3c0..5e5d5bc 100644 --- a/OdectyMVC/OdectyMVC.csproj +++ b/OdectyMVC/OdectyMVC.csproj @@ -8,12 +8,16 @@ - - - + + + + + + + diff --git a/OdectyMVC/Program.cs b/OdectyMVC/Program.cs index b8cfa0a..bca3a4b 100644 --- a/OdectyMVC/Program.cs +++ b/OdectyMVC/Program.cs @@ -8,9 +8,45 @@ using OdectyMVC.DataLayer; 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 + { + ["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(builder.Configuration.GetSection("RabbitMQSettings")); @@ -25,7 +61,6 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); -builder.Logging.AddEventLog(conf => conf.SourceName = "OdectyMVC"); #if !DEBUG builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) From 48bc8c2849d15d262e14e579537883a50a6a440b Mon Sep 17 00:00:00 2001 From: Zefek Date: Sat, 16 May 2026 21:00:57 +0200 Subject: [PATCH 2/4] healthcheck --- OdectyMVC/DataLayer/RabbitMQProvider.cs | 2 ++ .../HealthChecks/GaugeFileHealthCheck.cs | 14 ++++++++++++++ OdectyMVC/HealthChecks/RabbitMQHealthCheck.cs | 17 +++++++++++++++++ OdectyMVC/OdectyMVC.csproj | 1 + OdectyMVC/Program.cs | 19 +++++++++++++++++++ 5 files changed, 53 insertions(+) create mode 100644 OdectyMVC/HealthChecks/GaugeFileHealthCheck.cs create mode 100644 OdectyMVC/HealthChecks/RabbitMQHealthCheck.cs diff --git a/OdectyMVC/DataLayer/RabbitMQProvider.cs b/OdectyMVC/DataLayer/RabbitMQProvider.cs index 2a4ab2b..d6d1902 100644 --- a/OdectyMVC/DataLayer/RabbitMQProvider.cs +++ b/OdectyMVC/DataLayer/RabbitMQProvider.cs @@ -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 options) { try diff --git a/OdectyMVC/HealthChecks/GaugeFileHealthCheck.cs b/OdectyMVC/HealthChecks/GaugeFileHealthCheck.cs new file mode 100644 index 0000000..955cf8d --- /dev/null +++ b/OdectyMVC/HealthChecks/GaugeFileHealthCheck.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace OdectyMVC.HealthChecks; + +public class GaugeFileHealthCheck : IHealthCheck +{ + private const string FileName = "GaugeList.json"; + + public Task CheckHealthAsync( + HealthCheckContext context, CancellationToken cancellationToken = default) + => Task.FromResult(File.Exists(FileName) + ? HealthCheckResult.Healthy($"{FileName} is present") + : HealthCheckResult.Unhealthy($"{FileName} is missing")); +} diff --git a/OdectyMVC/HealthChecks/RabbitMQHealthCheck.cs b/OdectyMVC/HealthChecks/RabbitMQHealthCheck.cs new file mode 100644 index 0000000..42b61e3 --- /dev/null +++ b/OdectyMVC/HealthChecks/RabbitMQHealthCheck.cs @@ -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 CheckHealthAsync( + HealthCheckContext context, CancellationToken cancellationToken = default) + => Task.FromResult(provider.IsConnected + ? HealthCheckResult.Healthy("RabbitMQ connection is open") + : HealthCheckResult.Unhealthy("RabbitMQ connection is not available")); +} diff --git a/OdectyMVC/OdectyMVC.csproj b/OdectyMVC/OdectyMVC.csproj index 5e5d5bc..7f47d22 100644 --- a/OdectyMVC/OdectyMVC.csproj +++ b/OdectyMVC/OdectyMVC.csproj @@ -7,6 +7,7 @@ + diff --git a/OdectyMVC/Program.cs b/OdectyMVC/Program.cs index bca3a4b..e986fec 100644 --- a/OdectyMVC/Program.cs +++ b/OdectyMVC/Program.cs @@ -1,11 +1,14 @@ +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; @@ -62,6 +65,10 @@ builder.Services.AddSingleton(); builder.Services.AddHostedService(); +builder.Services.AddHealthChecks() + .AddCheck("rabbitmq", tags: new[] { "ready" }) + .AddCheck("gauge-file", tags: new[] { "ready" }); + #if !DEBUG builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddScheme("basic", null) @@ -142,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?}"); From 652206d90aa66f3bd77a827a1fd12b2c31204c69 Mon Sep 17 00:00:00 2001 From: Zefek Date: Sat, 16 May 2026 21:13:02 +0200 Subject: [PATCH 3/4] =?UTF-8?q?Ov=C4=9B=C5=99en=C3=AD=20po=20nasazen=C3=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e482a7a..655dc44 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 From 936e810c216974475f4ea53e25c9a2e44eb032a2 Mon Sep 17 00:00:00 2001 From: Zefek Date: Sat, 16 May 2026 21:20:16 +0200 Subject: [PATCH 4/4] Oprava --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 655dc44..ddc12df 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -194,7 +194,7 @@ jobs: } if ($i -lt $maxAttempts) { - Write-Host "Pokus $i/$maxAttempts: $lastError. Zkousim znovu za $delaySec s..." + Write-Host "Pokus ${i}/${maxAttempts}: $lastError. Zkousim znovu za $delaySec s..." Start-Sleep -Seconds $delaySec } }