diff --git a/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs b/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs
index f7d9e87423a..08203506e87 100644
--- a/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs
+++ b/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs
@@ -81,13 +81,7 @@ public async Task StartAsync()
}
if (_exposePort is not null)
{
- IMetricServer metricServer = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
- // MetricServer uses HttpListener which on Windows either needs
- // permissions at OS or Admin mode, whereas KestrelMetricServer doesn't need those
- new KestrelMetricServer(_exposePort.Value) :
- // KestrelMetricServer intercept SIGTERM causing exitcode to be incorrect
- new MetricServer(_exposePort.Value);
- metricServer.Start();
+ new NethermindKestrelMetricServer(_exposePort.Value).Start();
}
await Task.Factory.StartNew(() => _metricsController.StartUpdating(), TaskCreationOptions.LongRunning);
if (_logger.IsInfo) _logger.Info($"Started monitoring for the group: {_options.Group}, instance: {_options.Instance}");
diff --git a/src/Nethermind/Nethermind.Monitoring/NethermindKestrelMetricServer.cs b/src/Nethermind/Nethermind.Monitoring/NethermindKestrelMetricServer.cs
new file mode 100644
index 00000000000..1b6e5b4c50e
--- /dev/null
+++ b/src/Nethermind/Nethermind.Monitoring/NethermindKestrelMetricServer.cs
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+#nullable enable
+using System;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.Extensions.DependencyInjection;
+using Prometheus;
+
+namespace Nethermind.Monitoring;
+
+///
+/// Copy of KestrelMetricServer but does not wait for Ctrl-C so that it does not intercept exit code
+///
+public sealed class NethermindKestrelMetricServer : MetricHandler
+{
+ public NethermindKestrelMetricServer(int port, string url = "/metrics", CollectorRegistry? registry = null, X509Certificate2? certificate = null) : this("+", port, url, registry, certificate)
+ {
+ }
+
+ public NethermindKestrelMetricServer(string hostname, int port, string url = "/metrics", CollectorRegistry? registry = null, X509Certificate2? certificate = null) : this(LegacyOptions(hostname, port, url, registry, certificate))
+ {
+ }
+
+ private static KestrelMetricServerOptions LegacyOptions(string hostname, int port, string url, CollectorRegistry? registry, X509Certificate2? certificate) =>
+ new KestrelMetricServerOptions
+ {
+ Hostname = hostname,
+ Port = (ushort)port,
+ Url = url,
+ Registry = registry,
+ TlsCertificate = certificate,
+ };
+
+ public NethermindKestrelMetricServer(KestrelMetricServerOptions options)
+ {
+ _hostname = options.Hostname;
+ _port = options.Port;
+ _url = options.Url;
+ _certificate = options.TlsCertificate;
+
+ // We use one callback to apply the legacy settings, and from within this we call the real callback.
+ _configureExporter = settings =>
+ {
+ // Legacy setting, may be overridden by ConfigureExporter.
+ settings.Registry = options.Registry;
+
+ if (options.ConfigureExporter != null)
+ options.ConfigureExporter(settings);
+ };
+ }
+
+ private readonly string _hostname;
+ private readonly int _port;
+ private readonly string _url;
+
+ private readonly X509Certificate2? _certificate;
+
+ private readonly Action _configureExporter;
+
+ protected override Task StartServer(CancellationToken cancel)
+ {
+ var s = _certificate != null ? "s" : "";
+ var hostAddress = $"http{s}://{_hostname}:{_port}";
+
+ // If the caller needs to customize any of this, they can just set up their own web host and inject the middleware.
+ var builder = new WebHostBuilder()
+ .UseKestrel()
+ .UseIISIntegration()
+ .Configure(app =>
+ {
+ app.UseMetricServer(_configureExporter, _url);
+
+ // If there is any URL prefix, we just redirect people going to root URL to our prefix.
+ if (!string.IsNullOrWhiteSpace(_url.Trim('/')))
+ {
+ app.MapWhen(context => context.Request.Path.Value?.Trim('/') == "",
+ configuration =>
+ {
+ configuration.Use((HttpContext context, RequestDelegate next) =>
+ {
+ context.Response.Redirect(_url);
+ return Task.CompletedTask;
+ });
+ });
+ }
+ });
+
+ if (_certificate != null)
+ {
+ builder = builder.ConfigureServices(services =>
+ {
+ Action configureEndpoint = options =>
+ {
+ options.UseHttps(_certificate);
+ };
+
+ services.Configure(options =>
+ {
+ options.Listen(IPAddress.Any, _port, configureEndpoint);
+ });
+ });
+ }
+ else
+ {
+ builder = builder.UseUrls(hostAddress);
+ }
+
+ var webHost = builder.Build();
+ webHost.Start();
+
+ // This is what changed
+ // return webHost.WaitForShutdownAsync(cancel);
+
+ return webHost.RunAsync(cancel);
+ }
+}