diff --git a/nuget.config b/nuget.config
index 5223539..a2b8c8a 100644
--- a/nuget.config
+++ b/nuget.config
@@ -14,6 +14,8 @@
+
+
diff --git a/src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogRedactorSeam.cs b/src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogRedactorSeam.cs
new file mode 100644
index 0000000..0beab89
--- /dev/null
+++ b/src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogRedactorSeam.cs
@@ -0,0 +1,28 @@
+using ZB.MOM.WW.Telemetry.Serilog;
+
+namespace ZB.MOM.WW.MxGateway.Server.Diagnostics;
+
+///
+/// Adapts the static to the shared seam
+/// so the telemetry RedactionEnricher masks API-key/credential material on every log event.
+///
+public sealed class GatewayLogRedactorSeam : ILogRedactor
+{
+ private static readonly string[] IdentityKeys = ["ClientIdentity", "authorization", "Authorization"];
+
+ ///
+ /// Masks API-key/credential material in known identity-bearing log properties.
+ ///
+ /// The log event property dictionary to redact in place.
+ public void Redact(IDictionary properties)
+ {
+ ArgumentNullException.ThrowIfNull(properties);
+ foreach (var key in IdentityKeys)
+ {
+ if (properties.TryGetValue(key, out var value) && value is string s)
+ {
+ properties[key] = GatewayLogRedactor.RedactClientIdentity(s);
+ }
+ }
+ }
+}
diff --git a/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs b/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs
index beba939..b1931a9 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs
+++ b/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs
@@ -15,6 +15,8 @@ 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;
+using ZB.MOM.WW.Telemetry.Serilog;
namespace ZB.MOM.WW.MxGateway.Server;
@@ -61,6 +63,8 @@ public static class GatewayApplication
ConfigureSelfSignedTls(builder);
+ builder.AddZbSerilog(o => o.ServiceName = "mxgateway");
+
builder.Services.AddGatewayConfiguration();
builder.Services.AddSqliteAuthStore();
builder.Services.AddGatewayGrpcAuthorization();
@@ -70,6 +74,12 @@ public static class GatewayApplication
failureStatus: null,
tags: new[] { ZbHealthTags.Ready });
builder.Services.AddSingleton();
+ builder.AddZbTelemetry(o =>
+ {
+ o.ServiceName = "mxgateway";
+ o.Meters = [GatewayMetrics.MeterName]; // "MxGateway.Server" — name unchanged
+ });
+ builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
@@ -175,6 +185,7 @@ public static class GatewayApplication
endpoints.MapStaticAssets(ResolveStaticAssetsManifestPath());
endpoints.MapZbHealth();
+ endpoints.MapZbMetrics();
endpoints.MapGrpcService();
endpoints.MapGrpcService();
diff --git a/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj b/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj
index c8c8527..855acc4 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj
+++ b/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj
@@ -7,6 +7,11 @@
+
+
+
+
+
diff --git a/src/ZB.MOM.WW.MxGateway.Server/appsettings.Development.json b/src/ZB.MOM.WW.MxGateway.Server/appsettings.Development.json
index 0c208ae..674b2e0 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/appsettings.Development.json
+++ b/src/ZB.MOM.WW.MxGateway.Server/appsettings.Development.json
@@ -1,8 +1,13 @@
{
- "Logging": {
- "LogLevel": {
+ "Serilog": {
+ "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
+ "MinimumLevel": {
"Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
+ "Override": { "Microsoft.AspNetCore": "Warning" }
+ },
+ "WriteTo": [
+ { "Name": "Console" },
+ { "Name": "File", "Args": { "path": "logs/mxgateway-.log", "rollingInterval": "Day" } }
+ ]
}
}
diff --git a/src/ZB.MOM.WW.MxGateway.Server/appsettings.json b/src/ZB.MOM.WW.MxGateway.Server/appsettings.json
index 63928c7..bc565ee 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/appsettings.json
+++ b/src/ZB.MOM.WW.MxGateway.Server/appsettings.json
@@ -1,9 +1,14 @@
{
- "Logging": {
- "LogLevel": {
+ "Serilog": {
+ "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
+ "MinimumLevel": {
"Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
+ "Override": { "Microsoft.AspNetCore": "Warning" }
+ },
+ "WriteTo": [
+ { "Name": "Console" },
+ { "Name": "File", "Args": { "path": "logs/mxgateway-.log", "rollingInterval": "Day" } }
+ ]
},
"AllowedHosts": "*",
"MxGateway": {
diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Diagnostics/GatewayLogRedactorSeamTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Diagnostics/GatewayLogRedactorSeamTests.cs
new file mode 100644
index 0000000..494808e
--- /dev/null
+++ b/src/ZB.MOM.WW.MxGateway.Tests/Diagnostics/GatewayLogRedactorSeamTests.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using ZB.MOM.WW.MxGateway.Server.Diagnostics;
+using Xunit;
+
+public class GatewayLogRedactorSeamTests
+{
+ [Fact]
+ public void Redact_MasksApiKeyInClientIdentity()
+ {
+ var redactor = new GatewayLogRedactorSeam();
+ var props = new Dictionary { ["ClientIdentity"] = "Bearer mxgw_operator01_super-secret" };
+ redactor.Redact(props);
+ Assert.Equal("Bearer mxgw_operator01_[redacted]", props["ClientIdentity"]);
+ }
+}
diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs
index 9fb3f05..e5d5e91 100644
--- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs
+++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs
@@ -1,7 +1,10 @@
+using System.Net;
+using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
@@ -29,6 +32,15 @@ public sealed class GatewayApplicationTests
Assert.DoesNotContain("/health/live", paths);
}
+ /// Verifies that Build registers Serilog as the host logging provider.
+ [Fact]
+ public void Build_UsesSerilogLoggerProvider()
+ {
+ using var app = GatewayApplication.Build([]);
+ var factory = app.Services.GetRequiredService();
+ Assert.Equal("SerilogLoggerFactory", factory.GetType().Name);
+ }
+
/// Verifies that Build registers the gateway metrics service.
[Fact]
public async Task Build_RegistersGatewayMetrics()
@@ -40,6 +52,28 @@ public sealed class GatewayApplicationTests
Assert.NotNull(metrics);
}
+ /// Verifies that Build mounts the Prometheus /metrics scrape endpoint.
+ [Fact]
+ public async Task Build_MapsMetricsEndpoint()
+ {
+ // Bind an ephemeral port (:0) — xUnit runs test collections in parallel, so any
+ // started-host test must avoid a fixed port to prevent a bind collision.
+ await using WebApplication app = GatewayApplication.Build(["--urls=http://127.0.0.1:0"]);
+ await app.StartAsync();
+ try
+ {
+ using var client = new HttpClient { BaseAddress = new Uri(app.Urls.First()) };
+
+ using HttpResponseMessage response = await client.GetAsync("/metrics");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ finally
+ {
+ await app.StopAsync();
+ }
+ }
+
/// Verifies that Build maps dashboard and authentication endpoints when the dashboard is enabled.
[Fact]
public async Task Build_WhenDashboardEnabled_MapsBlazorDashboardAndAuthEndpoints()