diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 9428a56678..b983aa397d 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -16,7 +16,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; private const string ActivityName = "Microsoft.AspNetCore.Hosting.HttpRequestIn"; - private const string ActivityStartKey = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start"; + private const string ActivityStartKey = ActivityName + ".Start"; + private const string ActivityStopKey = ActivityName + ".Stop"; private const string DeprecatedDiagnosticsBeginRequestKey = "Microsoft.AspNetCore.Hosting.BeginRequest"; private const string DeprecatedDiagnosticsEndRequestKey = "Microsoft.AspNetCore.Hosting.EndRequest"; @@ -278,7 +279,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal if (_diagnosticListener.IsEnabled(ActivityStartKey)) { hasDiagnosticListener = true; - _diagnosticListener.StartActivity(activity, new { HttpContext = httpContext }); + StartActivity(activity, httpContext); } else { @@ -293,12 +294,32 @@ namespace Microsoft.AspNetCore.Hosting.Internal { if (hasDiagnosticListener) { - _diagnosticListener.StopActivity(activity, new { HttpContext = httpContext }); + StopActivity(activity, httpContext); } else { activity.Stop(); } } + + // These are versions of DiagnosticSource.Start/StopActivity that don't allocate strings per call (see https://github.com/dotnet/corefx/issues/37055) + private Activity StartActivity(Activity activity, HttpContext httpContext) + { + activity.Start(); + _diagnosticListener.Write(ActivityStartKey, httpContext); + return activity; + } + + private void StopActivity(Activity activity, HttpContext httpContext) + { + // Stop sets the end time if it was unset, but we want it set before we issue the write + // so we do it now. + if (activity.Duration == TimeSpan.Zero) + { + activity.SetEndTime(DateTime.UtcNow); + } + _diagnosticListener.Write(ActivityStopKey, httpContext); + activity.Stop(); // Resets Activity.Current (we want this after the Write) + } } } diff --git a/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs b/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs index 2c299f83d9..d3c88f791f 100644 --- a/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs +++ b/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs @@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.Http public override Microsoft.AspNetCore.Http.ConnectionInfo Connection { get { throw null; } } public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } public Microsoft.AspNetCore.Http.Features.FormOptions FormOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public Microsoft.AspNetCore.Http.HttpContext HttpContext { get { throw null; } } public override System.Collections.Generic.IDictionary Items { get { throw null; } set { } } public override Microsoft.AspNetCore.Http.HttpRequest Request { get { throw null; } } public override System.Threading.CancellationToken RequestAborted { get { throw null; } set { } } diff --git a/src/Http/Http/src/DefaultHttpContext.cs b/src/Http/Http/src/DefaultHttpContext.cs index 65166b4e0c..8ab60e9776 100644 --- a/src/Http/Http/src/DefaultHttpContext.cs +++ b/src/Http/Http/src/DefaultHttpContext.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Security.Claims; using System.Threading; using Microsoft.AspNetCore.Http.Features; @@ -157,6 +158,12 @@ namespace Microsoft.AspNetCore.Http } } + // This property exists because of backwards compatibility. + // We send an anonymous object with an HttpContext property + // via DiagnosticSource in various events throughout the pipeline. Instead + // we just send the HttpContext to avoid extra allocations + [EditorBrowsable(EditorBrowsableState.Never)] + public HttpContext HttpContext => this; public override void Abort() {