Attach the correlation id to the request scope if logging is on (#1138)
- Add the correlation id to the request scope as well as setting it as the parent id of the activity
This commit is contained in:
parent
3860ff4496
commit
e4350945c5
|
|
@ -46,12 +46,22 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
RecordRequestStartEventLog(httpContext);
|
RecordRequestStartEventLog(httpContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var diagnosticListenerEnabled = _diagnosticListener.IsEnabled();
|
||||||
|
var loggingEnabled = _logger.IsEnabled(LogLevel.Critical);
|
||||||
|
|
||||||
if (_diagnosticListener.IsEnabled())
|
// If logging is enabled or the diagnostic listener is enabled, try to get the correlation
|
||||||
|
// id from the header
|
||||||
|
StringValues correlationId;
|
||||||
|
if (diagnosticListenerEnabled || loggingEnabled)
|
||||||
|
{
|
||||||
|
httpContext.Request.Headers.TryGetValue(RequestIdHeaderName, out correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagnosticListenerEnabled)
|
||||||
{
|
{
|
||||||
if (_diagnosticListener.IsEnabled(ActivityName, httpContext))
|
if (_diagnosticListener.IsEnabled(ActivityName, httpContext))
|
||||||
{
|
{
|
||||||
context.Activity = StartActivity(httpContext);
|
context.Activity = StartActivity(httpContext, correlationId);
|
||||||
}
|
}
|
||||||
if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsBeginRequestKey))
|
if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsBeginRequestKey))
|
||||||
{
|
{
|
||||||
|
|
@ -61,12 +71,12 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// To avoid allocation, return a null scope if the logger is not on at least to some degree.
|
// To avoid allocation, return a null scope if the logger is not on at least to some degree.
|
||||||
if (_logger.IsEnabled(LogLevel.Critical))
|
if (loggingEnabled)
|
||||||
{
|
{
|
||||||
// Scope may be relevant for a different level of logging, so we always create it
|
// Scope may be relevant for a different level of logging, so we always create it
|
||||||
// see: https://github.com/aspnet/Hosting/pull/944
|
// see: https://github.com/aspnet/Hosting/pull/944
|
||||||
// Scope can be null if logging is not on.
|
// Scope can be null if logging is not on.
|
||||||
context.Scope = _logger.RequestScope(httpContext);
|
context.Scope = _logger.RequestScope(httpContext, correlationId);
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Information))
|
if (_logger.IsEnabled(LogLevel.Information))
|
||||||
{
|
{
|
||||||
|
|
@ -230,15 +240,15 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private Activity StartActivity(HttpContext httpContext)
|
private Activity StartActivity(HttpContext httpContext, StringValues requestId)
|
||||||
{
|
{
|
||||||
var activity = new Activity(ActivityName);
|
var activity = new Activity(ActivityName);
|
||||||
if (httpContext.Request.Headers.TryGetValue(RequestIdHeaderName, out var requestId))
|
if (!StringValues.IsNullOrEmpty(requestId))
|
||||||
{
|
{
|
||||||
activity.SetParentId(requestId);
|
activity.SetParentId(requestId);
|
||||||
|
|
||||||
// We expect baggage to be empty by default
|
// We expect baggage to be empty by default
|
||||||
// Only very advanced users will be using it in near future, we encouradge them to keep baggage small (few items)
|
// Only very advanced users will be using it in near future, we encourage them to keep baggage small (few items)
|
||||||
string[] baggage = httpContext.Request.Headers.GetCommaSeparatedValues(CorrelationContextHeaderName);
|
string[] baggage = httpContext.Request.Headers.GetCommaSeparatedValues(CorrelationContextHeaderName);
|
||||||
if (baggage != StringValues.Empty)
|
if (baggage != StringValues.Empty)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,9 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
{
|
{
|
||||||
internal static class HostingLoggerExtensions
|
internal static class HostingLoggerExtensions
|
||||||
{
|
{
|
||||||
public static IDisposable RequestScope(this ILogger logger, HttpContext httpContext)
|
public static IDisposable RequestScope(this ILogger logger, HttpContext httpContext, string correlationId)
|
||||||
{
|
{
|
||||||
|
return logger.BeginScope(new HostingLogScope(httpContext, correlationId));
|
||||||
return logger.BeginScope(new HostingLogScope(httpContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ApplicationError(this ILogger logger, Exception exception)
|
public static void ApplicationError(this ILogger logger, Exception exception)
|
||||||
|
|
@ -96,6 +95,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
private class HostingLogScope : IReadOnlyList<KeyValuePair<string, object>>
|
private class HostingLogScope : IReadOnlyList<KeyValuePair<string, object>>
|
||||||
{
|
{
|
||||||
private readonly HttpContext _httpContext;
|
private readonly HttpContext _httpContext;
|
||||||
|
private readonly string _correlationId;
|
||||||
|
|
||||||
private string _cachedToString;
|
private string _cachedToString;
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return 2;
|
return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,13 +119,19 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
{
|
{
|
||||||
return new KeyValuePair<string, object>("RequestPath", _httpContext.Request.Path.ToString());
|
return new KeyValuePair<string, object>("RequestPath", _httpContext.Request.Path.ToString());
|
||||||
}
|
}
|
||||||
throw new IndexOutOfRangeException(nameof(index));
|
else if (index == 2)
|
||||||
|
{
|
||||||
|
return new KeyValuePair<string, object>("CorrelationId", _correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HostingLogScope(HttpContext httpContext)
|
public HostingLogScope(HttpContext httpContext, string correlationId)
|
||||||
{
|
{
|
||||||
_httpContext = httpContext;
|
_httpContext = httpContext;
|
||||||
|
_correlationId = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting.Internal;
|
using Microsoft.AspNetCore.Hosting.Internal;
|
||||||
|
|
@ -30,6 +31,22 @@ namespace Microsoft.AspNetCore.Hosting.Tests
|
||||||
hostingApplication.DisposeContext(context, null);
|
hostingApplication.DisposeContext(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateContextSetsCorrelationIdInScope()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var logger = new LoggerWithScopes();
|
||||||
|
var hostingApplication = CreateApplication(out var features, logger: logger);
|
||||||
|
features.Get<IHttpRequestFeature>().Headers["Request-Id"] = "some correlation id";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = hostingApplication.CreateContext(features);
|
||||||
|
|
||||||
|
Assert.Equal(1, logger.Scopes.Count);
|
||||||
|
var pairs = ((IReadOnlyList<KeyValuePair<string, object>>)logger.Scopes[0]).ToDictionary(p => p.Key, p => p.Value);
|
||||||
|
Assert.Equal("some correlation id", pairs["CorrelationId"].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ActivityIsNotCreatedWhenIsEnabledForActivityIsFalse()
|
public void ActivityIsNotCreatedWhenIsEnabledForActivityIsFalse()
|
||||||
{
|
{
|
||||||
|
|
@ -60,7 +77,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests
|
||||||
hostingApplication.CreateContext(features);
|
hostingApplication.CreateContext(features);
|
||||||
Assert.Null(Activity.Current);
|
Assert.Null(Activity.Current);
|
||||||
Assert.True(isEnabledActivityFired);
|
Assert.True(isEnabledActivityFired);
|
||||||
Assert.False (isEnabledStartFired);
|
Assert.False(isEnabledStartFired);
|
||||||
Assert.False(eventsFired);
|
Assert.False(eventsFired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +278,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HostingApplication CreateApplication(out FeatureCollection features,
|
private static HostingApplication CreateApplication(out FeatureCollection features,
|
||||||
DiagnosticListener diagnosticSource = null)
|
DiagnosticListener diagnosticSource = null, ILogger logger = null)
|
||||||
{
|
{
|
||||||
var httpContextFactory = new Mock<IHttpContextFactory>();
|
var httpContextFactory = new Mock<IHttpContextFactory>();
|
||||||
|
|
||||||
|
|
@ -272,7 +289,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests
|
||||||
|
|
||||||
var hostingApplication = new HostingApplication(
|
var hostingApplication = new HostingApplication(
|
||||||
ctx => Task.FromResult(0),
|
ctx => Task.FromResult(0),
|
||||||
new NullScopeLogger(),
|
logger ?? new NullScopeLogger(),
|
||||||
diagnosticSource ?? new NoopDiagnosticSource(),
|
diagnosticSource ?? new NoopDiagnosticSource(),
|
||||||
httpContextFactory.Object);
|
httpContextFactory.Object);
|
||||||
|
|
||||||
|
|
@ -290,6 +307,31 @@ namespace Microsoft.AspNetCore.Hosting.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LoggerWithScopes : ILogger
|
||||||
|
{
|
||||||
|
public IDisposable BeginScope<TState>(TState state)
|
||||||
|
{
|
||||||
|
Scopes.Add(state);
|
||||||
|
return new Scope();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<object> Scopes { get; set; } = new List<object>();
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => true;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Scope : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class NoopDiagnosticSource : DiagnosticListener
|
private class NoopDiagnosticSource : DiagnosticListener
|
||||||
{
|
{
|
||||||
public NoopDiagnosticSource() : base("DummyListener")
|
public NoopDiagnosticSource() : base("DummyListener")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue