Add support for connection scopes if logging is enabled (#1953)

* Add support for connection scopes if logging is enabled
- Don't create a scope if logging isn't on
- Copied the pattern we use in Hosting
This commit is contained in:
David Fowler 2017-07-12 11:45:08 -07:00 committed by GitHub
parent fd1758fdfc
commit 7ebbdad974
5 changed files with 168 additions and 56 deletions

View File

@ -0,0 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public class ConnectionLogScope : IReadOnlyList<KeyValuePair<string, object>>
{
private readonly string _connectionId;
private string _cachedToString;
public ConnectionLogScope(string connectionId)
{
_connectionId = connectionId;
}
public KeyValuePair<string, object> this[int index]
{
get
{
if (index == 0)
{
return new KeyValuePair<string, object>("ConnectionId", _connectionId);
}
throw new ArgumentOutOfRangeException(nameof(index));
}
}
public int Count => 1;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
for (int i = 0; i < Count; ++i)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override string ToString()
{
if (_cachedToString == null)
{
_cachedToString = string.Format(
CultureInfo.InvariantCulture,
"ConnectionId:{0}",
_connectionId);
}
return _cachedToString;
}
}
}

View File

@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -79,10 +81,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void StartRequestProcessing<TContext>(IHttpApplication<TContext> application) public void StartRequestProcessing<TContext>(IHttpApplication<TContext> application)
{ {
_lifetimeTask = ProcessRequestsAsync<TContext>(application); _lifetimeTask = ProcessRequestsAsync(application);
} }
private async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application) private async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
{
using (BeginConnectionScope())
{ {
try try
{ {
@ -147,6 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
KestrelEventSource.Log.ConnectionStop(this); KestrelEventSource.Log.ConnectionStop(this);
} }
} }
}
internal void CreateFrame<TContext>(IHttpApplication<TContext> application, IPipeReader input, IPipe output) internal void CreateFrame<TContext>(IHttpApplication<TContext> application, IPipeReader input, IPipe output)
{ {
@ -454,5 +459,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_writeTimingWrites--; _writeTimingWrites--;
} }
} }
private IDisposable BeginConnectionScope()
{
if (Log.IsEnabled(LogLevel.Critical))
{
return Log.BeginScope(new ConnectionLogScope(ConnectionId));
}
return null;
}
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
@ -529,5 +530,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.True(_frameConnection.TimedOut); Assert.True(_frameConnection.TimedOut);
Assert.True(aborted.Wait(TimeSpan.FromSeconds(10))); Assert.True(aborted.Wait(TimeSpan.FromSeconds(10)));
} }
[Fact]
public async Task StartRequestProcessingCreatesLogScopeWithConnectionId()
{
_frameConnection.StartRequestProcessing(new DummyApplication());
_frameConnection.OnConnectionClosed(ex: null);
await _frameConnection.StopAsync().TimeoutAfter(TimeSpan.FromSeconds(5));
var scopeObjects = ((TestKestrelTrace)_frameConnectionContext.ServiceContext.Log)
.Logger
.Scopes
.OfType<IReadOnlyList<KeyValuePair<string, object>>>()
.ToList();
Assert.Equal(1, scopeObjects.Count);
var pairs = scopeObjects[0].ToDictionary(p => p.Key, p => p.Value);
Assert.True(pairs.ContainsKey("ConnectionId"));
Assert.Equal(_frameConnection.ConnectionId, pairs["ConnectionId"]);
}
} }
} }

View File

@ -19,6 +19,8 @@ namespace Microsoft.AspNetCore.Testing
public ConcurrentBag<LogMessage> Messages { get; } = new ConcurrentBag<LogMessage>(); public ConcurrentBag<LogMessage> Messages { get; } = new ConcurrentBag<LogMessage>();
public ConcurrentBag<object> Scopes { get; } = new ConcurrentBag<object>();
public int TotalErrorsLogged => Messages.Count(message => message.LogLevel == LogLevel.Error); public int TotalErrorsLogged => Messages.Count(message => message.LogLevel == LogLevel.Error);
public int CriticalErrorsLogged => Messages.Count(message => message.LogLevel == LogLevel.Critical); public int CriticalErrorsLogged => Messages.Count(message => message.LogLevel == LogLevel.Critical);
@ -27,6 +29,7 @@ namespace Microsoft.AspNetCore.Testing
public IDisposable BeginScope<TState>(TState state) public IDisposable BeginScope<TState>(TState state)
{ {
Scopes.Add(state);
return new Disposable(() => { }); return new Disposable(() => { });
} }

View File

@ -13,8 +13,12 @@ namespace Microsoft.AspNetCore.Testing
public class TestServiceContext : ServiceContext public class TestServiceContext : ServiceContext
{ {
public TestServiceContext() public TestServiceContext()
: this(new LoggerFactory(new[] { new KestrelTestLoggerProvider() }))
{ {
var logger = new TestApplicationErrorLogger();
var kestrelTrace = new TestKestrelTrace(logger);
var loggerFactory = new LoggerFactory(new[] { new KestrelTestLoggerProvider(logger) });
Initialize(loggerFactory, kestrelTrace);
} }
public TestServiceContext(ILoggerFactory loggerFactory) public TestServiceContext(ILoggerFactory loggerFactory)
@ -23,6 +27,11 @@ namespace Microsoft.AspNetCore.Testing
} }
public TestServiceContext(ILoggerFactory loggerFactory, IKestrelTrace kestrelTrace) public TestServiceContext(ILoggerFactory loggerFactory, IKestrelTrace kestrelTrace)
{
Initialize(loggerFactory, kestrelTrace);
}
private void Initialize(ILoggerFactory loggerFactory, IKestrelTrace kestrelTrace)
{ {
LoggerFactory = loggerFactory; LoggerFactory = loggerFactory;
Log = kestrelTrace; Log = kestrelTrace;
@ -37,7 +46,7 @@ namespace Microsoft.AspNetCore.Testing
}; };
} }
public ILoggerFactory LoggerFactory { get; } public ILoggerFactory LoggerFactory { get; set; }
public string DateHeaderValue => DateHeaderValueManager.GetDateHeaderValues().String; public string DateHeaderValue => DateHeaderValueManager.GetDateHeaderValues().String;
} }