diff --git a/Foxnouns.Backend/Config.cs b/Foxnouns.Backend/Config.cs index 6766b3b..62a107f 100644 --- a/Foxnouns.Backend/Config.cs +++ b/Foxnouns.Backend/Config.cs @@ -10,6 +10,7 @@ public class Config public string MediaBaseUrl { get; set; } = null!; public string Address => $"http://{Host}:{Port}"; + public string? MetricsAddress => Logging.MetricsPort != null ? $"http://{Host}:{Logging.MetricsPort}" : null; public LoggingConfig Logging { get; init; } = new(); public DatabaseConfig Database { get; init; } = new(); @@ -27,6 +28,7 @@ public class Config public bool SentryTracing { get; init; } = false; public double SentryTracesSampleRate { get; init; } = 0.0; public bool LogQueries { get; init; } = false; + public int? MetricsPort { get; init; } } public class DatabaseConfig diff --git a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs index 38279ac..bcd29f4 100644 --- a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs +++ b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs @@ -1,3 +1,6 @@ +using App.Metrics; +using App.Metrics.AspNetCore; +using App.Metrics.Formatters.Prometheus; using Foxnouns.Backend.Database; using Foxnouns.Backend.Jobs; using Foxnouns.Backend.Middleware; @@ -6,6 +9,7 @@ using Microsoft.EntityFrameworkCore; using NodaTime; using Serilog; using Serilog.Events; +using IClock = NodaTime.IClock; namespace Foxnouns.Backend.Extensions; @@ -29,6 +33,7 @@ public static class WebApplicationExtensions .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning) + .MinimumLevel.Override("Hangfire", LogEventLevel.Information) .WriteTo.Console(); if (config.Logging.SeqLogUrl != null) @@ -52,6 +57,36 @@ public static class WebApplicationExtensions return config; } + public static WebApplicationBuilder AddMetrics(this WebApplicationBuilder builder) + { + var config = builder.Configuration.Get() ?? new(); + var metrics = AppMetrics.CreateDefaultBuilder() + .OutputMetrics.AsPrometheusPlainText() + .Build(); + + builder.Services.AddSingleton(metrics); + builder.Services.AddSingleton(metrics); + + builder.WebHost + .ConfigureMetrics(metrics) + .UseMetrics(opts => + { + opts.EndpointOptions = options => + { + // Metrics must listen on a separate port for security reasons. If no metrics port is set, disable the endpoint entirely. + options.MetricsEndpointEnabled = config.Logging.MetricsPort != null; + options.EnvironmentInfoEndpointEnabled = config.Logging.MetricsPort != null; + options.MetricsTextEndpointEnabled = false; + options.MetricsEndpointOutputFormatter = metrics.OutputMetricsFormatters + .OfType().First(); + }; + }) + .UseMetricsWebTracking() + .ConfigureAppMetricsHostingConfiguration(opts => { opts.AllEndpointsPort = config.Logging.MetricsPort; }); + + return builder; + } + public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder builder) { var file = Environment.GetEnvironmentVariable("FOXNOUNS_CONFIG_FILE") ?? "config.ini"; diff --git a/Foxnouns.Backend/Foxnouns.Backend.csproj b/Foxnouns.Backend/Foxnouns.Backend.csproj index d554482..b43e2df 100644 --- a/Foxnouns.Backend/Foxnouns.Backend.csproj +++ b/Foxnouns.Backend/Foxnouns.Backend.csproj @@ -6,6 +6,9 @@ + + + diff --git a/Foxnouns.Backend/Metrics.cs b/Foxnouns.Backend/Metrics.cs new file mode 100644 index 0000000..a830ab8 --- /dev/null +++ b/Foxnouns.Backend/Metrics.cs @@ -0,0 +1,6 @@ +namespace Foxnouns.Backend; + +public static class Metrics +{ + +} \ No newline at end of file diff --git a/Foxnouns.Backend/Program.cs b/Foxnouns.Backend/Program.cs index 4a311a7..7230598 100644 --- a/Foxnouns.Backend/Program.cs +++ b/Foxnouns.Backend/Program.cs @@ -21,7 +21,7 @@ var builder = WebApplication.CreateBuilder(args); var config = builder.AddConfiguration(); -builder.AddSerilog(); +builder.AddSerilog().AddMetrics(); builder.WebHost .UseSentry(opts => @@ -75,9 +75,9 @@ builder.Services .Build()); builder.Services.AddHangfire(c => c.UseSentry().UseRedisStorage(config.Jobs.Redis, new RedisStorageOptions -{ - Prefix = "foxnouns_" -})) + { + Prefix = "foxnouns_" + })) .AddHangfireServer(options => { options.WorkerCount = config.Jobs.Workers; }); var app = builder.Build(); @@ -103,6 +103,7 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions app.Urls.Clear(); app.Urls.Add(config.Address); +if (config.MetricsAddress != null) app.Urls.Add(config.MetricsAddress); // Fire off the periodic tasks loop in the background _ = new Timer(_ => diff --git a/Foxnouns.Backend/config.example.ini b/Foxnouns.Backend/config.example.ini index c8d38ca..e316c8e 100644 --- a/Foxnouns.Backend/config.example.ini +++ b/Foxnouns.Backend/config.example.ini @@ -20,6 +20,8 @@ SentryTracing = true SentryTracesSampleRate = 1.0 ; Whether to log SQL queries. Note that this is very verbose. Defaults to false. LogQueries = false +; The port the /metrics endpoint will listen on. If not set, metrics will be disabled. +MetricsPort = 5001 [Database] ; The database URL in ADO.NET format.