From 18b81bacceacfd239359e6932742590e3f5094e3 Mon Sep 17 00:00:00 2001 From: Vance Morrison Date: Wed, 27 Mar 2019 22:40:17 -0700 Subject: [PATCH] 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. --- .../Internal/HostingApplicationDiagnostics.cs | 31 +++++++++----- .../Hosting/test/HostingApplicationTests.cs | 40 ++++++++++++++++++- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index a42e94066e..82d825ea2b 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -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 }); } } -} \ No newline at end of file +} diff --git a/src/Hosting/Hosting/test/HostingApplicationTests.cs b/src/Hosting/Hosting/test/HostingApplicationTests.cs index de1dc01899..002421a90d 100644 --- a/src/Hosting/Hosting/test/HostingApplicationTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationTests.cs @@ -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(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(object o, string name) { Assert.NotNull(o);