Create the connection logging scope in ConnectionHandler (#2013)

- Instead of doing it on the FrameConnection only. This will
make sure all middleware logs get the connection id as part of their scope.
This commit is contained in:
David Fowler 2017-08-22 14:32:58 -07:00 committed by GitHub
parent 11a9b6498d
commit dc9ed60465
4 changed files with 167 additions and 117 deletions

View File

@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_connectionDelegate = connectionDelegate;
}
private IKestrelTrace Log => _serviceContext.Log;
public void OnConnection(IFeatureCollection features)
{
var connectionContext = new DefaultConnectionContext(features);
@ -51,14 +53,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private async Task Execute(ConnectionContext connectionContext)
{
using (BeginConnectionScope(connectionContext))
{
Log.ConnectionStart(connectionContext.ConnectionId);
try
{
await _connectionDelegate(connectionContext);
}
catch (Exception ex)
{
_serviceContext.Log.LogCritical(0, ex, $"{nameof(ConnectionHandler)}.{nameof(Execute)}() {connectionContext.ConnectionId}");
Log.LogCritical(0, ex, $"{nameof(ConnectionHandler)}.{nameof(Execute)}() {connectionContext.ConnectionId}");
}
Log.ConnectionStop(connectionContext.ConnectionId);
}
}
private IDisposable BeginConnectionScope(ConnectionContext connectionContext)
{
if (Log.IsEnabled(LogLevel.Critical))
{
return Log.BeginScope(new ConnectionLogScope(connectionContext.ConnectionId));
}
return null;
}
// Internal for testing

View File

@ -91,12 +91,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}
private async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication)
{
using (BeginConnectionScope())
{
try
{
Log.ConnectionStart(ConnectionId);
KestrelEventSource.Log.ConnectionStart(this);
AdaptedPipeline adaptedPipeline = null;
@ -175,11 +172,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_context.ServiceContext.ConnectionManager.NormalConnectionCount.ReleaseOne();
}
Log.ConnectionStop(ConnectionId);
KestrelEventSource.Log.ConnectionStop(this);
}
}
}
internal void CreateFrame<TContext>(IHttpApplication<TContext> httpApplication, IPipeConnection transport, IPipeConnection application)
{
@ -497,15 +492,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_writeTimingWrites--;
}
}
private IDisposable BeginConnectionScope()
{
if (Log.IsEnabled(LogLevel.Critical))
{
return Log.BeginScope(new ConnectionLogScope(ConnectionId));
}
return null;
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Protocols.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class ConnectionHandlerTests
{
[Fact]
public void OnConnectionCreatesLogScopeWithConnectionId()
{
var serviceContext = new TestServiceContext();
var tcs = new TaskCompletionSource<object>();
var handler = new ConnectionHandler(serviceContext, _ => tcs.Task);
var connection = new TestConnection();
handler.OnConnection(connection);
// The scope should be created
var scopeObjects = ((TestKestrelTrace)serviceContext.Log)
.Logger
.Scopes
.OfType<IReadOnlyList<KeyValuePair<string, object>>>()
.ToList();
Assert.Single(scopeObjects);
var pairs = scopeObjects[0].ToDictionary(p => p.Key, p => p.Value);
Assert.True(pairs.ContainsKey("ConnectionId"));
Assert.Equal(connection.ConnectionId, pairs["ConnectionId"]);
tcs.TrySetResult(null);
// Verify the scope was disposed after request processing completed
Assert.True(((TestKestrelTrace)serviceContext.Log).Logger.Scopes.IsEmpty);
}
private class TestConnection : FeatureCollection, IConnectionIdFeature, IConnectionTransportFeature
{
public TestConnection()
{
Set<IConnectionIdFeature>(this);
Set<IConnectionTransportFeature>(this);
}
public PipeFactory PipeFactory { get; } = new PipeFactory();
public IPipeConnection Transport { get; set; }
public IPipeConnection Application { get; set; }
public IScheduler InputWriterScheduler => TaskRunScheduler.Default;
public IScheduler OutputReaderScheduler => TaskRunScheduler.Default;
public Task ConnectionAborted => Task.CompletedTask;
public Task ConnectionClosed => Task.CompletedTask;
public string ConnectionId { get; set; }
}
}
}

View File

@ -528,29 +528,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.True(_frameConnection.TimedOut);
Assert.True(aborted.Wait(TimeSpan.FromSeconds(10)));
}
[Fact]
public async Task StartRequestProcessingCreatesLogScopeWithConnectionId()
{
_ = _frameConnection.StartRequestProcessing(new DummyApplication());
var scopeObjects = ((TestKestrelTrace)_frameConnectionContext.ServiceContext.Log)
.Logger
.Scopes
.OfType<IReadOnlyList<KeyValuePair<string, object>>>()
.ToList();
_frameConnection.OnConnectionClosed(ex: null);
await _frameConnection.StopAsync().TimeoutAfter(TimeSpan.FromSeconds(5));
Assert.Single(scopeObjects);
var pairs = scopeObjects[0].ToDictionary(p => p.Key, p => p.Value);
Assert.True(pairs.ContainsKey("ConnectionId"));
Assert.Equal(_frameConnection.ConnectionId, pairs["ConnectionId"]);
// Verify the scope was disposed after request processing completed
Assert.True(((TestKestrelTrace)_frameConnectionContext.ServiceContext.Log).Logger.Scopes.IsEmpty);
}
}
}