Support for receiving ID via TraceParent HTTP header. (#8495)

- Also moved the fetching of ANY HTTP header fields until AFTER the most stringent
- Check to see if logging is on (thus deferring work if the logger is sampling).
- Added transfer of TraceState from http header to Activity
- Added tests to insure that we were reading traceparent and tracestate headers as expected.
This commit is contained in:
Vance Morrison 2019-03-27 22:40:17 -07:00 committed by David Fowler
parent 017f409fe4
commit 18b81bacce
2 changed files with 60 additions and 11 deletions

View File

@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
private const string RequestIdHeaderName = "Request-Id";
private const string CorrelationContextHeaderName = "Correlation-Context";
private const string TraceParentHeaderName = "traceparent";
private const string TraceStateHeaderName = "tracestate";
private readonly DiagnosticListener _diagnosticListener;
private readonly ILogger _logger;
@ -49,19 +51,12 @@ namespace Microsoft.AspNetCore.Hosting.Internal
var diagnosticListenerEnabled = _diagnosticListener.IsEnabled();
var loggingEnabled = _logger.IsEnabled(LogLevel.Critical);
// If logging is enabled or the diagnostic listener is enabled, try to get the correlation
// id from the header
StringValues correlationId = default;
if (diagnosticListenerEnabled || loggingEnabled)
{
httpContext.Request.Headers.TryGetValue(RequestIdHeaderName, out correlationId);
}
if (diagnosticListenerEnabled)
{
if (_diagnosticListener.IsEnabled(ActivityName, httpContext))
{
context.Activity = StartActivity(httpContext, correlationId);
context.Activity = StartActivity(httpContext);
}
if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsBeginRequestKey))
{
@ -73,6 +68,12 @@ namespace Microsoft.AspNetCore.Hosting.Internal
// To avoid allocation, return a null scope if the logger is not on at least to some degree.
if (loggingEnabled)
{
// Get the request ID (first try TraceParent header otherwise Request-ID header
if (!httpContext.Request.Headers.TryGetValue(TraceParentHeaderName, out var correlationId))
{
httpContext.Request.Headers.TryGetValue(RequestIdHeaderName, out correlationId);
}
// Scope may be relevant for a different level of logging, so we always create it
// see: https://github.com/aspnet/Hosting/pull/944
// Scope can be null if logging is not on.
@ -240,12 +241,22 @@ namespace Microsoft.AspNetCore.Hosting.Internal
}
[MethodImpl(MethodImplOptions.NoInlining)]
private Activity StartActivity(HttpContext httpContext, StringValues requestId)
private Activity StartActivity(HttpContext httpContext)
{
var activity = new Activity(ActivityName);
if (!httpContext.Request.Headers.TryGetValue(TraceParentHeaderName, out var requestId))
{
httpContext.Request.Headers.TryGetValue(RequestIdHeaderName, out requestId);
}
if (!StringValues.IsNullOrEmpty(requestId))
{
activity.SetParentId(requestId);
if (httpContext.Request.Headers.TryGetValue(TraceStateHeaderName, out var traceState))
{
activity.TraceStateString = traceState;
}
// We expect baggage to be empty by default
// Only very advanced users will be using it in near future, we encourage them to keep baggage small (few items)
@ -280,4 +291,4 @@ namespace Microsoft.AspNetCore.Hosting.Internal
_diagnosticListener.StopActivity(activity, new { HttpContext = httpContext });
}
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -267,6 +267,44 @@ namespace Microsoft.AspNetCore.Hosting.Tests
Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key2" && pair.Value == "value2");
}
[Fact]
public void ActivityTraceParentAndTraceStateFromHeaders()
{
var diagnosticSource = new DiagnosticListener("DummySource");
var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair => { }),
s =>
{
if (s.StartsWith("Microsoft.AspNetCore.Hosting.HttpRequestIn"))
{
return true;
}
return false;
});
features.Set<IHttpRequestFeature>(new HttpRequestFeature()
{
Headers = new HeaderDictionary()
{
{"traceparent", "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01"},
{"tracestate", "TraceState1"},
{"Correlation-Context", "Key1=value1, Key2=value2"}
}
});
hostingApplication.CreateContext(features);
Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName);
Assert.Equal(ActivityIdFormat.W3C, Activity.Current.IdFormat);
Assert.Equal("0123456789abcdef0123456789abcdef", Activity.Current.TraceId.ToHexString());
Assert.Equal("0123456789abcdef", Activity.Current.ParentSpanId.ToHexString());
Assert.Equal("TraceState1", Activity.Current.TraceStateString);
Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key1" && pair.Value == "value1");
Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key2" && pair.Value == "value2");
}
private static void AssertProperty<T>(object o, string name)
{
Assert.NotNull(o);