From f3c407ceec2f92a0f277f33efee0693e2542fc55 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 6 Sep 2020 11:06:48 -0700 Subject: [PATCH 1/6] Trying to improve the PropertyFetcher API. --- .../Implementation/HttpInListener.cs | 19 ++++----- .../Instrumentation/PropertyFetcher.cs | 39 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index a1a9c427f9d..40f88817ac8 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -21,7 +21,6 @@ using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using OpenTelemetry.Context; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Instrumentation.GrpcNetClient; using OpenTelemetry.Trace; @@ -35,11 +34,11 @@ internal class HttpInListener : ListenerHandler private const string UnknownHostName = "UNKNOWN-HOST"; private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener"; private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers[name]; - private readonly PropertyFetcher startContextFetcher = new PropertyFetcher("HttpContext"); - private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext"); - private readonly PropertyFetcher beforeActionActionDescriptorFetcher = new PropertyFetcher("actionDescriptor"); - private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new PropertyFetcher("AttributeRouteInfo"); - private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template"); + private readonly PropertyFetcher startContextFetcher = new PropertyFetcher("HttpContext"); + private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext"); + private readonly PropertyFetcher beforeActionActionDescriptorFetcher = new PropertyFetcher("actionDescriptor"); + private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new PropertyFetcher("AttributeRouteInfo"); + private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template"); private readonly bool hostingSupportsW3C; private readonly AspNetCoreInstrumentationOptions options; private readonly ActivitySourceAdapter activitySource; @@ -55,7 +54,8 @@ public HttpInListener(string name, AspNetCoreInstrumentationOptions options, Act [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")] public override void OnStartActivity(Activity activity, object payload) { - if (!(this.startContextFetcher.Fetch(payload) is HttpContext context)) + HttpContext context = this.startContextFetcher.Fetch(payload); + if (context == null) { AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity)); return; @@ -138,7 +138,8 @@ public override void OnStopActivity(Activity activity, object payload) { if (activity.IsAllDataRequested) { - if (!(this.stopContextFetcher.Fetch(payload) is HttpContext context)) + HttpContext context = this.stopContextFetcher.Fetch(payload); + if (context == null) { AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity)); return; @@ -193,7 +194,7 @@ public override void OnCustom(string name, Activity activity, object payload) // Taking reference on MVC will increase size of deployment for non-MVC apps. var actionDescriptor = this.beforeActionActionDescriptorFetcher.Fetch(payload); var attributeRouteInfo = this.beforeActionAttributeRouteInfoFetcher.Fetch(actionDescriptor); - var template = this.beforeActionTemplateFetcher.Fetch(attributeRouteInfo) as string; + var template = this.beforeActionTemplateFetcher.Fetch(attributeRouteInfo); if (!string.IsNullOrEmpty(template)) { diff --git a/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs b/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs index b31cb3d00d5..c70bce5a661 100644 --- a/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs +++ b/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs @@ -23,13 +23,14 @@ namespace OpenTelemetry.Instrumentation /// /// PropertyFetcher fetches a property from an object. /// - public class PropertyFetcher + /// The type of the property being fetched. + public class PropertyFetcher { private readonly string propertyName; private PropertyFetch innerFetcher; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Property name to fetch. public PropertyFetcher(string propertyName) @@ -42,7 +43,7 @@ public PropertyFetcher(string propertyName) /// /// Object to be fetched. /// Property fetched. - public object Fetch(object obj) + public TProperty Fetch(object obj) { if (this.innerFetcher == null) { @@ -56,7 +57,7 @@ public object Fetch(object obj) this.innerFetcher = PropertyFetch.FetcherForProperty(property); } - return this.innerFetcher?.Fetch(obj); + return this.innerFetcher.Fetch(obj); } // see https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs @@ -68,43 +69,41 @@ private class PropertyFetch /// public static PropertyFetch FetcherForProperty(PropertyInfo propertyInfo) { - if (propertyInfo == null) + if (propertyInfo == null || !typeof(TProperty).IsAssignableFrom(propertyInfo.PropertyType)) { // returns null on any fetch. return new PropertyFetch(); } - var typedPropertyFetcher = typeof(TypedFetchProperty<,>); - var instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( - propertyInfo.DeclaringType, propertyInfo.PropertyType); + var typedPropertyFetcher = typeof(TypedPropertyFetch<,>); + var instantiatedTypedPropertyFetcher = typedPropertyFetcher.MakeGenericType( + typeof(TProperty), propertyInfo.DeclaringType, propertyInfo.PropertyType); return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, propertyInfo); } - /// - /// Given an object, fetch the property that this propertyFetch represents. - /// - public virtual object Fetch(object obj) + public virtual TProperty Fetch(object obj) { - return null; + return default; } - private class TypedFetchProperty : PropertyFetch + private class TypedPropertyFetch : PropertyFetch + where TDeclaredProperty : TProperty { - private readonly Func propertyFetch; + private readonly Func propertyFetch; - public TypedFetchProperty(PropertyInfo property) + public TypedPropertyFetch(PropertyInfo property) { - this.propertyFetch = (Func)property.GetMethod.CreateDelegate(typeof(Func)); + this.propertyFetch = (Func)property.GetMethod.CreateDelegate(typeof(Func)); } - public override object Fetch(object obj) + public override TProperty Fetch(object obj) { - if (obj is TObject o) + if (obj is TDeclaredObject o) { return this.propertyFetch(o); } - return null; + return default; } } } From b5240a214f72953ecfc6c8a13d6c83f2c392c046 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 6 Sep 2020 11:15:55 -0700 Subject: [PATCH 2/6] Cleanup. --- .../Instrumentation/PropertyFetcher.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs b/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs index c70bce5a661..8a1f5b5c001 100644 --- a/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs +++ b/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs @@ -23,8 +23,8 @@ namespace OpenTelemetry.Instrumentation /// /// PropertyFetcher fetches a property from an object. /// - /// The type of the property being fetched. - public class PropertyFetcher + /// The type of the property being fetched. + public class PropertyFetcher { private readonly string propertyName; private PropertyFetch innerFetcher; @@ -43,7 +43,7 @@ public PropertyFetcher(string propertyName) /// /// Object to be fetched. /// Property fetched. - public TProperty Fetch(object obj) + public T Fetch(object obj) { if (this.innerFetcher == null) { @@ -69,7 +69,7 @@ private class PropertyFetch /// public static PropertyFetch FetcherForProperty(PropertyInfo propertyInfo) { - if (propertyInfo == null || !typeof(TProperty).IsAssignableFrom(propertyInfo.PropertyType)) + if (propertyInfo == null || !typeof(T).IsAssignableFrom(propertyInfo.PropertyType)) { // returns null on any fetch. return new PropertyFetch(); @@ -77,17 +77,17 @@ public static PropertyFetch FetcherForProperty(PropertyInfo propertyInfo) var typedPropertyFetcher = typeof(TypedPropertyFetch<,>); var instantiatedTypedPropertyFetcher = typedPropertyFetcher.MakeGenericType( - typeof(TProperty), propertyInfo.DeclaringType, propertyInfo.PropertyType); + typeof(T), propertyInfo.DeclaringType, propertyInfo.PropertyType); return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, propertyInfo); } - public virtual TProperty Fetch(object obj) + public virtual T Fetch(object obj) { return default; } private class TypedPropertyFetch : PropertyFetch - where TDeclaredProperty : TProperty + where TDeclaredProperty : T { private readonly Func propertyFetch; @@ -96,7 +96,7 @@ public TypedPropertyFetch(PropertyInfo property) this.propertyFetch = (Func)property.GetMethod.CreateDelegate(typeof(Func)); } - public override TProperty Fetch(object obj) + public override T Fetch(object obj) { if (obj is TDeclaredObject o) { From 5a71e2ba9fb5116b16572adfa3137ef4db8f90e1 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 6 Sep 2020 11:18:46 -0700 Subject: [PATCH 3/6] Cleanup. --- src/OpenTelemetry/Instrumentation/PropertyFetcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs b/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs index 8a1f5b5c001..19cdf50b418 100644 --- a/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs +++ b/src/OpenTelemetry/Instrumentation/PropertyFetcher.cs @@ -30,7 +30,7 @@ public class PropertyFetcher private PropertyFetch innerFetcher; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Property name to fetch. public PropertyFetcher(string propertyName) From 4b60e17fc6861b51836f334e0ce1b368f41ae29d Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Mon, 7 Sep 2020 20:57:05 -0300 Subject: [PATCH 4/6] updating files, adding propertyFetcherTest --- .../Implementation/HttpInListener.cs | 4 +- .../GrpcClientDiagnosticListener.cs | 2 +- .../HttpHandlerDiagnosticListener.cs | 33 ++++++------- .../SqlClientDiagnosticListener.cs | 14 +++--- .../Instrumentation/PropertyFetcherTest.cs | 49 +++++++++++++++++++ 5 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 test/OpenTelemetry.Tests/Instrumentation/PropertyFetcherTest.cs diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs index 83a18c53f11..9b18556129b 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs @@ -31,8 +31,8 @@ internal class HttpInListener : ListenerHandler public const string ResponseCustomPropertyName = "OTel.AspNet.Response"; private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener"; private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name); - private readonly PropertyFetcher routeFetcher = new PropertyFetcher("Route"); - private readonly PropertyFetcher routeTemplateFetcher = new PropertyFetcher("RouteTemplate"); + private readonly PropertyFetcher routeFetcher = new PropertyFetcher("Route"); + private readonly PropertyFetcher routeTemplateFetcher = new PropertyFetcher("RouteTemplate"); private readonly AspNetInstrumentationOptions options; private readonly ActivitySourceAdapter activitySource; diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs index b6c90be5283..4c9d57beb90 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs @@ -26,7 +26,7 @@ internal class GrpcClientDiagnosticListener : ListenerHandler public const string RequestCustomPropertyName = "OTel.GrpcHandler.Request"; private readonly ActivitySourceAdapter activitySource; - private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request"); + private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request"); public GrpcClientDiagnosticListener(ActivitySourceAdapter activitySource) : base("Grpc.Net.Client") diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index 36c05eaafe0..5eb8338232d 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System; using System.Collections.Generic; using System.Diagnostics; @@ -22,7 +23,6 @@ using System.Runtime.Versioning; using System.Text.RegularExpressions; using System.Threading.Tasks; -using OpenTelemetry.Context; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Trace; @@ -49,10 +49,10 @@ internal class HttpHandlerDiagnosticListener : ListenerHandler private static readonly Regex CoreAppMajorVersionCheckRegex = new Regex("^\\.NETCoreApp,Version=v(\\d+)\\.", RegexOptions.Compiled | RegexOptions.IgnoreCase); private readonly ActivitySourceAdapter activitySource; - private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request"); - private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response"); - private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception"); - private readonly PropertyFetcher stopRequestStatusFetcher = new PropertyFetcher("RequestTaskStatus"); + private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request"); + private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response"); + private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception"); + private readonly PropertyFetcher stopRequestStatusFetcher = new PropertyFetcher("RequestTaskStatus"); private readonly bool httpClientSupportsW3C; private readonly HttpClientInstrumentationOptions options; @@ -120,21 +120,20 @@ public override void OnStopActivity(Activity activity, object payload) { if (activity.IsAllDataRequested) { - var requestTaskStatus = this.stopRequestStatusFetcher.Fetch(payload) as TaskStatus?; + // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs + // requestTaskStatus is not null + var requestTaskStatus = this.stopRequestStatusFetcher.Fetch(payload); - if (requestTaskStatus.HasValue) + if (requestTaskStatus != TaskStatus.RanToCompletion) { - if (requestTaskStatus != TaskStatus.RanToCompletion) + if (requestTaskStatus == TaskStatus.Canceled) { - if (requestTaskStatus == TaskStatus.Canceled) - { - activity.SetStatus(Status.Cancelled); - } - else if (requestTaskStatus != TaskStatus.Faulted) - { - // Faults are handled in OnException and should already have a span.Status of Unknown w/ Description. - activity.SetStatus(Status.Unknown); - } + activity.SetStatus(Status.Cancelled); + } + else if (requestTaskStatus != TaskStatus.Faulted) + { + // Faults are handled in OnException and should already have a span.Status of Unknown w/ Description. + activity.SetStatus(Status.Unknown); } } diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs index b95081a6ee5..10fbd1bcfa7 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs @@ -43,13 +43,13 @@ internal class SqlClientDiagnosticListener : ListenerHandler internal static readonly ActivitySource SqlClientActivitySource = new ActivitySource(ActivitySourceName, Version.ToString()); #pragma warning restore SA1202 // Elements should be ordered by access - private readonly PropertyFetcher commandFetcher = new PropertyFetcher("Command"); - private readonly PropertyFetcher connectionFetcher = new PropertyFetcher("Connection"); - private readonly PropertyFetcher dataSourceFetcher = new PropertyFetcher("DataSource"); - private readonly PropertyFetcher databaseFetcher = new PropertyFetcher("Database"); - private readonly PropertyFetcher commandTypeFetcher = new PropertyFetcher("CommandType"); - private readonly PropertyFetcher commandTextFetcher = new PropertyFetcher("CommandText"); - private readonly PropertyFetcher exceptionFetcher = new PropertyFetcher("Exception"); + private readonly PropertyFetcher commandFetcher = new PropertyFetcher("Command"); + private readonly PropertyFetcher connectionFetcher = new PropertyFetcher("Connection"); + private readonly PropertyFetcher dataSourceFetcher = new PropertyFetcher("DataSource"); + private readonly PropertyFetcher databaseFetcher = new PropertyFetcher("Database"); + private readonly PropertyFetcher commandTypeFetcher = new PropertyFetcher("CommandType"); + private readonly PropertyFetcher commandTextFetcher = new PropertyFetcher("CommandText"); + private readonly PropertyFetcher exceptionFetcher = new PropertyFetcher("Exception"); private readonly SqlClientInstrumentationOptions options; public SqlClientDiagnosticListener(string sourceName, SqlClientInstrumentationOptions options) diff --git a/test/OpenTelemetry.Tests/Instrumentation/PropertyFetcherTest.cs b/test/OpenTelemetry.Tests/Instrumentation/PropertyFetcherTest.cs new file mode 100644 index 00000000000..92f10fe4c34 --- /dev/null +++ b/test/OpenTelemetry.Tests/Instrumentation/PropertyFetcherTest.cs @@ -0,0 +1,49 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using OpenTelemetry.Instrumentation; +using Xunit; + +namespace OpenTelemetry.Tests.Instrumentation +{ + public class PropertyFetcherTest + { + [Fact] + public void FetchValidProperty() + { + var activity = new Activity("test"); + var fetch = new PropertyFetcher("DisplayName"); + var result = fetch.Fetch(activity); + + Assert.Equal(activity.DisplayName, result); + } + + [Fact] + public void FetchInvalidProperty() + { + var activity = new Activity("test"); + var fetch = new PropertyFetcher("DisplayName2"); + var result = fetch.Fetch(activity); + + var fetchInt = new PropertyFetcher("DisplayName2"); + var resultInt = fetchInt.Fetch(activity); + + Assert.Equal(default, result); + Assert.Equal(default, resultInt); + } + } +} From 7fb504c3a60416d9924d7093c29bbbf74a42bef8 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 8 Sep 2020 19:37:42 -0700 Subject: [PATCH 5/6] CHANGELOG update. --- src/OpenTelemetry/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 87e1c8f0268..1dea61ffe7b 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -7,6 +7,8 @@ ([#1203](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1203)) * `PropertyFetcher` is now public ([#1232](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1232)) +* `PropertyFetcher` changed to `PropertyFetcher` + ([#1238](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1238)) ## 0.5.0-beta.2 From 1c0a6576efac5cb3871825c45b58ed1478a397e9 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 10 Sep 2020 20:58:56 -0700 Subject: [PATCH 6/6] Attempting to fix line endings. --- .../Implementation/HttpInListener.cs | 453 ++++++++------- .../Implementation/HttpInListener.cs | 546 +++++++++--------- .../HttpHandlerDiagnosticListener.cs | 376 ++++++------ 3 files changed, 687 insertions(+), 688 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs index 9b18556129b..30a95fa938a 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs @@ -1,227 +1,226 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Web; -using System.Web.Routing; -using OpenTelemetry.Context; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.AspNet.Implementation -{ - internal class HttpInListener : ListenerHandler - { - public const string RequestCustomPropertyName = "OTel.AspNet.Request"; - public const string ResponseCustomPropertyName = "OTel.AspNet.Response"; - private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener"; - private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name); - private readonly PropertyFetcher routeFetcher = new PropertyFetcher("Route"); - private readonly PropertyFetcher routeTemplateFetcher = new PropertyFetcher("RouteTemplate"); - private readonly AspNetInstrumentationOptions options; - private readonly ActivitySourceAdapter activitySource; - - public HttpInListener(string name, AspNetInstrumentationOptions options, ActivitySourceAdapter activitySource) - : base(name) - { - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.activitySource = activitySource; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")] - public override void OnStartActivity(Activity activity, object payload) - { - var context = HttpContext.Current; - if (context == null) - { - AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity)); - return; - } - - try - { - if (this.options.Filter?.Invoke(context) == false) - { - AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); - activity.IsAllDataRequested = false; - return; - } - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.RequestFilterException(ex); - activity.IsAllDataRequested = false; - return; - } - - var request = context.Request; - var requestValues = request.Unvalidated; - - if (!(this.options.Propagator is TextMapPropagator)) - { - var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter); - - if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context) - { - // Create a new activity with its parent set from the extracted context. - // This makes the new activity as a "sibling" of the activity created by - // ASP.NET. - Activity newOne = new Activity(ActivityNameByHttpInListener); - newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags); - newOne.TraceStateString = ctx.ActivityContext.TraceState; - - // Starting the new activity make it the Activity.Current one. - newOne.Start(); - - // Both new activity and old one store the other activity - // inside them. This is required in the Stop step to - // correctly stop and restore Activity.Current. - newOne.SetCustomProperty("OTel.ActivityByAspNet", activity); - activity.SetCustomProperty("OTel.ActivityByHttpInListener", newOne); - activity = newOne; - } - - if (ctx.Baggage != default) - { - Baggage.Current = ctx.Baggage; - } - } - - // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md - var path = requestValues.Path; - activity.DisplayName = path; - - this.activitySource.Start(activity, ActivityKind.Server); - - if (activity.IsAllDataRequested) - { - activity.SetCustomProperty(RequestCustomPropertyName, request); - if (request.Url.Port == 80 || request.Url.Port == 443) - { - activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host); - } - else - { - activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host + ":" + request.Url.Port); - } - - activity.SetTag(SemanticConventions.AttributeHttpMethod, request.HttpMethod); - activity.SetTag(SpanAttributeConstants.HttpPathKey, path); - activity.SetTag(SemanticConventions.AttributeHttpUserAgent, request.UserAgent); - activity.SetTag(SemanticConventions.AttributeHttpUrl, request.Url.ToString()); - } - } - - public override void OnStopActivity(Activity activity, object payload) - { - Activity activityToEnrich = activity; - Activity createdActivity = null; - - if (!(this.options.Propagator is TextMapPropagator)) - { - // If using custom context propagator, then the activity here - // could be either the one from Asp.Net, or the one - // this instrumentation created in Start. - // This is because Asp.Net, under certain circumstances, restores Activity.Current - // to its own activity. - if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start")) - { - // This block is hit if Asp.Net did restore Current to its own activity, - // and we need to retrieve the one created by HttpInListener, - // or an additional activity was never created. - createdActivity = (Activity)activity.GetCustomProperty("OTel.ActivityByHttpInListener"); - activityToEnrich = createdActivity ?? activity; - } - } - - if (activityToEnrich.IsAllDataRequested) - { - var context = HttpContext.Current; - if (context == null) - { - AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity)); - return; - } - - var response = context.Response; - - activityToEnrich.SetCustomProperty(ResponseCustomPropertyName, response); - activityToEnrich.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); - - activityToEnrich.SetStatus( - SpanHelper - .ResolveSpanStatusForHttpStatusCode(response.StatusCode) - .WithDescription(response.StatusDescription)); - - var routeData = context.Request.RequestContext.RouteData; - - string template = null; - if (routeData.Values.TryGetValue("MS_SubRoutes", out object msSubRoutes)) - { - // WebAPI attribute routing flows here. Use reflection to not take a dependency on microsoft.aspnet.webapi.core\[version]\lib\[framework]\System.Web.Http. - - if (msSubRoutes is Array attributeRouting && attributeRouting.Length == 1) - { - var subRouteData = attributeRouting.GetValue(0); - - var route = this.routeFetcher.Fetch(subRouteData); - template = this.routeTemplateFetcher.Fetch(route) as string; - } - } - else if (routeData.Route is Route route) - { - // MVC + WebAPI traditional routing & MVC attribute routing flow here. - template = route.Url; - } - - if (!string.IsNullOrEmpty(template)) - { - // Override the name that was previously set to the path part of URL. - activityToEnrich.DisplayName = template; - activityToEnrich.SetTag(SemanticConventions.AttributeHttpRoute, template); - } - } - - if (!(this.options.Propagator is TextMapPropagator)) - { - if (activity.OperationName.Equals(ActivityNameByHttpInListener)) - { - // If instrumentation started a new Activity, it must - // be stopped here. - activity.Stop(); - - // Restore the original activity as Current. - var activityByAspNet = (Activity)activity.GetCustomProperty("OTel.ActivityByAspNet"); - Activity.Current = activityByAspNet; - } - else if (createdActivity != null) - { - // This block is hit if Asp.Net did restore Current to its own activity, - // then we need to retrieve the one created by HttpInListener - // and stop it. - createdActivity.Stop(); - - // Restore current back to the one created by Asp.Net - Activity.Current = activity; - } - } - - this.activitySource.Stop(activityToEnrich); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Web; +using System.Web.Routing; +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.AspNet.Implementation +{ + internal class HttpInListener : ListenerHandler + { + public const string RequestCustomPropertyName = "OTel.AspNet.Request"; + public const string ResponseCustomPropertyName = "OTel.AspNet.Response"; + private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener"; + private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name); + private readonly PropertyFetcher routeFetcher = new PropertyFetcher("Route"); + private readonly PropertyFetcher routeTemplateFetcher = new PropertyFetcher("RouteTemplate"); + private readonly AspNetInstrumentationOptions options; + private readonly ActivitySourceAdapter activitySource; + + public HttpInListener(string name, AspNetInstrumentationOptions options, ActivitySourceAdapter activitySource) + : base(name) + { + this.options = options ?? throw new ArgumentNullException(nameof(options)); + this.activitySource = activitySource; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")] + public override void OnStartActivity(Activity activity, object payload) + { + var context = HttpContext.Current; + if (context == null) + { + AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity)); + return; + } + + try + { + if (this.options.Filter?.Invoke(context) == false) + { + AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); + activity.IsAllDataRequested = false; + return; + } + } + catch (Exception ex) + { + AspNetInstrumentationEventSource.Log.RequestFilterException(ex); + activity.IsAllDataRequested = false; + return; + } + + var request = context.Request; + var requestValues = request.Unvalidated; + + if (!(this.options.Propagator is TextMapPropagator)) + { + var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter); + + if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context) + { + // Create a new activity with its parent set from the extracted context. + // This makes the new activity as a "sibling" of the activity created by + // ASP.NET. + Activity newOne = new Activity(ActivityNameByHttpInListener); + newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags); + newOne.TraceStateString = ctx.ActivityContext.TraceState; + + // Starting the new activity make it the Activity.Current one. + newOne.Start(); + + // Both new activity and old one store the other activity + // inside them. This is required in the Stop step to + // correctly stop and restore Activity.Current. + newOne.SetCustomProperty("OTel.ActivityByAspNet", activity); + activity.SetCustomProperty("OTel.ActivityByHttpInListener", newOne); + activity = newOne; + } + + if (ctx.Baggage != default) + { + Baggage.Current = ctx.Baggage; + } + } + + // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md + var path = requestValues.Path; + activity.DisplayName = path; + + this.activitySource.Start(activity, ActivityKind.Server); + + if (activity.IsAllDataRequested) + { + activity.SetCustomProperty(RequestCustomPropertyName, request); + if (request.Url.Port == 80 || request.Url.Port == 443) + { + activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host); + } + else + { + activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host + ":" + request.Url.Port); + } + + activity.SetTag(SemanticConventions.AttributeHttpMethod, request.HttpMethod); + activity.SetTag(SpanAttributeConstants.HttpPathKey, path); + activity.SetTag(SemanticConventions.AttributeHttpUserAgent, request.UserAgent); + activity.SetTag(SemanticConventions.AttributeHttpUrl, request.Url.ToString()); + } + } + + public override void OnStopActivity(Activity activity, object payload) + { + Activity activityToEnrich = activity; + Activity createdActivity = null; + + if (!(this.options.Propagator is TextMapPropagator)) + { + // If using custom context propagator, then the activity here + // could be either the one from Asp.Net, or the one + // this instrumentation created in Start. + // This is because Asp.Net, under certain circumstances, restores Activity.Current + // to its own activity. + if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start")) + { + // This block is hit if Asp.Net did restore Current to its own activity, + // and we need to retrieve the one created by HttpInListener, + // or an additional activity was never created. + createdActivity = (Activity)activity.GetCustomProperty("OTel.ActivityByHttpInListener"); + activityToEnrich = createdActivity ?? activity; + } + } + + if (activityToEnrich.IsAllDataRequested) + { + var context = HttpContext.Current; + if (context == null) + { + AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity)); + return; + } + + var response = context.Response; + + activityToEnrich.SetCustomProperty(ResponseCustomPropertyName, response); + activityToEnrich.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); + + activityToEnrich.SetStatus( + SpanHelper + .ResolveSpanStatusForHttpStatusCode(response.StatusCode) + .WithDescription(response.StatusDescription)); + + var routeData = context.Request.RequestContext.RouteData; + + string template = null; + if (routeData.Values.TryGetValue("MS_SubRoutes", out object msSubRoutes)) + { + // WebAPI attribute routing flows here. Use reflection to not take a dependency on microsoft.aspnet.webapi.core\[version]\lib\[framework]\System.Web.Http. + + if (msSubRoutes is Array attributeRouting && attributeRouting.Length == 1) + { + var subRouteData = attributeRouting.GetValue(0); + + var route = this.routeFetcher.Fetch(subRouteData); + template = this.routeTemplateFetcher.Fetch(route) as string; + } + } + else if (routeData.Route is Route route) + { + // MVC + WebAPI traditional routing & MVC attribute routing flow here. + template = route.Url; + } + + if (!string.IsNullOrEmpty(template)) + { + // Override the name that was previously set to the path part of URL. + activityToEnrich.DisplayName = template; + activityToEnrich.SetTag(SemanticConventions.AttributeHttpRoute, template); + } + } + + if (!(this.options.Propagator is TextMapPropagator)) + { + if (activity.OperationName.Equals(ActivityNameByHttpInListener)) + { + // If instrumentation started a new Activity, it must + // be stopped here. + activity.Stop(); + + // Restore the original activity as Current. + var activityByAspNet = (Activity)activity.GetCustomProperty("OTel.ActivityByAspNet"); + Activity.Current = activityByAspNet; + } + else if (createdActivity != null) + { + // This block is hit if Asp.Net did restore Current to its own activity, + // then we need to retrieve the one created by HttpInListener + // and stop it. + createdActivity.Stop(); + + // Restore current back to the one created by Asp.Net + Activity.Current = activity; + } + } + + this.activitySource.Stop(activityToEnrich); + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index 40f88817ac8..e4665e1bac0 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -1,273 +1,273 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Instrumentation.GrpcNetClient; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation -{ - internal class HttpInListener : ListenerHandler - { - public const string RequestCustomPropertyName = "OTel.AspNetCore.Request"; - public const string ResponseCustomPropertyName = "OTel.AspNetCore.Response"; - private const string UnknownHostName = "UNKNOWN-HOST"; - private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener"; - private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers[name]; - private readonly PropertyFetcher startContextFetcher = new PropertyFetcher("HttpContext"); - private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext"); - private readonly PropertyFetcher beforeActionActionDescriptorFetcher = new PropertyFetcher("actionDescriptor"); - private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new PropertyFetcher("AttributeRouteInfo"); - private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template"); - private readonly bool hostingSupportsW3C; - private readonly AspNetCoreInstrumentationOptions options; - private readonly ActivitySourceAdapter activitySource; - - public HttpInListener(string name, AspNetCoreInstrumentationOptions options, ActivitySourceAdapter activitySource) - : base(name) - { - this.hostingSupportsW3C = typeof(HttpRequest).Assembly.GetName().Version.Major >= 3; - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.activitySource = activitySource; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")] - public override void OnStartActivity(Activity activity, object payload) - { - HttpContext context = this.startContextFetcher.Fetch(payload); - if (context == null) - { - AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity)); - return; - } - - try - { - if (this.options.Filter?.Invoke(context) == false) - { - AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); - activity.IsAllDataRequested = false; - return; - } - } - catch (Exception ex) - { - AspNetCoreInstrumentationEventSource.Log.RequestFilterException(ex); - activity.IsAllDataRequested = false; - return; - } - - var request = context.Request; - if (!this.hostingSupportsW3C || !(this.options.Propagator is TextMapPropagator)) - { - var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter); - - if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context) - { - // Create a new activity with its parent set from the extracted context. - // This makes the new activity as a "sibling" of the activity created by - // Asp.Net Core. - Activity newOne = new Activity(ActivityNameByHttpInListener); - newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags); - newOne.TraceStateString = ctx.ActivityContext.TraceState; - - // Starting the new activity make it the Activity.Current one. - newOne.Start(); - activity = newOne; - } - - if (ctx.Baggage != default) - { - Baggage.Current = ctx.Baggage; - } - } - - this.activitySource.Start(activity, ActivityKind.Server); - - if (activity.IsAllDataRequested) - { - activity.SetCustomProperty(RequestCustomPropertyName, request); - - var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/"; - activity.DisplayName = path; - - // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md - - if (request.Host.Port == 80 || request.Host.Port == 443) - { - activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host); - } - else - { - activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host + ":" + request.Host.Port); - } - - activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method); - activity.SetTag(SpanAttributeConstants.HttpPathKey, path); - activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUri(request)); - - var userAgent = request.Headers["User-Agent"].FirstOrDefault(); - if (!string.IsNullOrEmpty(userAgent)) - { - activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent); - } - } - } - - public override void OnStopActivity(Activity activity, object payload) - { - if (activity.IsAllDataRequested) - { - HttpContext context = this.stopContextFetcher.Fetch(payload); - if (context == null) - { - AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity)); - return; - } - - var response = context.Response; - activity.SetCustomProperty(ResponseCustomPropertyName, response); - activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); - - if (TryGetGrpcMethod(activity, out var grpcMethod)) - { - AddGrpcAttributes(activity, grpcMethod, context); - } - else - { - Status status = SpanHelper.ResolveSpanStatusForHttpStatusCode(response.StatusCode); - activity.SetStatus(status.WithDescription(response.HttpContext.Features.Get()?.ReasonPhrase)); - } - } - - if (activity.OperationName.Equals(ActivityNameByHttpInListener)) - { - // If instrumentation started a new Activity, it must - // be stopped here. - activity.Stop(); - - // After the activity.Stop() code, Activity.Current becomes null. - // If Asp.Net Core uses Activity.Current?.Stop() - it'll not stop the activity - // it created. - // Currently Asp.Net core does not use Activity.Current, instead it stores a - // reference to its activity, and calls .Stop on it. - - // TODO: Should we still restore Activity.Current here? - // If yes, then we need to store the asp.net core activity inside - // the one created by the instrumentation. - // And retrieve it here, and set it to Current. - } - - this.activitySource.Stop(activity); - } - - public override void OnCustom(string name, Activity activity, object payload) - { - if (name == "Microsoft.AspNetCore.Mvc.BeforeAction") - { - if (activity.IsAllDataRequested) - { - // See https://github.com/aspnet/Mvc/blob/2414db256f32a047770326d14d8b0e2afd49ba49/src/Microsoft.AspNetCore.Mvc.Core/MvcCoreDiagnosticSourceExtensions.cs#L36-L44 - // Reflection accessing: ActionDescriptor.AttributeRouteInfo.Template - // The reason to use reflection is to avoid a reference on MVC package. - // This package can be used with non-MVC apps and this logic simply wouldn't run. - // Taking reference on MVC will increase size of deployment for non-MVC apps. - var actionDescriptor = this.beforeActionActionDescriptorFetcher.Fetch(payload); - var attributeRouteInfo = this.beforeActionAttributeRouteInfoFetcher.Fetch(actionDescriptor); - var template = this.beforeActionTemplateFetcher.Fetch(attributeRouteInfo); - - if (!string.IsNullOrEmpty(template)) - { - // override the span name that was previously set to the path part of URL. - activity.DisplayName = template; - activity.SetTag(SemanticConventions.AttributeHttpRoute, template); - } - - // TODO: Should we get values from RouteData? - // private readonly PropertyFetcher beforActionRouteDataFetcher = new PropertyFetcher("routeData"); - // var routeData = this.beforActionRouteDataFetcher.Fetch(payload) as RouteData; - } - } - } - - private static string GetUri(HttpRequest request) - { - var builder = new StringBuilder(); - - builder.Append(request.Scheme).Append("://"); - - if (request.Host.HasValue) - { - builder.Append(request.Host.Value); - } - else - { - // HTTP 1.0 request with NO host header would result in empty Host. - // Use placeholder to avoid incorrect URL like "http:///" - builder.Append(UnknownHostName); - } - - if (request.PathBase.HasValue) - { - builder.Append(request.PathBase.Value); - } - - if (request.Path.HasValue) - { - builder.Append(request.Path.Value); - } - - if (request.QueryString.HasValue) - { - builder.Append(request.QueryString); - } - - return builder.ToString(); - } - - private static bool TryGetGrpcMethod(Activity activity, out string grpcMethod) - { - grpcMethod = GrpcTagHelper.GetGrpcMethodFromActivity(activity); - return !string.IsNullOrEmpty(grpcMethod); - } - - private static void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext context) - { - // TODO: Should the leading slash be trimmed? Spec seems to suggest no leading slash: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md#span-name - // Client instrumentation is trimming the leading slash. Whatever we decide here, should we apply the same to the client side? - // activity.DisplayName = grpcMethod?.Trim('/'); - - activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc); - - if (GrpcTagHelper.TryParseRpcServiceAndRpcMethod(grpcMethod, out var rpcService, out var rpcMethod)) - { - activity.SetTag(SemanticConventions.AttributeRpcService, rpcService); - activity.SetTag(SemanticConventions.AttributeRpcMethod, rpcMethod); - } - - activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString()); - activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort); - activity.SetStatus(GrpcTagHelper.GetGrpcStatusCodeFromActivity(activity)); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Instrumentation.GrpcNetClient; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation +{ + internal class HttpInListener : ListenerHandler + { + public const string RequestCustomPropertyName = "OTel.AspNetCore.Request"; + public const string ResponseCustomPropertyName = "OTel.AspNetCore.Response"; + private const string UnknownHostName = "UNKNOWN-HOST"; + private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener"; + private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers[name]; + private readonly PropertyFetcher startContextFetcher = new PropertyFetcher("HttpContext"); + private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext"); + private readonly PropertyFetcher beforeActionActionDescriptorFetcher = new PropertyFetcher("actionDescriptor"); + private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new PropertyFetcher("AttributeRouteInfo"); + private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template"); + private readonly bool hostingSupportsW3C; + private readonly AspNetCoreInstrumentationOptions options; + private readonly ActivitySourceAdapter activitySource; + + public HttpInListener(string name, AspNetCoreInstrumentationOptions options, ActivitySourceAdapter activitySource) + : base(name) + { + this.hostingSupportsW3C = typeof(HttpRequest).Assembly.GetName().Version.Major >= 3; + this.options = options ?? throw new ArgumentNullException(nameof(options)); + this.activitySource = activitySource; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")] + public override void OnStartActivity(Activity activity, object payload) + { + HttpContext context = this.startContextFetcher.Fetch(payload); + if (context == null) + { + AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity)); + return; + } + + try + { + if (this.options.Filter?.Invoke(context) == false) + { + AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); + activity.IsAllDataRequested = false; + return; + } + } + catch (Exception ex) + { + AspNetCoreInstrumentationEventSource.Log.RequestFilterException(ex); + activity.IsAllDataRequested = false; + return; + } + + var request = context.Request; + if (!this.hostingSupportsW3C || !(this.options.Propagator is TextMapPropagator)) + { + var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter); + + if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context) + { + // Create a new activity with its parent set from the extracted context. + // This makes the new activity as a "sibling" of the activity created by + // Asp.Net Core. + Activity newOne = new Activity(ActivityNameByHttpInListener); + newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags); + newOne.TraceStateString = ctx.ActivityContext.TraceState; + + // Starting the new activity make it the Activity.Current one. + newOne.Start(); + activity = newOne; + } + + if (ctx.Baggage != default) + { + Baggage.Current = ctx.Baggage; + } + } + + this.activitySource.Start(activity, ActivityKind.Server); + + if (activity.IsAllDataRequested) + { + activity.SetCustomProperty(RequestCustomPropertyName, request); + + var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/"; + activity.DisplayName = path; + + // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md + + if (request.Host.Port == 80 || request.Host.Port == 443) + { + activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host); + } + else + { + activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host + ":" + request.Host.Port); + } + + activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method); + activity.SetTag(SpanAttributeConstants.HttpPathKey, path); + activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUri(request)); + + var userAgent = request.Headers["User-Agent"].FirstOrDefault(); + if (!string.IsNullOrEmpty(userAgent)) + { + activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent); + } + } + } + + public override void OnStopActivity(Activity activity, object payload) + { + if (activity.IsAllDataRequested) + { + HttpContext context = this.stopContextFetcher.Fetch(payload); + if (context == null) + { + AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity)); + return; + } + + var response = context.Response; + activity.SetCustomProperty(ResponseCustomPropertyName, response); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); + + if (TryGetGrpcMethod(activity, out var grpcMethod)) + { + AddGrpcAttributes(activity, grpcMethod, context); + } + else + { + Status status = SpanHelper.ResolveSpanStatusForHttpStatusCode(response.StatusCode); + activity.SetStatus(status.WithDescription(response.HttpContext.Features.Get()?.ReasonPhrase)); + } + } + + if (activity.OperationName.Equals(ActivityNameByHttpInListener)) + { + // If instrumentation started a new Activity, it must + // be stopped here. + activity.Stop(); + + // After the activity.Stop() code, Activity.Current becomes null. + // If Asp.Net Core uses Activity.Current?.Stop() - it'll not stop the activity + // it created. + // Currently Asp.Net core does not use Activity.Current, instead it stores a + // reference to its activity, and calls .Stop on it. + + // TODO: Should we still restore Activity.Current here? + // If yes, then we need to store the asp.net core activity inside + // the one created by the instrumentation. + // And retrieve it here, and set it to Current. + } + + this.activitySource.Stop(activity); + } + + public override void OnCustom(string name, Activity activity, object payload) + { + if (name == "Microsoft.AspNetCore.Mvc.BeforeAction") + { + if (activity.IsAllDataRequested) + { + // See https://github.com/aspnet/Mvc/blob/2414db256f32a047770326d14d8b0e2afd49ba49/src/Microsoft.AspNetCore.Mvc.Core/MvcCoreDiagnosticSourceExtensions.cs#L36-L44 + // Reflection accessing: ActionDescriptor.AttributeRouteInfo.Template + // The reason to use reflection is to avoid a reference on MVC package. + // This package can be used with non-MVC apps and this logic simply wouldn't run. + // Taking reference on MVC will increase size of deployment for non-MVC apps. + var actionDescriptor = this.beforeActionActionDescriptorFetcher.Fetch(payload); + var attributeRouteInfo = this.beforeActionAttributeRouteInfoFetcher.Fetch(actionDescriptor); + var template = this.beforeActionTemplateFetcher.Fetch(attributeRouteInfo); + + if (!string.IsNullOrEmpty(template)) + { + // override the span name that was previously set to the path part of URL. + activity.DisplayName = template; + activity.SetTag(SemanticConventions.AttributeHttpRoute, template); + } + + // TODO: Should we get values from RouteData? + // private readonly PropertyFetcher beforActionRouteDataFetcher = new PropertyFetcher("routeData"); + // var routeData = this.beforActionRouteDataFetcher.Fetch(payload) as RouteData; + } + } + } + + private static string GetUri(HttpRequest request) + { + var builder = new StringBuilder(); + + builder.Append(request.Scheme).Append("://"); + + if (request.Host.HasValue) + { + builder.Append(request.Host.Value); + } + else + { + // HTTP 1.0 request with NO host header would result in empty Host. + // Use placeholder to avoid incorrect URL like "http:///" + builder.Append(UnknownHostName); + } + + if (request.PathBase.HasValue) + { + builder.Append(request.PathBase.Value); + } + + if (request.Path.HasValue) + { + builder.Append(request.Path.Value); + } + + if (request.QueryString.HasValue) + { + builder.Append(request.QueryString); + } + + return builder.ToString(); + } + + private static bool TryGetGrpcMethod(Activity activity, out string grpcMethod) + { + grpcMethod = GrpcTagHelper.GetGrpcMethodFromActivity(activity); + return !string.IsNullOrEmpty(grpcMethod); + } + + private static void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext context) + { + // TODO: Should the leading slash be trimmed? Spec seems to suggest no leading slash: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md#span-name + // Client instrumentation is trimming the leading slash. Whatever we decide here, should we apply the same to the client side? + // activity.DisplayName = grpcMethod?.Trim('/'); + + activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc); + + if (GrpcTagHelper.TryParseRpcServiceAndRpcMethod(grpcMethod, out var rpcService, out var rpcMethod)) + { + activity.SetTag(SemanticConventions.AttributeRpcService, rpcService); + activity.SetTag(SemanticConventions.AttributeRpcMethod, rpcMethod); + } + + activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString()); + activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort); + activity.SetStatus(GrpcTagHelper.GetGrpcStatusCodeFromActivity(activity)); + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index 5eb8338232d..d43c9e0e01c 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -1,188 +1,188 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net.Http; -using System.Net.Sockets; -using System.Reflection; -using System.Runtime.Versioning; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.Http.Implementation -{ - internal class HttpHandlerDiagnosticListener : ListenerHandler - { - public const string RequestCustomPropertyName = "OTel.HttpHandler.Request"; - public const string ResponseCustomPropertyName = "OTel.HttpHandler.Response"; - public const string ExceptionCustomPropertyName = "OTel.HttpHandler.Exception"; - - private static readonly Func> HttpRequestMessageHeaderValuesGetter = (request, name) => - { - if (request.Headers.TryGetValues(name, out var values)) - { - return values; - } - - return null; - }; - - private static readonly Action HttpRequestMessageHeaderValueSetter = (request, name, value) => request.Headers.Add(name, value); - - private static readonly Regex CoreAppMajorVersionCheckRegex = new Regex("^\\.NETCoreApp,Version=v(\\d+)\\.", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private readonly ActivitySourceAdapter activitySource; - private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request"); - private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response"); - private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception"); - private readonly PropertyFetcher stopRequestStatusFetcher = new PropertyFetcher("RequestTaskStatus"); - private readonly bool httpClientSupportsW3C; - private readonly HttpClientInstrumentationOptions options; - - public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options, ActivitySourceAdapter activitySource) - : base("HttpHandlerDiagnosticListener") - { - var framework = Assembly - .GetEntryAssembly()? - .GetCustomAttribute()? - .FrameworkName; - - // Depending on the .NET version/flavor this will look like - // '.NETCoreApp,Version=v3.0', '.NETCoreApp,Version = v2.2' or '.NETFramework,Version = v4.7.1' - - if (framework != null) - { - var match = CoreAppMajorVersionCheckRegex.Match(framework); - - this.httpClientSupportsW3C = match.Success && int.Parse(match.Groups[1].Value) >= 3; - } - - this.options = options; - this.activitySource = activitySource; - } - - public override void OnStartActivity(Activity activity, object payload) - { - if (!(this.startRequestFetcher.Fetch(payload) is HttpRequestMessage request)) - { - HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnStartActivity)); - return; - } - - if (this.options.Propagator.Extract(default, request, HttpRequestMessageHeaderValuesGetter) != default) - { - // this request is already instrumented, we should back off - activity.IsAllDataRequested = false; - return; - } - - activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method); - - this.activitySource.Start(activity, ActivityKind.Client); - - if (activity.IsAllDataRequested) - { - activity.SetCustomProperty(RequestCustomPropertyName, request); - activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)); - activity.SetTag(SemanticConventions.AttributeHttpHost, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri)); - activity.SetTag(SemanticConventions.AttributeHttpUrl, request.RequestUri.OriginalString); - - if (this.options.SetHttpFlavor) - { - activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)); - } - } - - if (!(this.httpClientSupportsW3C && this.options.Propagator is TextMapPropagator)) - { - this.options.Propagator.Inject(new PropagationContext(activity.Context, Baggage.Current), request, HttpRequestMessageHeaderValueSetter); - } - } - - public override void OnStopActivity(Activity activity, object payload) - { - if (activity.IsAllDataRequested) - { - // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs - // requestTaskStatus is not null - var requestTaskStatus = this.stopRequestStatusFetcher.Fetch(payload); - - if (requestTaskStatus != TaskStatus.RanToCompletion) - { - if (requestTaskStatus == TaskStatus.Canceled) - { - activity.SetStatus(Status.Cancelled); - } - else if (requestTaskStatus != TaskStatus.Faulted) - { - // Faults are handled in OnException and should already have a span.Status of Unknown w/ Description. - activity.SetStatus(Status.Unknown); - } - } - - if (this.stopResponseFetcher.Fetch(payload) is HttpResponseMessage response) - { - activity.SetCustomProperty(ResponseCustomPropertyName, response); - - activity.SetTag(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode); - - activity.SetStatus( - SpanHelper - .ResolveSpanStatusForHttpStatusCode((int)response.StatusCode) - .WithDescription(response.ReasonPhrase)); - } - } - - this.activitySource.Stop(activity); - } - - public override void OnException(Activity activity, object payload) - { - if (activity.IsAllDataRequested) - { - if (!(this.stopExceptionFetcher.Fetch(payload) is Exception exc)) - { - HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnException)); - return; - } - - activity.SetCustomProperty(ExceptionCustomPropertyName, exc); - - if (exc is HttpRequestException) - { - if (exc.InnerException is SocketException exception) - { - switch (exception.SocketErrorCode) - { - case SocketError.HostNotFound: - activity.SetStatus(Status.InvalidArgument.WithDescription(exc.Message)); - return; - } - } - - if (exc.InnerException != null) - { - activity.SetStatus(Status.Unknown.WithDescription(exc.Message)); - } - } - } - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.Versioning; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.Http.Implementation +{ + internal class HttpHandlerDiagnosticListener : ListenerHandler + { + public const string RequestCustomPropertyName = "OTel.HttpHandler.Request"; + public const string ResponseCustomPropertyName = "OTel.HttpHandler.Response"; + public const string ExceptionCustomPropertyName = "OTel.HttpHandler.Exception"; + + private static readonly Func> HttpRequestMessageHeaderValuesGetter = (request, name) => + { + if (request.Headers.TryGetValues(name, out var values)) + { + return values; + } + + return null; + }; + + private static readonly Action HttpRequestMessageHeaderValueSetter = (request, name, value) => request.Headers.Add(name, value); + + private static readonly Regex CoreAppMajorVersionCheckRegex = new Regex("^\\.NETCoreApp,Version=v(\\d+)\\.", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private readonly ActivitySourceAdapter activitySource; + private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request"); + private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response"); + private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception"); + private readonly PropertyFetcher stopRequestStatusFetcher = new PropertyFetcher("RequestTaskStatus"); + private readonly bool httpClientSupportsW3C; + private readonly HttpClientInstrumentationOptions options; + + public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options, ActivitySourceAdapter activitySource) + : base("HttpHandlerDiagnosticListener") + { + var framework = Assembly + .GetEntryAssembly()? + .GetCustomAttribute()? + .FrameworkName; + + // Depending on the .NET version/flavor this will look like + // '.NETCoreApp,Version=v3.0', '.NETCoreApp,Version = v2.2' or '.NETFramework,Version = v4.7.1' + + if (framework != null) + { + var match = CoreAppMajorVersionCheckRegex.Match(framework); + + this.httpClientSupportsW3C = match.Success && int.Parse(match.Groups[1].Value) >= 3; + } + + this.options = options; + this.activitySource = activitySource; + } + + public override void OnStartActivity(Activity activity, object payload) + { + if (!(this.startRequestFetcher.Fetch(payload) is HttpRequestMessage request)) + { + HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnStartActivity)); + return; + } + + if (this.options.Propagator.Extract(default, request, HttpRequestMessageHeaderValuesGetter) != default) + { + // this request is already instrumented, we should back off + activity.IsAllDataRequested = false; + return; + } + + activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method); + + this.activitySource.Start(activity, ActivityKind.Client); + + if (activity.IsAllDataRequested) + { + activity.SetCustomProperty(RequestCustomPropertyName, request); + activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)); + activity.SetTag(SemanticConventions.AttributeHttpHost, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri)); + activity.SetTag(SemanticConventions.AttributeHttpUrl, request.RequestUri.OriginalString); + + if (this.options.SetHttpFlavor) + { + activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)); + } + } + + if (!(this.httpClientSupportsW3C && this.options.Propagator is TextMapPropagator)) + { + this.options.Propagator.Inject(new PropagationContext(activity.Context, Baggage.Current), request, HttpRequestMessageHeaderValueSetter); + } + } + + public override void OnStopActivity(Activity activity, object payload) + { + if (activity.IsAllDataRequested) + { + // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs + // requestTaskStatus is not null + var requestTaskStatus = this.stopRequestStatusFetcher.Fetch(payload); + + if (requestTaskStatus != TaskStatus.RanToCompletion) + { + if (requestTaskStatus == TaskStatus.Canceled) + { + activity.SetStatus(Status.Cancelled); + } + else if (requestTaskStatus != TaskStatus.Faulted) + { + // Faults are handled in OnException and should already have a span.Status of Unknown w/ Description. + activity.SetStatus(Status.Unknown); + } + } + + if (this.stopResponseFetcher.Fetch(payload) is HttpResponseMessage response) + { + activity.SetCustomProperty(ResponseCustomPropertyName, response); + + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode); + + activity.SetStatus( + SpanHelper + .ResolveSpanStatusForHttpStatusCode((int)response.StatusCode) + .WithDescription(response.ReasonPhrase)); + } + } + + this.activitySource.Stop(activity); + } + + public override void OnException(Activity activity, object payload) + { + if (activity.IsAllDataRequested) + { + if (!(this.stopExceptionFetcher.Fetch(payload) is Exception exc)) + { + HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnException)); + return; + } + + activity.SetCustomProperty(ExceptionCustomPropertyName, exc); + + if (exc is HttpRequestException) + { + if (exc.InnerException is SocketException exception) + { + switch (exception.SocketErrorCode) + { + case SocketError.HostNotFound: + activity.SetStatus(Status.InvalidArgument.WithDescription(exc.Message)); + return; + } + } + + if (exc.InnerException != null) + { + activity.SetStatus(Status.Unknown.WithDescription(exc.Message)); + } + } + } + } + } +}