Skip to content

Commit 8f99140

Browse files
authored
Add client disconnect logs (#1521)
1 parent 6d076da commit 8f99140

File tree

6 files changed

+79
-9
lines changed

6 files changed

+79
-9
lines changed

src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IO.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
using Microsoft.AspNetCore.Connections;
9+
using Microsoft.AspNetCore.Http.Features;
910

1011
namespace Microsoft.AspNetCore.Server.IIS.Core
1112
{
@@ -121,6 +122,7 @@ private async Task ReadBody()
121122
catch (Exception ex)
122123
{
123124
error = ex;
125+
Log.UnexpectedError(_logger, nameof(IISHttpContext), ex);
124126
}
125127
finally
126128
{
@@ -174,6 +176,7 @@ private async Task WriteBody(bool flush = false)
174176
catch (Exception ex)
175177
{
176178
error = ex;
179+
Log.UnexpectedError(_logger, nameof(IISHttpContext), ex);
177180
}
178181
finally
179182
{
@@ -199,9 +202,9 @@ private void AbortIO()
199202
{
200203
cts.Cancel();
201204
}
202-
catch (Exception)
205+
catch (Exception ex)
203206
{
204-
// ignore
207+
Log.ApplicationError(_logger, ((IHttpConnectionFeature)this).ConnectionId, TraceIdentifier, ex);
205208
}
206209
});
207210
}
@@ -219,6 +222,7 @@ public void Abort(Exception reason)
219222
internal void ConnectionReset()
220223
{
221224
AbortIO();
225+
Log.ConnectionDisconnect(_logger, ((IHttpConnectionFeature)this).ConnectionId);
222226
}
223227
}
224228
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.AspNetCore.Server.IIS.Core
9+
{
10+
internal abstract partial class IISHttpContext
11+
{
12+
private static class Log
13+
{
14+
private static readonly Action<ILogger, string, Exception> _connectionDisconnect =
15+
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(1, "ConnectionDisconnect"), @"Connection ID ""{ConnectionId}"" disconnecting.");
16+
17+
private static readonly Action<ILogger, string, string, Exception> _applicationError =
18+
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(2, "ApplicationError"), @"Connection ID ""{ConnectionId}"", Request ID ""{TraceIdentifier}"": An unhandled exception was thrown by the application.");
19+
20+
private static readonly Action<ILogger, string, string, Exception> _unexpectedError =
21+
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(3, "UnexpectedError"), @"Unexpected exception in ""{ClassName}.{MethodName}"".");
22+
23+
public static void ConnectionDisconnect(ILogger logger, string connectionId)
24+
{
25+
_connectionDisconnect(logger, connectionId, null);
26+
}
27+
28+
public static void ApplicationError(ILogger logger, string connectionId, string traceIdentifier, Exception ex)
29+
{
30+
_applicationError(logger, connectionId, traceIdentifier, ex);
31+
}
32+
33+
public static void UnexpectedError(ILogger logger, string className, Exception ex, [CallerMemberName] string methodName = null)
34+
{
35+
_unexpectedError(logger, className, methodName, ex);
36+
}
37+
}
38+
}
39+
}

src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
using Microsoft.AspNetCore.Builder;
1818
using Microsoft.AspNetCore.Connections;
1919
using Microsoft.AspNetCore.Http;
20+
using Microsoft.AspNetCore.Http.Features;
2021
using Microsoft.AspNetCore.HttpSys.Internal;
2122
using Microsoft.AspNetCore.Server.IIS.Core.IO;
2223
using Microsoft.AspNetCore.WebUtilities;
24+
using Microsoft.Extensions.Logging;
2325

2426
namespace Microsoft.AspNetCore.Server.IIS.Core
2527
{
@@ -50,6 +52,8 @@ internal abstract partial class IISHttpContext : NativeRequestContext, IDisposab
5052
private readonly MemoryPool<byte> _memoryPool;
5153
private readonly IISHttpServer _server;
5254

55+
private readonly ILogger _logger;
56+
5357
private GCHandle _thisHandle;
5458
protected Task _readBodyTask;
5559
protected Task _writeBodyTask;
@@ -69,13 +73,15 @@ internal unsafe IISHttpContext(
6973
MemoryPool<byte> memoryPool,
7074
IntPtr pInProcessHandler,
7175
IISServerOptions options,
72-
IISHttpServer server)
76+
IISHttpServer server,
77+
ILogger logger)
7378
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler))
7479
{
7580
_memoryPool = memoryPool;
7681
_pInProcessHandler = pInProcessHandler;
7782
_options = options;
7883
_server = server;
84+
_logger = logger;
7985
}
8086

