From 1c4cc3c79f4c9aeac07d92c94669daf3b3d80ab5 Mon Sep 17 00:00:00 2001 From: Spencer Twaddle <7374698+stwaddle@users.noreply.github.com> Date: Thu, 7 May 2026 06:43:04 -0500 Subject: [PATCH] Updated deploy script, and reduced logging noise --- .env.example | 3 ++ deploy.ps1 | 2 +- src/Budget.Api/Program.cs | 28 ++++++++-- src/Budget.Api/appsettings.json | 3 +- .../Data/SlowQueryInterceptor.cs | 53 +++++++++++++++++++ 5 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 src/Budget.Infrastructure/Data/SlowQueryInterceptor.cs diff --git a/.env.example b/.env.example index 3ce81b6..429b6b7 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,6 @@ POSTGRES_PASSWORD=changeme # Note: client OIDC values live in src/Budget.Client/.env (committed). # Override locally in src/Budget.Client/.env.local (gitignored). + +# Trusted reverse-proxy networks (comma-separated CIDRs). Default covers the Docker bridge range. +# Budget__TrustedProxyNetworks=172.16.0.0/12 diff --git a/deploy.ps1 b/deploy.ps1 index 4543b85..48700b2 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -6,7 +6,7 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } docker push $image if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -ssh stwaddle_com "cd stwaddlecom && docker compose up -d --pull always budget" +ssh stwaddle_com "cd stwaddlecom && docker compose pull budget && docker compose down budget && docker compose up -d budget" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Write-Host "Waiting for container to start..." diff --git a/src/Budget.Api/Program.cs b/src/Budget.Api/Program.cs index ffb69ca..e53b3a4 100644 --- a/src/Budget.Api/Program.cs +++ b/src/Budget.Api/Program.cs @@ -41,14 +41,32 @@ var connStr = builder.Configuration.GetConnectionString("DefaultConnection") $"Username={builder.Configuration["POSTGRES_USER"] ?? "budget"};" + $"Password={builder.Configuration["POSTGRES_PASSWORD"] ?? "changeme"}"; -builder.Services.AddDbContext(opt => opt.UseNpgsql(connStr)); +builder.Services.AddSingleton(); +builder.Services.AddDbContext((sp, opt) => +{ + opt.UseNpgsql(connStr); + opt.AddInterceptors(sp.GetRequiredService()); +}); builder.Services.Configure(options => { - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; - options.KnownProxies.Clear(); + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor + | ForwardedHeaders.XForwardedProto + | ForwardedHeaders.XForwardedHost; + options.KnownIPNetworks.Clear(); - options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("172.20.0.0/16")); + options.KnownProxies.Clear(); + + var trustedNetworks = builder.Configuration["Budget:TrustedProxyNetworks"] ?? "172.16.0.0/12"; + foreach (var cidr in trustedNetworks.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + var parts = cidr.Split('/'); + var prefix = System.Net.IPAddress.Parse(parts[0]); + var prefixLength = parts.Length > 1 + ? int.Parse(parts[1]) + : (prefix.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 ? 128 : 32); + options.KnownIPNetworks.Add(new System.Net.IPNetwork(prefix, prefixLength)); + } }); var oidc = builder.Configuration.GetSection("Oidc"); @@ -127,6 +145,8 @@ while (true) jwtOpts.ConfigurationManager!.RequestRefresh(); await jwtOpts.ConfigurationManager!.GetConfigurationAsync(CancellationToken.None); startupLogger.LogInformation("OIDC discovery succeeded"); + startupLogger.LogInformation("Trusted proxy networks: {Networks}", + app.Configuration["Budget:TrustedProxyNetworks"] ?? "172.16.0.0/12 (default)"); break; } catch (Exception ex) diff --git a/src/Budget.Api/appsettings.json b/src/Budget.Api/appsettings.json index 5ba93c8..9586be5 100644 --- a/src/Budget.Api/appsettings.json +++ b/src/Budget.Api/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "AllowedHosts": "budget.stwaddle.com", diff --git a/src/Budget.Infrastructure/Data/SlowQueryInterceptor.cs b/src/Budget.Infrastructure/Data/SlowQueryInterceptor.cs new file mode 100644 index 0000000..d3a6817 --- /dev/null +++ b/src/Budget.Infrastructure/Data/SlowQueryInterceptor.cs @@ -0,0 +1,53 @@ +using System.Data.Common; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace Budget.Infrastructure.Data; + +public class SlowQueryInterceptor(ILogger logger) : DbCommandInterceptor +{ + private static readonly TimeSpan Threshold = TimeSpan.FromMilliseconds(500); + + public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result) + { + LogIfSlow(command, eventData.Duration); + return result; + } + + public override ValueTask ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default) + { + LogIfSlow(command, eventData.Duration); + return new ValueTask(result); + } + + public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result) + { + LogIfSlow(command, eventData.Duration); + return result; + } + + public override ValueTask NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default) + { + LogIfSlow(command, eventData.Duration); + return new ValueTask(result); + } + + public override object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result) + { + LogIfSlow(command, eventData.Duration); + return result; + } + + public override ValueTask ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object? result, CancellationToken cancellationToken = default) + { + LogIfSlow(command, eventData.Duration); + return new ValueTask(result); + } + + private void LogIfSlow(DbCommand command, TimeSpan duration) + { + if (duration >= Threshold) + logger.LogWarning("Slow query ({Duration}ms): {CommandText}", + (long)duration.TotalMilliseconds, command.CommandText); + } +}