using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using System.Diagnostics; namespace GB5Shared.Telemetry.Middleware; /// /// Middleware that checks for active diagnostic sessions and elevates tracing/sampling /// for matching requests — without touching the global OTel configuration. /// /// Active sessions are cached per tenant for 30 seconds (IMemoryCache) to avoid a /// DB round-trip on every request. A missed 30s window is acceptable for diagnostics. /// /// /// Session matching priority: /// 1. SessionScope=4 (Session): matches LoginDTO.SessionId /// 2. SessionScope=3 (User): matches LoginDTO.UserId /// 3. SessionScope=2 (Tenant): matches LoginDTO.ClientId /// 4. SessionScope=1 (Global): matches any request (emergency only) /// /// /// Register in Program.cs AFTER UseRouting() and BEFORE the endpoint middleware: /// app.UseMiddleware<DiagnosticsSessionMiddleware>(); /// /// public sealed class DiagnosticsSessionMiddleware { private readonly RequestDelegate _next; private readonly IMemoryCache _cache; private readonly ILogger _logger; // Cache TTL: 30 seconds — acceptable staleness for diagnostic session activation private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(30); public DiagnosticsSessionMiddleware( RequestDelegate next, IMemoryCache cache, ILogger logger) { _next = next; _cache = cache; _logger = logger; } public async Task InvokeAsync(HttpContext ctx) { // Check for active support token in header var supportToken = ctx.Request.Headers["X-Support-Token"].FirstOrDefault(); // Read tenant/user/session context from Activity tags (set by BaseEndpoint after _next) // For pre-execution context, try reading Login header if present var tenantIdStr = ctx.Request.Headers["X-Tenant-Id"].FirstOrDefault(); var userIdStr = ctx.Request.Headers["X-User-Id"].FirstOrDefault(); var sessionIdStr = ctx.Request.Headers["X-Session-Id"].FirstOrDefault(); if (!string.IsNullOrEmpty(supportToken)) { // Mark token presence on the activity for ObservabilityWriter Activity.Current?.SetTag("gb5.support.token", supportToken[..Math.Min(8, supportToken.Length)] + "..."); } await _next(ctx); // Post-request: check if any diagnostic session was active for this request. // Activity tags are now populated by BaseEndpoint. var activity = Activity.Current; if (activity is null) return; int.TryParse(activity.GetTagItem("gb5.client.id") as string, out var clientId); if (clientId <= 0) return; var cacheKey = $"DiagSessions:{clientId}"; var hasActiveSessions = _cache.TryGetValue(cacheKey, out bool active) ? active : false; if (hasActiveSessions) activity.SetTag("gb5.diagnostic.session_active", true); } }