8187
public Version HttpVersion { get; set; }
@@ -450,6 +456,8 @@ protected void ReportApplicationError(Exception ex)
450456
{
451457
_applicationException = new AggregateException(_applicationException, ex);
452458
}
459+
460+
Log.ApplicationError(_logger, ((IHttpConnectionFeature)this).ConnectionId, TraceIdentifier, ex);
453461
}
454462

455463
public void PostCompletion(NativeMethods.REQUEST_NOTIFICATION_STATUS requestNotificationStatus)

src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77
using System.Threading.Tasks;
88
using Microsoft.AspNetCore.Builder;
99
using Microsoft.AspNetCore.Hosting.Server;
10+
using Microsoft.Extensions.Logging;
1011

1112
namespace Microsoft.AspNetCore.Server.IIS.Core
1213
{
1314
internal class IISHttpContextOfT<TContext> : IISHttpContext
1415
{
1516
private readonly IHttpApplication<TContext> _application;
1617

17-
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server)
18-
: base(memoryPool, pInProcessHandler, options, server)
18+
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server, ILogger logger)
19+
: base(memoryPool, pInProcessHandler, options, server, logger)
1920
{
2021
_application = application;
2122
}

src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpServer.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public Task StartAsync<TContext>(IHttpApplication<TContext> application, Cancell
8282
{
8383
_httpServerHandle = GCHandle.Alloc(this);
8484

85-
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this);
85+
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this, _logger);
8686
_nativeApplication.RegisterCallbacks(_requestHandler, _shutdownHandler, _onDisconnect, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
8787
return Task.CompletedTask;
8888
}
@@ -260,18 +260,20 @@ private class IISContextFactory<T> : IISContextFactory
260260
private readonly MemoryPool<byte> _memoryPool;
261261
private readonly IISServerOptions _options;
262262
private readonly IISHttpServer _server;
263+
private readonly ILogger _logger;
263264

264-
public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> application, IISServerOptions options, IISHttpServer server)
265+
public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> application, IISServerOptions options, IISHttpServer server, ILogger logger)
265266
{
266267
_application = application;
267268
_memoryPool = memoryPool;
268269
_options = options;
269270
_server = server;
271+
_logger = logger;
270272
}
271273

272274
public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler)
273275
{
274-
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server);
276+
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server, _logger);
275277
}
276278
}
277279
}

test/IIS.Tests/ClientDisconnectTests.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
1717
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
1818
public class ClientDisconnectTests : StrictTestServerTests
1919
{
20-
2120
[ConditionalFact]
2221
public async Task WritesSucceedAfterClientDisconnect()
2322
{
@@ -48,6 +47,8 @@ public async Task WritesSucceedAfterClientDisconnect()
4847

4948
await requestCompletedCompletionSource.Task.DefaultTimeout();
5049
}
50+
51+
AssertConnectionDisconnectLog();
5152
}
5253

5354
[ConditionalFact]
@@ -88,6 +89,8 @@ public async Task WritesCancelledWhenUsingAbortedToken()
8889

8990
Assert.IsType<OperationCanceledException>(exception);
9091
}
92+
93+
AssertConnectionDisconnectLog();
9194
}
9295

9396
[ConditionalFact]
@@ -125,6 +128,8 @@ public async Task ReadThrowsAfterClientDisconnect()
125128

126129
Assert.IsType<ConnectionResetException>(exception);
127130
Assert.Equal("The client has disconnected", exception.Message);
131+
132+
AssertConnectionDisconnectLog();
128133
}
129134

130135
[ConditionalFact]
@@ -202,6 +207,8 @@ public async Task ReaderThrowsCancelledException()
202207
}
203208
Assert.IsType<OperationCanceledException>(exception);
204209
}
210+
211+
AssertConnectionDisconnectLog();
205212
}
206213

207214
[ConditionalFact]
@@ -253,6 +260,7 @@ await connection.Receive(
253260

254261
Assert.IsType<ConnectionResetException>(exception);
255262
Assert.Equal("The client has disconnected", exception.Message);
263+
AssertConnectionDisconnectLog();
256264
}
257265

258266
[ConditionalFact]
@@ -275,7 +283,15 @@ public async Task RequestAbortedIsTrippedWithoutIO()
275283
}
276284
await requestAborted.Task;
277285
}
286+
287+
AssertConnectionDisconnectLog();
278288
}
289+
290+
private void AssertConnectionDisconnectLog()
291+
{
292+
Assert.Contains(TestSink.Writes, w => w.EventId.Name == "ConnectionDisconnect");
293+
}
294+
279295
private static async Task SendContentLength1Post(TestConnection connection)
280296
{
281297
await connection.Send(

0 commit comments

Comments
 (0)