From fca5250e7df03ccc47a1132a2de32029053823fe Mon Sep 17 00:00:00 2001 From: Haik Date: Tue, 10 Mar 2026 13:41:43 +0400 Subject: [PATCH] Log HTTP out and SignalR hub invocation failures that were previously silently lost Co-Authored-By: Claude Opus 4.6 --- .../Middleware/OutboundLoggingHandler.cs | 47 +++++++++++++++++-- .../Middleware/SignalRLoggingHubFilter.cs | 38 +++++++++++++-- src/SharedKernel/SharedKernel.csproj | 6 +-- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/SharedKernel/Logging/Middleware/OutboundLoggingHandler.cs b/src/SharedKernel/Logging/Middleware/OutboundLoggingHandler.cs index 7094e0c..1ab38ac 100644 --- a/src/SharedKernel/Logging/Middleware/OutboundLoggingHandler.cs +++ b/src/SharedKernel/Logging/Middleware/OutboundLoggingHandler.cs @@ -12,10 +12,36 @@ protected override async Task SendAsync(HttpRequestMessage var (reqHeaders, reqBody) = await CaptureRequestAsync(request, cancellationToken); - var response = await base.SendAsync(request, cancellationToken); + HttpResponseMessage response; + try + { + response = await base.SendAsync(request, cancellationToken); + } + catch (Exception ex) + { + var elapsedMs = Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds; + + var errorBody = new Dictionary + { + ["exceptionType"] = ex.GetType().Name, + ["message"] = ex.Message + }; + + LogHttpOutFailure( + ex, + request.Method.Method, + request.RequestUri?.GetLeftPart(UriPartial.Path) ?? "", + elapsedMs, + "HttpOut", + string.IsNullOrEmpty(request.RequestUri?.Query) ? null : request.RequestUri!.Query, + LogFormatting.ToJsonString(reqHeaders), + LogFormatting.ToJsonString(reqBody), + LogFormatting.ToJsonString(errorBody)); + + throw; + } - var elapsedMs = Stopwatch.GetElapsedTime(timestamp) - .TotalMilliseconds; + var elapsed = Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds; var (resHeaders, resBody) = await CaptureResponseAsync(response, cancellationToken); @@ -23,7 +49,7 @@ protected override async Task SendAsync(HttpRequestMessage request.Method.Method, request.RequestUri?.GetLeftPart(UriPartial.Path) ?? "", (int)response.StatusCode, - elapsedMs, + elapsed, "HttpOut", string.IsNullOrEmpty(request.RequestUri?.Query) ? null : request.RequestUri!.Query, LogFormatting.ToJsonString(reqHeaders), @@ -150,4 +176,17 @@ private partial void LogHttpOut(string method, string requestBody, string responseHeaders, string responseBody); + + [LoggerMessage(Level = LogLevel.Warning, + Message = "[HTTP OUT] {Method} {HostPath} -> FAILED in {ElapsedMs}ms | " + + "{Kind} query={Query} requestHeader={RequestHeaders} requestBody={RequestBody} error={ErrorBody}")] + private partial void LogHttpOutFailure(Exception ex, + string method, + string hostPath, + double elapsedMs, + string kind, + string? query, + string requestHeaders, + string requestBody, + string errorBody); } \ No newline at end of file diff --git a/src/SharedKernel/Logging/Middleware/SignalRLoggingHubFilter.cs b/src/SharedKernel/Logging/Middleware/SignalRLoggingHubFilter.cs index ae443b3..e8dbe5f 100644 --- a/src/SharedKernel/Logging/Middleware/SignalRLoggingHubFilter.cs +++ b/src/SharedKernel/Logging/Middleware/SignalRLoggingHubFilter.cs @@ -21,11 +21,30 @@ internal sealed partial class SignalRLoggingHubFilter(ILogger + { + ["exceptionType"] = ex.GetType().Name, + ["message"] = ex.Message + }; - LogInvoke(hubName, methodName, elapsedMs, "SignalR", connectionId, userId, LogFormatting.ToJsonString(redactedArgs)); + LogInvokeFailure(ex, hubName, methodName, elapsedMs, "SignalR", connectionId, userId, + LogFormatting.ToJsonString(redactedArgs), LogFormatting.ToJsonString(errorBody)); + + throw; + } + + var elapsed = Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds; + + LogInvoke(hubName, methodName, elapsed, "SignalR", connectionId, userId, LogFormatting.ToJsonString(redactedArgs)); return result; } @@ -64,6 +83,19 @@ private partial void LogInvoke( string? userId, string args); + [LoggerMessage(Level = LogLevel.Warning, + Message = "[SignalR] {Hub}.{HubMethod} FAILED in {ElapsedMs}ms | {Kind} connId={ConnId} userId={UserId} args={Args} error={ErrorBody}")] + private partial void LogInvokeFailure( + Exception ex, + string hub, + string hubMethod, + double elapsedMs, + string kind, + string? connId, + string? userId, + string args, + string errorBody); + [LoggerMessage(Level = LogLevel.Information, Message = "[Connected] SignalR {Hub} | {Kind} connId={ConnId} userId={UserId}")] private partial void LogConnected( diff --git a/src/SharedKernel/SharedKernel.csproj b/src/SharedKernel/SharedKernel.csproj index f62f7ae..cfed4f3 100644 --- a/src/SharedKernel/SharedKernel.csproj +++ b/src/SharedKernel/SharedKernel.csproj @@ -21,8 +21,8 @@ pandatech.png README.md - 2.2.3 - Cors now will start to expose Content-Disposition header + 2.2.4 + Log HTTP out and SignalR hub invocation failures (timeout, connection errors, exceptions) that were previously silently lost true true @@ -71,7 +71,7 @@ - +