deba5ed115
Move the per-request correlation context and secret redaction off the MEL mechanism onto the Serilog primitives the shared bootstrap consumes. - GatewayRequestLoggingMiddlewareExtensions now pushes the correlation properties (SessionId / WorkerProcessId / CorrelationId / CommandMethod / ClientIdentity) via Serilog LogContext.PushProperty for the request lifetime and pops them on completion, replacing MEL ILogger.BeginScope. Header parsing and property names are unchanged; GatewayLogScope remains the data holder. - Add GatewayLogRedactorAdapter : ILogRedactor delegating to the existing GatewayLogRedactor policy (mxgw_ bearer tokens / credential-bearing command values), registered as a singleton so the shared RedactionEnricher masks secrets on every event. Remove the now-dead GatewayLoggerExtensions MEL helper. - Tests: add GatewayLogRedactorAdapterTests; serialize the four host-building test classes into one non-parallel collection (HostBuildingCollection) so the process-wide Serilog bootstrap logger is not frozen by two concurrent host builds racing in parallel collections. The net48/x86 worker is untouched.
192 lines
7.3 KiB
C#
192 lines
7.3 KiB
C#
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
|
|
using Serilog;
|
|
using ZB.MOM.WW.MxGateway.Contracts;
|
|
using ZB.MOM.WW.MxGateway.Server.Alarms;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
using ZB.MOM.WW.MxGateway.Server.Diagnostics;
|
|
using ZB.MOM.WW.MxGateway.Server.Galaxy;
|
|
using ZB.MOM.WW.MxGateway.Server.Grpc;
|
|
using ZB.MOM.WW.MxGateway.Server.Metrics;
|
|
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
|
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;
|
|
using ZB.MOM.WW.MxGateway.Server.Sessions;
|
|
using ZB.MOM.WW.MxGateway.Server.Workers;
|
|
using ZB.MOM.WW.Telemetry.Serilog;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server;
|
|
|
|
/// <summary>
|
|
/// Configures and builds the gateway web application.
|
|
/// </summary>
|
|
public static class GatewayApplication
|
|
{
|
|
private const string StaticAssetsManifestFileName = "ZB.MOM.WW.MxGateway.Server.staticwebassets.endpoints.json";
|
|
|
|
/// <summary>
|
|
/// Builds a configured web application with all gateway services and middleware.
|
|
/// </summary>
|
|
/// <param name="args">Command-line arguments passed to the application.</param>
|
|
/// <returns>A configured web application ready to run.</returns>
|
|
public static WebApplication Build(string[] args)
|
|
{
|
|
WebApplicationBuilder builder = CreateBuilder(args);
|
|
WebApplication app = builder.Build();
|
|
|
|
// Push the per-request correlation properties (via Serilog LogContext) before the
|
|
// request-logging middleware emits its completion event, so those properties appear on it.
|
|
app.UseGatewayRequestLoggingScope();
|
|
app.UseSerilogRequestLogging();
|
|
app.UseStaticFiles();
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
app.UseAntiforgery();
|
|
app.MapGatewayEndpoints();
|
|
|
|
return app;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a web application builder configured with gateway services.
|
|
/// </summary>
|
|
/// <param name="args">Command-line arguments passed to the application.</param>
|
|
/// <returns>A configured web application builder.</returns>
|
|
public static WebApplicationBuilder CreateBuilder(string[] args)
|
|
{
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
|
{
|
|
Args = args,
|
|
ContentRootPath = ResolveContentRootPath(),
|
|
});
|
|
StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration);
|
|
|
|
ConfigureSerilog(builder);
|
|
|
|
builder.Services.AddGatewayConfiguration();
|
|
builder.Services.AddSqliteAuthStore();
|
|
builder.Services.AddGatewayGrpcAuthorization();
|
|
builder.Services.AddHealthChecks();
|
|
builder.Services.AddSingleton<GatewayMetrics>();
|
|
builder.Services.AddSingleton<MxAccessGrpcMapper>();
|
|
builder.Services.AddSingleton<MxAccessGrpcRequestValidator>();
|
|
builder.Services.AddSingleton<IEventStreamService, EventStreamService>();
|
|
builder.Services.AddWorkerProcessLauncher();
|
|
builder.Services.AddGatewaySessions();
|
|
builder.Services.AddGatewayAlarms();
|
|
builder.Services.AddGatewayDashboard();
|
|
builder.Services.AddGalaxyRepository();
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces the default Microsoft.Extensions.Logging provider with the shared
|
|
/// <c>ZB.MOM.WW.Telemetry.Serilog</c> bootstrap (<see cref="ZbSerilogExtensions.AddZbSerilog"/>).
|
|
/// Sinks and minimum level come from the <c>Serilog</c> configuration section; identity
|
|
/// (<c>SiteId</c>/<c>NodeRole</c>) is read from <c>MxGateway:Telemetry</c> when present.
|
|
/// Also registers the project's <see cref="ILogRedactor"/> adapter so the shared redaction
|
|
/// enricher masks gateway secrets on every event.
|
|
/// </summary>
|
|
/// <param name="builder">The web application builder being configured.</param>
|
|
private static void ConfigureSerilog(WebApplicationBuilder builder)
|
|
{
|
|
string? siteId = builder.Configuration["MxGateway:Telemetry:SiteId"];
|
|
string? nodeRole = builder.Configuration["MxGateway:Telemetry:NodeRole"];
|
|
|
|
builder.Services.AddSingleton<ILogRedactor, GatewayLogRedactorAdapter>();
|
|
|
|
builder.AddZbSerilog(options =>
|
|
{
|
|
options.ServiceName = "mxgateway";
|
|
options.SiteId = string.IsNullOrWhiteSpace(siteId) ? null : siteId;
|
|
options.NodeRole = string.IsNullOrWhiteSpace(nodeRole) ? null : nodeRole;
|
|
});
|
|
}
|
|
|
|
private static string ResolveContentRootPath()
|
|
{
|
|
string? configuredContentRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_CONTENTROOT");
|
|
if (!string.IsNullOrWhiteSpace(configuredContentRootPath)
|
|
&& IsServerContentRoot(configuredContentRootPath))
|
|
{
|
|
return configuredContentRootPath;
|
|
}
|
|
|
|
string currentDirectory = Environment.CurrentDirectory;
|
|
if (IsServerContentRoot(currentDirectory))
|
|
{
|
|
return currentDirectory;
|
|
}
|
|
|
|
string baseDirectory = AppContext.BaseDirectory;
|
|
if (IsServerContentRoot(baseDirectory))
|
|
{
|
|
return baseDirectory;
|
|
}
|
|
|
|
string? discoveredContentRootPath = DiscoverServerContentRoot(currentDirectory)
|
|
?? DiscoverServerContentRoot(baseDirectory);
|
|
|
|
return discoveredContentRootPath ?? baseDirectory;
|
|
}
|
|
|
|
private static string? DiscoverServerContentRoot(string startPath)
|
|
{
|
|
DirectoryInfo? directory = new(startPath);
|
|
while (directory is not null)
|
|
{
|
|
if (IsServerContentRoot(directory.FullName))
|
|
{
|
|
return directory.FullName;
|
|
}
|
|
|
|
string serverProjectPath = Path.Combine(directory.FullName, "src", "ZB.MOM.WW.MxGateway.Server");
|
|
if (IsServerContentRoot(serverProjectPath))
|
|
{
|
|
return serverProjectPath;
|
|
}
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static bool IsServerContentRoot(string path)
|
|
{
|
|
return File.Exists(Path.Combine(path, "appsettings.json"))
|
|
&& Directory.Exists(Path.Combine(path, "wwwroot"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maps gateway endpoints including gRPC services, health checks, and the dashboard.
|
|
/// </summary>
|
|
/// <param name="endpoints">Endpoint route builder to map endpoints to.</param>
|
|
/// <returns>The same endpoint route builder for chaining.</returns>
|
|
public static IEndpointRouteBuilder MapGatewayEndpoints(this IEndpointRouteBuilder endpoints)
|
|
{
|
|
endpoints.MapStaticAssets(ResolveStaticAssetsManifestPath());
|
|
|
|
endpoints.MapGet(
|
|
"/health/live",
|
|
() => Results.Ok(new GatewayHealthReply(
|
|
Status: "Healthy",
|
|
DefaultBackend: GatewayContractInfo.DefaultBackendName,
|
|
WorkerProtocolVersion: GatewayContractInfo.WorkerProtocolVersion)))
|
|
.WithName("LiveHealth");
|
|
|
|
endpoints.MapGrpcService<MxAccessGatewayService>();
|
|
endpoints.MapGrpcService<GalaxyRepositoryGrpcService>();
|
|
endpoints.MapGatewayDashboard();
|
|
|
|
return endpoints;
|
|
}
|
|
|
|
private static string ResolveStaticAssetsManifestPath()
|
|
{
|
|
string manifestPath = Path.Combine(AppContext.BaseDirectory, StaticAssetsManifestFileName);
|
|
|
|
return File.Exists(manifestPath) ? manifestPath : StaticAssetsManifestFileName;
|
|
}
|
|
}
|