From d9e91a7525cab01467d2e44661195c9ecb086e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Kie=C5=82kowicz?= Date: Fri, 12 Aug 2022 19:05:42 +0200 Subject: [PATCH] [Instrumenation.AspNet] File scoped namespace (#581) --- .../ActivityHelper.cs | 315 ++++--- .../AspNetTelemetryEventSource.cs | 147 ++-- .../TelemetryHttpModule.cs | 179 ++-- .../TelemetryHttpModuleOptions.cs | 67 +- .../AspNetInstrumentation.cs | 35 +- .../AspNetInstrumentationOptions.cs | 73 +- .../AspNetMetrics.cs | 49 +- .../AspNetInstrumentationEventSource.cs | 69 +- .../Implementation/HttpInListener.cs | 257 +++--- .../Implementation/HttpInMetricsListener.cs | 47 +- .../MeterProviderBuilderExtensions.cs | 31 +- .../TracerProviderBuilderExtensions.cs | 39 +- .../ActivityHelperTest.cs | 791 +++++++++--------- .../HttpContextHelper.cs | 111 ++- .../WebConfigTransformTest.cs | 237 +++--- .../WebConfigWithLocationTagTransformTest.cs | 203 +++-- .../BasicTests.cs | 15 +- .../EventSourceTest.cs | 13 +- .../HttpInListenerTests.cs | 555 ++++++------ .../HttpInMetricsListenerTests.cs | 123 ++- 20 files changed, 1668 insertions(+), 1688 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs index 4a1e648ad0c..5c510f7c448 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs @@ -22,203 +22,202 @@ using OpenTelemetry.Context; using OpenTelemetry.Context.Propagation; -namespace OpenTelemetry.Instrumentation.AspNet +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// Activity helper class. +/// +internal static class ActivityHelper { /// - /// Activity helper class. + /// Key to store the state in HttpContext. /// - internal static class ActivityHelper - { - /// - /// Key to store the state in HttpContext. - /// - internal const string ContextKey = "__AspnetInstrumentationContext__"; - internal static readonly object StartedButNotSampledObj = new(); - - private const string BaggageSlotName = "otel.baggage"; - private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name); - private static readonly ActivitySource AspNetSource = new( - TelemetryHttpModule.AspNetSourceName, - typeof(ActivityHelper).Assembly.GetName().Version.ToString()); - - /// - /// Try to get the started for the running . - /// - /// . - /// Started or if 1) start has not been called or 2) start was - /// called but sampling decided not to create an instance. - /// if start has been called. - public static bool HasStarted(HttpContext context, out Activity aspNetActivity) - { - Debug.Assert(context != null, "Context is null."); + internal const string ContextKey = "__AspnetInstrumentationContext__"; + internal static readonly object StartedButNotSampledObj = new(); - object itemValue = context.Items[ContextKey]; - if (itemValue is ContextHolder contextHolder) - { - aspNetActivity = contextHolder.Activity; - return true; - } + private const string BaggageSlotName = "otel.baggage"; + private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name); + private static readonly ActivitySource AspNetSource = new( + TelemetryHttpModule.AspNetSourceName, + typeof(ActivityHelper).Assembly.GetName().Version.ToString()); - aspNetActivity = null; - return itemValue == StartedButNotSampledObj; - } + /// + /// Try to get the started for the running . + /// + /// . + /// Started or if 1) start has not been called or 2) start was + /// called but sampling decided not to create an instance. + /// if start has been called. + public static bool HasStarted(HttpContext context, out Activity aspNetActivity) + { + Debug.Assert(context != null, "Context is null."); - /// - /// Creates root (first level) activity that describes incoming request. - /// - /// . - /// . - /// Callback action. - /// New root activity. - public static Activity StartAspNetActivity(TextMapPropagator textMapPropagator, HttpContext context, Action onRequestStartedCallback) + object itemValue = context.Items[ContextKey]; + if (itemValue is ContextHolder contextHolder) { - Debug.Assert(context != null, "Context is null."); + aspNetActivity = contextHolder.Activity; + return true; + } - PropagationContext propagationContext = textMapPropagator.Extract(default, context.Request, HttpRequestHeaderValuesGetter); + aspNetActivity = null; + return itemValue == StartedButNotSampledObj; + } - Activity activity = AspNetSource.StartActivity(TelemetryHttpModule.AspNetActivityName, ActivityKind.Server, propagationContext.ActivityContext); + /// + /// Creates root (first level) activity that describes incoming request. + /// + /// . + /// . + /// Callback action. + /// New root activity. + public static Activity StartAspNetActivity(TextMapPropagator textMapPropagator, HttpContext context, Action onRequestStartedCallback) + { + Debug.Assert(context != null, "Context is null."); - if (activity != null) - { - if (textMapPropagator is not TraceContextPropagator) - { - Baggage.Current = propagationContext.Baggage; - - context.Items[ContextKey] = new ContextHolder { Activity = activity, Baggage = RuntimeContext.GetValue(BaggageSlotName) }; - } - else - { - context.Items[ContextKey] = new ContextHolder { Activity = activity }; - } - - try - { - onRequestStartedCallback?.Invoke(activity, context); - } - catch (Exception callbackEx) - { - AspNetTelemetryEventSource.Log.CallbackException(activity, "OnStarted", callbackEx); - } - - AspNetTelemetryEventSource.Log.ActivityStarted(activity); - } - else - { - context.Items[ContextKey] = StartedButNotSampledObj; - } + PropagationContext propagationContext = textMapPropagator.Extract(default, context.Request, HttpRequestHeaderValuesGetter); - return activity; - } + Activity activity = AspNetSource.StartActivity(TelemetryHttpModule.AspNetActivityName, ActivityKind.Server, propagationContext.ActivityContext); - /// - /// Stops the activity and notifies listeners about it. - /// - /// . - /// . - /// . - /// Callback action. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void StopAspNetActivity(TextMapPropagator textMapPropagator, Activity aspNetActivity, HttpContext context, Action onRequestStoppedCallback) + if (activity != null) { - Debug.Assert(context != null, "Context is null."); - - if (aspNetActivity == null) + if (textMapPropagator is not TraceContextPropagator) { - Debug.Assert(context.Items[ContextKey] == StartedButNotSampledObj, "Context item is not StartedButNotSampledObj."); + Baggage.Current = propagationContext.Baggage; - // This is the case where a start was called but no activity was - // created due to a sampling decision. - context.Items[ContextKey] = null; - return; + context.Items[ContextKey] = new ContextHolder { Activity = activity, Baggage = RuntimeContext.GetValue(BaggageSlotName) }; + } + else + { + context.Items[ContextKey] = new ContextHolder { Activity = activity }; } - - Debug.Assert(context.Items[ContextKey] is ContextHolder, "Context item is not an ContextHolder instance."); - - var currentActivity = Activity.Current; - - aspNetActivity.Stop(); - context.Items[ContextKey] = null; try { - onRequestStoppedCallback?.Invoke(aspNetActivity, context); + onRequestStartedCallback?.Invoke(activity, context); } catch (Exception callbackEx) { - AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnStopped", callbackEx); + AspNetTelemetryEventSource.Log.CallbackException(activity, "OnStarted", callbackEx); } - AspNetTelemetryEventSource.Log.ActivityStopped(currentActivity); + AspNetTelemetryEventSource.Log.ActivityStarted(activity); + } + else + { + context.Items[ContextKey] = StartedButNotSampledObj; + } - if (textMapPropagator is not TraceContextPropagator) - { - Baggage.Current = default; - } + return activity; + } - if (currentActivity != aspNetActivity) - { - Activity.Current = currentActivity; - } + /// + /// Stops the activity and notifies listeners about it. + /// + /// . + /// . + /// . + /// Callback action. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void StopAspNetActivity(TextMapPropagator textMapPropagator, Activity aspNetActivity, HttpContext context, Action onRequestStoppedCallback) + { + Debug.Assert(context != null, "Context is null."); + + if (aspNetActivity == null) + { + Debug.Assert(context.Items[ContextKey] == StartedButNotSampledObj, "Context item is not StartedButNotSampledObj."); + + // This is the case where a start was called but no activity was + // created due to a sampling decision. + context.Items[ContextKey] = null; + return; } - /// - /// Notifies listeners about an unhandled exception thrown on the . - /// - /// . - /// . - /// . - /// Callback action. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteActivityException(Activity aspNetActivity, HttpContext context, Exception exception, Action onExceptionCallback) + Debug.Assert(context.Items[ContextKey] is ContextHolder, "Context item is not an ContextHolder instance."); + + var currentActivity = Activity.Current; + + aspNetActivity.Stop(); + context.Items[ContextKey] = null; + + try + { + onRequestStoppedCallback?.Invoke(aspNetActivity, context); + } + catch (Exception callbackEx) { - Debug.Assert(context != null, "Context is null."); - Debug.Assert(exception != null, "Exception is null."); + AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnStopped", callbackEx); + } - if (aspNetActivity != null) - { - try - { - onExceptionCallback?.Invoke(aspNetActivity, context, exception); - } - catch (Exception callbackEx) - { - AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnException", callbackEx); - } - - AspNetTelemetryEventSource.Log.ActivityException(aspNetActivity, exception); - } + AspNetTelemetryEventSource.Log.ActivityStopped(currentActivity); + + if (textMapPropagator is not TraceContextPropagator) + { + Baggage.Current = default; } - /// - /// It's possible that a request is executed in both native threads and managed threads, - /// in such case Activity.Current will be lost during native thread and managed thread switch. - /// This method is intended to restore the current activity in order to correlate the child - /// activities with the root activity of the request. - /// - /// . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void RestoreContextIfNeeded(HttpContext context) + if (currentActivity != aspNetActivity) { - Debug.Assert(context != null, "Context is null."); + Activity.Current = currentActivity; + } + } - if (context.Items[ContextKey] is ContextHolder contextHolder && Activity.Current != contextHolder.Activity) - { - Activity.Current = contextHolder.Activity; - if (contextHolder.Baggage != null) - { - RuntimeContext.SetValue(BaggageSlotName, contextHolder.Baggage); - } + /// + /// Notifies listeners about an unhandled exception thrown on the . + /// + /// . + /// . + /// . + /// Callback action. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteActivityException(Activity aspNetActivity, HttpContext context, Exception exception, Action onExceptionCallback) + { + Debug.Assert(context != null, "Context is null."); + Debug.Assert(exception != null, "Exception is null."); - AspNetTelemetryEventSource.Log.ActivityRestored(contextHolder.Activity); + if (aspNetActivity != null) + { + try + { + onExceptionCallback?.Invoke(aspNetActivity, context, exception); + } + catch (Exception callbackEx) + { + AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnException", callbackEx); } + + AspNetTelemetryEventSource.Log.ActivityException(aspNetActivity, exception); } + } - internal class ContextHolder + /// + /// It's possible that a request is executed in both native threads and managed threads, + /// in such case Activity.Current will be lost during native thread and managed thread switch. + /// This method is intended to restore the current activity in order to correlate the child + /// activities with the root activity of the request. + /// + /// . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void RestoreContextIfNeeded(HttpContext context) + { + Debug.Assert(context != null, "Context is null."); + + if (context.Items[ContextKey] is ContextHolder contextHolder && Activity.Current != contextHolder.Activity) { - public Activity Activity; - public object Baggage; + Activity.Current = contextHolder.Activity; + if (contextHolder.Baggage != null) + { + RuntimeContext.SetValue(BaggageSlotName, contextHolder.Baggage); + } + + AspNetTelemetryEventSource.Log.ActivityRestored(contextHolder.Activity); } } + + internal class ContextHolder + { + public Activity Activity; + public object Baggage; + } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs index 3fd4e239d0e..78f6cdb02cf 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs @@ -19,104 +19,103 @@ using System.Diagnostics.Tracing; using OpenTelemetry.Internal; -namespace OpenTelemetry.Instrumentation.AspNet +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// ETW EventSource tracing class. +/// +[EventSource(Name = "OpenTelemetry-Instrumentation-AspNet-Telemetry", Guid = "1de158cc-f7ce-4293-bd19-2358c93c8186")] +internal sealed class AspNetTelemetryEventSource : EventSource { /// - /// ETW EventSource tracing class. + /// Instance of the PlatformEventSource class. /// - [EventSource(Name = "OpenTelemetry-Instrumentation-AspNet-Telemetry", Guid = "1de158cc-f7ce-4293-bd19-2358c93c8186")] - internal sealed class AspNetTelemetryEventSource : EventSource - { - /// - /// Instance of the PlatformEventSource class. - /// - public static readonly AspNetTelemetryEventSource Log = new(); + public static readonly AspNetTelemetryEventSource Log = new(); - [NonEvent] - public void ActivityStarted(Activity activity) + [NonEvent] + public void ActivityStarted(Activity activity) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.All)) { - if (this.IsEnabled(EventLevel.Verbose, EventKeywords.All)) - { - this.ActivityStarted(activity?.Id); - } + this.ActivityStarted(activity?.Id); } + } - [NonEvent] - public void ActivityStopped(Activity activity) + [NonEvent] + public void ActivityStopped(Activity activity) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.All)) { - if (this.IsEnabled(EventLevel.Verbose, EventKeywords.All)) - { - this.ActivityStopped(activity?.Id); - } + this.ActivityStopped(activity?.Id); } + } - [NonEvent] - public void ActivityRestored(Activity activity) + [NonEvent] + public void ActivityRestored(Activity activity) + { + if (this.IsEnabled(EventLevel.Informational, EventKeywords.All)) { - if (this.IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - this.ActivityRestored(activity?.Id); - } + this.ActivityRestored(activity?.Id); } + } - [NonEvent] - public void ActivityException(Activity activity, Exception ex) + [NonEvent] + public void ActivityException(Activity activity, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.ActivityException(activity?.Id, ex.ToInvariantString()); - } + this.ActivityException(activity?.Id, ex.ToInvariantString()); } + } - [NonEvent] - public void CallbackException(Activity activity, string eventName, Exception ex) + [NonEvent] + public void CallbackException(Activity activity, string eventName, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.CallbackException(activity?.Id, eventName, ex.ToInvariantString()); - } + this.CallbackException(activity?.Id, eventName, ex.ToInvariantString()); } + } - [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] - public void TraceCallback(string callback) - { - this.WriteEvent(1, callback); - } + [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] + public void TraceCallback(string callback) + { + this.WriteEvent(1, callback); + } - [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] - public void ActivityStarted(string id) - { - this.WriteEvent(2, id); - } + [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] + public void ActivityStarted(string id) + { + this.WriteEvent(2, id); + } - [Event(3, Message = "Activity stopped, Id='{0}'", Level = EventLevel.Verbose)] - public void ActivityStopped(string id) - { - this.WriteEvent(3, id); - } + [Event(3, Message = "Activity stopped, Id='{0}'", Level = EventLevel.Verbose)] + public void ActivityStopped(string id) + { + this.WriteEvent(3, id); + } - [Event(4, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] - public void ActivityRestored(string id) - { - this.WriteEvent(4, id); - } + [Event(4, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] + public void ActivityRestored(string id) + { + this.WriteEvent(4, id); + } - [Event(5, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] - public void OnExecuteRequestStepInvocationError(string error) - { - this.WriteEvent(5, error); - } + [Event(5, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] + public void OnExecuteRequestStepInvocationError(string error) + { + this.WriteEvent(5, error); + } - [Event(6, Message = "Activity exception, Id='{0}': {1}", Level = EventLevel.Error)] - public void ActivityException(string id, string ex) - { - this.WriteEvent(6, id, ex); - } + [Event(6, Message = "Activity exception, Id='{0}': {1}", Level = EventLevel.Error)] + public void ActivityException(string id, string ex) + { + this.WriteEvent(6, id, ex); + } - [Event(7, Message = "Callback exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] - public void CallbackException(string id, string eventName, string ex) - { - this.WriteEvent(7, id, eventName, ex); - } + [Event(7, Message = "Callback exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] + public void CallbackException(string id, string eventName, string ex) + { + this.WriteEvent(7, id, eventName, ex); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs index 395c5aec12a..4df4fb43665 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs @@ -19,129 +19,128 @@ using System.Reflection; using System.Web; -namespace OpenTelemetry.Instrumentation.AspNet +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// Http Module sets ambient state using Activity API from DiagnosticsSource package. +/// +public class TelemetryHttpModule : IHttpModule { /// - /// Http Module sets ambient state using Activity API from DiagnosticsSource package. + /// OpenTelemetry.Instrumentation.AspNet name. /// - public class TelemetryHttpModule : IHttpModule - { - /// - /// OpenTelemetry.Instrumentation.AspNet name. - /// - public const string AspNetSourceName = "OpenTelemetry.Instrumentation.AspNet.Telemetry"; + public const string AspNetSourceName = "OpenTelemetry.Instrumentation.AspNet.Telemetry"; - /// - /// for OpenTelemetry.Instrumentation.AspNet created objects. - /// - public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; + /// + /// for OpenTelemetry.Instrumentation.AspNet created objects. + /// + public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; - // ServerVariable set only on rewritten HttpContext by URL Rewrite module. - private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; + // ServerVariable set only on rewritten HttpContext by URL Rewrite module. + private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; - // ServerVariable set on every request if URL module is registered in HttpModule pipeline. - private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; + // ServerVariable set on every request if URL module is registered in HttpModule pipeline. + private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; - private static readonly MethodInfo OnExecuteRequestStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); + private static readonly MethodInfo OnExecuteRequestStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); - /// - /// Gets the applied to requests processed by the handler. - /// - public static TelemetryHttpModuleOptions Options { get; } = new TelemetryHttpModuleOptions(); + /// + /// Gets the applied to requests processed by the handler. + /// + public static TelemetryHttpModuleOptions Options { get; } = new TelemetryHttpModuleOptions(); - /// - public void Dispose() - { - } + /// + public void Dispose() + { + } - /// - public void Init(HttpApplication context) - { - context.BeginRequest += this.Application_BeginRequest; - context.EndRequest += this.Application_EndRequest; - context.Error += this.Application_Error; + /// + public void Init(HttpApplication context) + { + context.BeginRequest += this.Application_BeginRequest; + context.EndRequest += this.Application_EndRequest; + context.Error += this.Application_Error; - if (HttpRuntime.UsingIntegratedPipeline && OnExecuteRequestStepMethodInfo != null) + if (HttpRuntime.UsingIntegratedPipeline && OnExecuteRequestStepMethodInfo != null) + { + // OnExecuteRequestStep is availabile starting with 4.7.1 + try + { + OnExecuteRequestStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); + } + catch (Exception e) { - // OnExecuteRequestStep is availabile starting with 4.7.1 - try - { - OnExecuteRequestStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); - } - catch (Exception e) - { - AspNetTelemetryEventSource.Log.OnExecuteRequestStepInvocationError(e.Message); - } + AspNetTelemetryEventSource.Log.OnExecuteRequestStepInvocationError(e.Message); } } + } - private void Application_BeginRequest(object sender, EventArgs e) + private void Application_BeginRequest(object sender, EventArgs e) + { + AspNetTelemetryEventSource.Log.TraceCallback("Application_BeginRequest"); + ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, ((HttpApplication)sender).Context, Options.OnRequestStartedCallback); + } + + private void OnExecuteRequestStep(HttpContextBase context, Action step) + { + // Called only on 4.7.1+ runtimes + + if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) { - AspNetTelemetryEventSource.Log.TraceCallback("Application_BeginRequest"); - ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, ((HttpApplication)sender).Context, Options.OnRequestStartedCallback); + ActivityHelper.RestoreContextIfNeeded(context.ApplicationInstance.Context); } - private void OnExecuteRequestStep(HttpContextBase context, Action step) - { - // Called only on 4.7.1+ runtimes + step(); + } - if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) - { - ActivityHelper.RestoreContextIfNeeded(context.ApplicationInstance.Context); - } + private void Application_EndRequest(object sender, EventArgs e) + { + AspNetTelemetryEventSource.Log.TraceCallback("Application_EndRequest"); + bool trackActivity = true; - step(); - } + var context = ((HttpApplication)sender).Context; - private void Application_EndRequest(object sender, EventArgs e) + if (!ActivityHelper.HasStarted(context, out Activity aspNetActivity)) { - AspNetTelemetryEventSource.Log.TraceCallback("Application_EndRequest"); - bool trackActivity = true; - - var context = ((HttpApplication)sender).Context; - - if (!ActivityHelper.HasStarted(context, out Activity aspNetActivity)) + // Rewrite: In case of rewrite, a new request context is created, called the child request, and it goes through the entire IIS/ASP.NET integrated pipeline. + // The child request can be mapped to any of the handlers configured in IIS, and it's execution is no different than it would be if it was received via the HTTP stack. + // The parent request jumps ahead in the pipeline to the end request notification, and waits for the child request to complete. + // When the child request completes, the parent request executes the end request notifications and completes itself. + // Do not create activity for parent request. Parent request has IIS_UrlRewriteModule ServerVariable with success response code. + // Child request contains an additional ServerVariable named - IIS_WasUrlRewritten. + // Track failed response activity: Different modules in the pipeline has ability to end the response. For example, authentication module could set HTTP 401 in OnBeginRequest and end the response. + if (context.Request.ServerVariables != null && context.Request.ServerVariables[URLRewriteRewrittenRequest] == null && context.Request.ServerVariables[URLRewriteModuleVersion] != null && context.Response.StatusCode == 200) { - // Rewrite: In case of rewrite, a new request context is created, called the child request, and it goes through the entire IIS/ASP.NET integrated pipeline. - // The child request can be mapped to any of the handlers configured in IIS, and it's execution is no different than it would be if it was received via the HTTP stack. - // The parent request jumps ahead in the pipeline to the end request notification, and waits for the child request to complete. - // When the child request completes, the parent request executes the end request notifications and completes itself. - // Do not create activity for parent request. Parent request has IIS_UrlRewriteModule ServerVariable with success response code. - // Child request contains an additional ServerVariable named - IIS_WasUrlRewritten. - // Track failed response activity: Different modules in the pipeline has ability to end the response. For example, authentication module could set HTTP 401 in OnBeginRequest and end the response. - if (context.Request.ServerVariables != null && context.Request.ServerVariables[URLRewriteRewrittenRequest] == null && context.Request.ServerVariables[URLRewriteModuleVersion] != null && context.Response.StatusCode == 200) - { - trackActivity = false; - } - else - { - // Activity has never been started - aspNetActivity = ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, context, Options.OnRequestStartedCallback); - } + trackActivity = false; } - - if (trackActivity) + else { - ActivityHelper.StopAspNetActivity(Options.TextMapPropagator, aspNetActivity, context, Options.OnRequestStoppedCallback); + // Activity has never been started + aspNetActivity = ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, context, Options.OnRequestStartedCallback); } } - private void Application_Error(object sender, EventArgs e) + if (trackActivity) { - AspNetTelemetryEventSource.Log.TraceCallback("Application_Error"); + ActivityHelper.StopAspNetActivity(Options.TextMapPropagator, aspNetActivity, context, Options.OnRequestStoppedCallback); + } + } - var context = ((HttpApplication)sender).Context; + private void Application_Error(object sender, EventArgs e) + { + AspNetTelemetryEventSource.Log.TraceCallback("Application_Error"); - var exception = context.Error; - if (exception != null) - { - if (!ActivityHelper.HasStarted(context, out Activity aspNetActivity)) - { - aspNetActivity = ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, context, Options.OnRequestStartedCallback); - } + var context = ((HttpApplication)sender).Context; - ActivityHelper.WriteActivityException(aspNetActivity, context, exception, Options.OnExceptionCallback); + var exception = context.Error; + if (exception != null) + { + if (!ActivityHelper.HasStarted(context, out Activity aspNetActivity)) + { + aspNetActivity = ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, context, Options.OnRequestStartedCallback); } + + ActivityHelper.WriteActivityException(aspNetActivity, context, exception, Options.OnExceptionCallback); } } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs index 2d0e42efb01..00be9abcaef 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs @@ -20,48 +20,47 @@ using OpenTelemetry.Context.Propagation; using OpenTelemetry.Internal; -namespace OpenTelemetry.Instrumentation.AspNet +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// Stores options for the . +/// +public class TelemetryHttpModuleOptions { + private TextMapPropagator textMapPropagator = new TraceContextPropagator(); + + internal TelemetryHttpModuleOptions() + { + } + /// - /// Stores options for the . + /// Gets or sets the to use to + /// extract from incoming requests. /// - public class TelemetryHttpModuleOptions + public TextMapPropagator TextMapPropagator { - private TextMapPropagator textMapPropagator = new TraceContextPropagator(); - - internal TelemetryHttpModuleOptions() - { - } - - /// - /// Gets or sets the to use to - /// extract from incoming requests. - /// - public TextMapPropagator TextMapPropagator + get => this.textMapPropagator; + set { - get => this.textMapPropagator; - set - { - Guard.ThrowIfNull(value); + Guard.ThrowIfNull(value); - this.textMapPropagator = value; - } + this.textMapPropagator = value; } + } - /// - /// Gets or sets a callback action to be fired when a request is started. - /// - public Action OnRequestStartedCallback { get; set; } + /// + /// Gets or sets a callback action to be fired when a request is started. + /// + public Action OnRequestStartedCallback { get; set; } - /// - /// Gets or sets a callback action to be fired when a request is stopped. - /// - public Action OnRequestStoppedCallback { get; set; } + /// + /// Gets or sets a callback action to be fired when a request is stopped. + /// + public Action OnRequestStoppedCallback { get; set; } - /// - /// Gets or sets a callback action to be fired when an unhandled - /// exception is thrown processing a request. - /// - public Action OnExceptionCallback { get; set; } - } + /// + /// Gets or sets a callback action to be fired when an unhandled + /// exception is thrown processing a request. + /// + public Action OnExceptionCallback { get; set; } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs index 2bd2a988be7..fd274fb947a 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs @@ -17,28 +17,27 @@ using System; using OpenTelemetry.Instrumentation.AspNet.Implementation; -namespace OpenTelemetry.Instrumentation.AspNet +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// Asp.Net Requests instrumentation. +/// +internal sealed class AspNetInstrumentation : IDisposable { + private readonly HttpInListener httpInListener; + /// - /// Asp.Net Requests instrumentation. + /// Initializes a new instance of the class. /// - internal sealed class AspNetInstrumentation : IDisposable + /// Configuration options for ASP.NET instrumentation. + public AspNetInstrumentation(AspNetInstrumentationOptions options) { - private readonly HttpInListener httpInListener; - - /// - /// Initializes a new instance of the class. - /// - /// Configuration options for ASP.NET instrumentation. - public AspNetInstrumentation(AspNetInstrumentationOptions options) - { - this.httpInListener = new HttpInListener(options); - } + this.httpInListener = new HttpInListener(options); + } - /// - public void Dispose() - { - this.httpInListener?.Dispose(); - } + /// + public void Dispose() + { + this.httpInListener?.Dispose(); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs index 4209ce23e05..50ded07c2c8 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs @@ -18,46 +18,45 @@ using System.Diagnostics; using System.Web; -namespace OpenTelemetry.Instrumentation.AspNet +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// Options for ASP.NET instrumentation. +/// +public class AspNetInstrumentationOptions { /// - /// Options for ASP.NET instrumentation. + /// Gets or sets a filter callback function that determines on a per + /// request basis whether or not to collect telemetry. /// - public class AspNetInstrumentationOptions - { - /// - /// Gets or sets a filter callback function that determines on a per - /// request basis whether or not to collect telemetry. - /// - /// - /// The filter callback receives the for the - /// current request and should return a boolean. - /// - /// If filter returns the request is - /// collected. - /// If filter returns or throws an - /// exception the request is filtered out (NOT collected). - /// - /// - public Func Filter { get; set; } + /// + /// The filter callback receives the for the + /// current request and should return a boolean. + /// + /// If filter returns the request is + /// collected. + /// If filter returns or throws an + /// exception the request is filtered out (NOT collected). + /// + /// + public Func Filter { get; set; } - /// - /// Gets or sets an action to enrich an Activity. - /// - /// - /// : the activity being enriched. - /// string: the name of the event. - /// object: the raw object from which additional information can be extracted to enrich the activity. - /// The type of this object depends on the event, which is given by the above parameter. - /// - public Action Enrich { get; set; } + /// + /// Gets or sets an action to enrich an Activity. + /// + /// + /// : the activity being enriched. + /// string: the name of the event. + /// object: the raw object from which additional information can be extracted to enrich the activity. + /// The type of this object depends on the event, which is given by the above parameter. + /// + public Action Enrich { get; set; } - /// - /// Gets or sets a value indicating whether the exception will be recorded as ActivityEvent or not. - /// - /// - /// See: . - /// - public bool RecordException { get; set; } - } + /// + /// Gets or sets a value indicating whether the exception will be recorded as ActivityEvent or not. + /// + /// + /// See: . + /// + public bool RecordException { get; set; } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs index 373fe8a5534..53fa1720da6 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs @@ -19,35 +19,34 @@ using System.Reflection; using OpenTelemetry.Instrumentation.AspNet.Implementation; -namespace OpenTelemetry.Instrumentation.AspNet +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// Asp.Net Requests instrumentation. +/// +internal class AspNetMetrics : IDisposable { - /// - /// Asp.Net Requests instrumentation. - /// - internal class AspNetMetrics : IDisposable - { - internal static readonly AssemblyName AssemblyName = typeof(HttpInMetricsListener).Assembly.GetName(); - internal static readonly string InstrumentationName = AssemblyName.Name; - internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString(); + internal static readonly AssemblyName AssemblyName = typeof(HttpInMetricsListener).Assembly.GetName(); + internal static readonly string InstrumentationName = AssemblyName.Name; + internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString(); - private readonly Meter meter; + private readonly Meter meter; - private readonly HttpInMetricsListener httpInMetricsListener; + private readonly HttpInMetricsListener httpInMetricsListener; - /// - /// Initializes a new instance of the class. - /// - public AspNetMetrics() - { - this.meter = new Meter(InstrumentationName, InstrumentationVersion); - this.httpInMetricsListener = new HttpInMetricsListener(this.meter); - } + /// + /// Initializes a new instance of the class. + /// + public AspNetMetrics() + { + this.meter = new Meter(InstrumentationName, InstrumentationVersion); + this.httpInMetricsListener = new HttpInMetricsListener(this.meter); + } - /// - public void Dispose() - { - this.meter?.Dispose(); - this.httpInMetricsListener?.Dispose(); - } + /// + public void Dispose() + { + this.meter?.Dispose(); + this.httpInMetricsListener?.Dispose(); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/AspNetInstrumentationEventSource.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/AspNetInstrumentationEventSource.cs index 58bd374d2a5..9d3d6ab35ca 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/AspNetInstrumentationEventSource.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/AspNetInstrumentationEventSource.cs @@ -18,50 +18,49 @@ using System.Diagnostics.Tracing; using OpenTelemetry.Internal; -namespace OpenTelemetry.Instrumentation.AspNet.Implementation +namespace OpenTelemetry.Instrumentation.AspNet.Implementation; + +/// +/// EventSource events emitted from the project. +/// +[EventSource(Name = "OpenTelemetry-Instrumentation-AspNet")] +internal sealed class AspNetInstrumentationEventSource : EventSource { - /// - /// EventSource events emitted from the project. - /// - [EventSource(Name = "OpenTelemetry-Instrumentation-AspNet")] - internal sealed class AspNetInstrumentationEventSource : EventSource - { - public static AspNetInstrumentationEventSource Log = new(); + public static AspNetInstrumentationEventSource Log = new(); - [NonEvent] - public void RequestFilterException(string operationName, Exception ex) + [NonEvent] + public void RequestFilterException(string operationName, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.RequestFilterException(operationName, ex.ToInvariantString()); - } + this.RequestFilterException(operationName, ex.ToInvariantString()); } + } - [NonEvent] - public void EnrichmentException(string eventName, Exception ex) + [NonEvent] + public void EnrichmentException(string eventName, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.EnrichmentException(eventName, ex.ToInvariantString()); - } + this.EnrichmentException(eventName, ex.ToInvariantString()); } + } - [Event(1, Message = "Request is filtered out and will not be collected. Operation='{0}'", Level = EventLevel.Verbose)] - public void RequestIsFilteredOut(string operationName) - { - this.WriteEvent(1, operationName); - } + [Event(1, Message = "Request is filtered out and will not be collected. Operation='{0}'", Level = EventLevel.Verbose)] + public void RequestIsFilteredOut(string operationName) + { + this.WriteEvent(1, operationName); + } - [Event(2, Message = "Filter callback threw an exception. Request will not be collected. Operation='{0}': {1}", Level = EventLevel.Error)] - public void RequestFilterException(string operationName, string exception) - { - this.WriteEvent(2, operationName, exception); - } + [Event(2, Message = "Filter callback threw an exception. Request will not be collected. Operation='{0}': {1}", Level = EventLevel.Error)] + public void RequestFilterException(string operationName, string exception) + { + this.WriteEvent(2, operationName, exception); + } - [Event(3, Message = "Enrich callback threw an exception. Event='{0}': {1}", Level = EventLevel.Error)] - public void EnrichmentException(string eventName, string exception) - { - this.WriteEvent(3, eventName, exception); - } + [Event(3, Message = "Enrich callback threw an exception. Event='{0}': {1}", Level = EventLevel.Error)] + public void EnrichmentException(string eventName, string exception) + { + this.WriteEvent(3, eventName, exception); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs index 946de293ebf..716b9c01367 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs @@ -22,175 +22,174 @@ using OpenTelemetry.Internal; using OpenTelemetry.Trace; -namespace OpenTelemetry.Instrumentation.AspNet.Implementation +namespace OpenTelemetry.Instrumentation.AspNet.Implementation; + +internal sealed class HttpInListener : IDisposable { - internal sealed class HttpInListener : IDisposable - { - private readonly PropertyFetcher routeFetcher = new("Route"); - private readonly PropertyFetcher routeTemplateFetcher = new("RouteTemplate"); - private readonly AspNetInstrumentationOptions options; + private readonly PropertyFetcher routeFetcher = new("Route"); + private readonly PropertyFetcher routeTemplateFetcher = new("RouteTemplate"); + private readonly AspNetInstrumentationOptions options; - public HttpInListener(AspNetInstrumentationOptions options) - { - Guard.ThrowIfNull(options); + public HttpInListener(AspNetInstrumentationOptions options) + { + Guard.ThrowIfNull(options); - this.options = options; + this.options = options; - TelemetryHttpModule.Options.TextMapPropagator = Propagators.DefaultTextMapPropagator; + TelemetryHttpModule.Options.TextMapPropagator = Propagators.DefaultTextMapPropagator; - TelemetryHttpModule.Options.OnRequestStartedCallback += this.OnStartActivity; - TelemetryHttpModule.Options.OnRequestStoppedCallback += this.OnStopActivity; - TelemetryHttpModule.Options.OnExceptionCallback += this.OnException; - } + TelemetryHttpModule.Options.OnRequestStartedCallback += this.OnStartActivity; + TelemetryHttpModule.Options.OnRequestStoppedCallback += this.OnStopActivity; + TelemetryHttpModule.Options.OnExceptionCallback += this.OnException; + } - public void Dispose() - { - TelemetryHttpModule.Options.OnRequestStartedCallback -= this.OnStartActivity; - TelemetryHttpModule.Options.OnRequestStoppedCallback -= this.OnStopActivity; - TelemetryHttpModule.Options.OnExceptionCallback -= this.OnException; - } + public void Dispose() + { + TelemetryHttpModule.Options.OnRequestStartedCallback -= this.OnStartActivity; + TelemetryHttpModule.Options.OnRequestStoppedCallback -= this.OnStopActivity; + TelemetryHttpModule.Options.OnExceptionCallback -= this.OnException; + } - /// - /// Gets the OpenTelemetry standard uri tag value for a span based on its request . - /// - /// . - /// Span uri value. - private static string GetUriTagValueFromRequestUri(Uri uri) - { - return string.IsNullOrEmpty(uri.UserInfo) ? uri.ToString() : string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment); - } + /// + /// Gets the OpenTelemetry standard uri tag value for a span based on its request . + /// + /// . + /// Span uri value. + private static string GetUriTagValueFromRequestUri(Uri uri) + { + return string.IsNullOrEmpty(uri.UserInfo) ? uri.ToString() : string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment); + } - private void OnStartActivity(Activity activity, HttpContext context) + private void OnStartActivity(Activity activity, HttpContext context) + { + if (activity.IsAllDataRequested) { - if (activity.IsAllDataRequested) + try { - try - { - // todo: Ideally we would also check - // Sdk.SuppressInstrumentation here to prevent tagging a - // span that will not be collected but we can't do that - // without an SDK reference. Need the spec to come around on - // this. - - if (this.options.Filter?.Invoke(context) == false) - { - AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); - activity.IsAllDataRequested = false; - activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; - return; - } - } - catch (Exception ex) + // todo: Ideally we would also check + // Sdk.SuppressInstrumentation here to prevent tagging a + // span that will not be collected but we can't do that + // without an SDK reference. Need the spec to come around on + // this. + + if (this.options.Filter?.Invoke(context) == false) { - AspNetInstrumentationEventSource.Log.RequestFilterException(activity.OperationName, ex); + AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); activity.IsAllDataRequested = false; activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; return; } + } + catch (Exception ex) + { + AspNetInstrumentationEventSource.Log.RequestFilterException(activity.OperationName, ex); + activity.IsAllDataRequested = false; + activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; + return; + } - var request = context.Request; - var requestValues = request.Unvalidated; + var request = context.Request; + var requestValues = request.Unvalidated; - // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md - var path = requestValues.Path; - activity.DisplayName = path; + // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md + var path = requestValues.Path; + activity.DisplayName = path; - 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); - } + 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(SemanticConventions.AttributeHttpTarget, path); - activity.SetTag(SemanticConventions.AttributeHttpUserAgent, request.UserAgent); - activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUriTagValueFromRequestUri(request.Url)); + activity.SetTag(SemanticConventions.AttributeHttpMethod, request.HttpMethod); + activity.SetTag(SemanticConventions.AttributeHttpTarget, path); + activity.SetTag(SemanticConventions.AttributeHttpUserAgent, request.UserAgent); + activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUriTagValueFromRequestUri(request.Url)); - try - { - this.options.Enrich?.Invoke(activity, "OnStartActivity", request); - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.EnrichmentException("OnStartActivity", ex); - } + try + { + this.options.Enrich?.Invoke(activity, "OnStartActivity", request); + } + catch (Exception ex) + { + AspNetInstrumentationEventSource.Log.EnrichmentException("OnStartActivity", ex); } } + } - private void OnStopActivity(Activity activity, HttpContext context) + private void OnStopActivity(Activity activity, HttpContext context) + { + if (activity.IsAllDataRequested) { - if (activity.IsAllDataRequested) - { - var response = context.Response; - - activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); + var response = context.Response; - if (activity.GetStatus().StatusCode == StatusCode.Unset) - { - activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, response.StatusCode)); - } + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); - var routeData = context.Request.RequestContext.RouteData; + if (activity.GetStatus().StatusCode == StatusCode.Unset) + { + activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, response.StatusCode)); + } - 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. + var routeData = context.Request.RequestContext.RouteData; - if (msSubRoutes is Array attributeRouting && attributeRouting.Length == 1) - { - var subRouteData = attributeRouting.GetValue(0); + 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. - _ = this.routeFetcher.TryFetch(subRouteData, out var route); - _ = this.routeTemplateFetcher.TryFetch(route, out template); - } - } - else if (routeData.Route is Route route) + if (msSubRoutes is Array attributeRouting && attributeRouting.Length == 1) { - // MVC + WebAPI traditional routing & MVC attribute routing flow here. - template = route.Url; - } + var subRouteData = attributeRouting.GetValue(0); - if (!string.IsNullOrEmpty(template)) - { - // Override the name that was previously set to the path part of URL. - activity.DisplayName = template; - activity.SetTag(SemanticConventions.AttributeHttpRoute, template); + _ = this.routeFetcher.TryFetch(subRouteData, out var route); + _ = this.routeTemplateFetcher.TryFetch(route, out template); } + } + else if (routeData.Route is Route route) + { + // MVC + WebAPI traditional routing & MVC attribute routing flow here. + template = route.Url; + } - try - { - this.options.Enrich?.Invoke(activity, "OnStopActivity", response); - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.EnrichmentException("OnStopActivity", ex); - } + if (!string.IsNullOrEmpty(template)) + { + // Override the name that was previously set to the path part of URL. + activity.DisplayName = template; + activity.SetTag(SemanticConventions.AttributeHttpRoute, template); + } + + try + { + this.options.Enrich?.Invoke(activity, "OnStopActivity", response); + } + catch (Exception ex) + { + AspNetInstrumentationEventSource.Log.EnrichmentException("OnStopActivity", ex); } } + } - private void OnException(Activity activity, HttpContext context, Exception exception) + private void OnException(Activity activity, HttpContext context, Exception exception) + { + if (activity.IsAllDataRequested) { - if (activity.IsAllDataRequested) + if (this.options.RecordException) { - if (this.options.RecordException) - { - activity.RecordException(exception); - } + activity.RecordException(exception); + } - activity.SetStatus(Status.Error.WithDescription(exception.Message)); + activity.SetStatus(Status.Error.WithDescription(exception.Message)); - try - { - this.options.Enrich?.Invoke(activity, "OnException", exception); - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.EnrichmentException("OnException", ex); - } + try + { + this.options.Enrich?.Invoke(activity, "OnException", exception); + } + catch (Exception ex) + { + AspNetInstrumentationEventSource.Log.EnrichmentException("OnException", ex); } } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInMetricsListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInMetricsListener.cs index ece456273ce..aa88a788987 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInMetricsListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInMetricsListener.cs @@ -20,35 +20,34 @@ using System.Web; using OpenTelemetry.Trace; -namespace OpenTelemetry.Instrumentation.AspNet.Implementation +namespace OpenTelemetry.Instrumentation.AspNet.Implementation; + +internal sealed class HttpInMetricsListener : IDisposable { - internal sealed class HttpInMetricsListener : IDisposable - { - private readonly Histogram httpServerDuration; + private readonly Histogram httpServerDuration; - public HttpInMetricsListener(Meter meter) - { - this.httpServerDuration = meter.CreateHistogram("http.server.duration", "ms", "measures the duration of the inbound HTTP request"); - TelemetryHttpModule.Options.OnRequestStoppedCallback += this.OnStopActivity; - } + public HttpInMetricsListener(Meter meter) + { + this.httpServerDuration = meter.CreateHistogram("http.server.duration", "ms", "measures the duration of the inbound HTTP request"); + TelemetryHttpModule.Options.OnRequestStoppedCallback += this.OnStopActivity; + } - public void Dispose() - { - TelemetryHttpModule.Options.OnRequestStoppedCallback -= this.OnStopActivity; - } + public void Dispose() + { + TelemetryHttpModule.Options.OnRequestStoppedCallback -= this.OnStopActivity; + } - private void OnStopActivity(Activity activity, HttpContext context) + private void OnStopActivity(Activity activity, HttpContext context) + { + // TODO: This is just a minimal set of attributes. See the spec for additional attributes: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server + var tags = new TagList { - // TODO: This is just a minimal set of attributes. See the spec for additional attributes: - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server - var tags = new TagList - { - { SemanticConventions.AttributeHttpMethod, context.Request.HttpMethod }, - { SemanticConventions.AttributeHttpScheme, context.Request.Url.Scheme }, - { SemanticConventions.AttributeHttpStatusCode, context.Response.StatusCode }, - }; + { SemanticConventions.AttributeHttpMethod, context.Request.HttpMethod }, + { SemanticConventions.AttributeHttpScheme, context.Request.Url.Scheme }, + { SemanticConventions.AttributeHttpStatusCode, context.Response.StatusCode }, + }; - this.httpServerDuration.Record(activity.Duration.TotalMilliseconds, tags); - } + this.httpServerDuration.Record(activity.Duration.TotalMilliseconds, tags); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNet/MeterProviderBuilderExtensions.cs index e92740546af..ee698391713 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/MeterProviderBuilderExtensions.cs @@ -17,26 +17,25 @@ using OpenTelemetry.Instrumentation.AspNet; using OpenTelemetry.Internal; -namespace OpenTelemetry.Metrics +namespace OpenTelemetry.Metrics; + +/// +/// Extension methods to simplify registering of ASP.NET request instrumentation. +/// +public static class MeterProviderBuilderExtensions { /// - /// Extension methods to simplify registering of ASP.NET request instrumentation. + /// Enables the incoming requests automatic data collection for ASP.NET. /// - public static class MeterProviderBuilderExtensions + /// being configured. + /// The instance of to chain the calls. + public static MeterProviderBuilder AddAspNetInstrumentation( + this MeterProviderBuilder builder) { - /// - /// Enables the incoming requests automatic data collection for ASP.NET. - /// - /// being configured. - /// The instance of to chain the calls. - public static MeterProviderBuilder AddAspNetInstrumentation( - this MeterProviderBuilder builder) - { - Guard.ThrowIfNull(builder); + Guard.ThrowIfNull(builder); - var instrumentation = new AspNetMetrics(); - builder.AddMeter(AspNetMetrics.InstrumentationName); - return builder.AddInstrumentation(() => instrumentation); - } + var instrumentation = new AspNetMetrics(); + builder.AddMeter(AspNetMetrics.InstrumentationName); + return builder.AddInstrumentation(() => instrumentation); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNet/TracerProviderBuilderExtensions.cs index b58aeb83b88..e5cbbc5d2ff 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/TracerProviderBuilderExtensions.cs @@ -18,32 +18,31 @@ using OpenTelemetry.Instrumentation.AspNet; using OpenTelemetry.Internal; -namespace OpenTelemetry.Trace +namespace OpenTelemetry.Trace; + +/// +/// Extension methods to simplify registering of ASP.NET request instrumentation. +/// +public static class TracerProviderBuilderExtensions { /// - /// Extension methods to simplify registering of ASP.NET request instrumentation. + /// Enables the incoming requests automatic data collection for ASP.NET. /// - public static class TracerProviderBuilderExtensions + /// being configured. + /// ASP.NET Request configuration options. + /// The instance of to chain the calls. + public static TracerProviderBuilder AddAspNetInstrumentation( + this TracerProviderBuilder builder, + Action configureAspNetInstrumentationOptions = null) { - /// - /// Enables the incoming requests automatic data collection for ASP.NET. - /// - /// being configured. - /// ASP.NET Request configuration options. - /// The instance of to chain the calls. - public static TracerProviderBuilder AddAspNetInstrumentation( - this TracerProviderBuilder builder, - Action configureAspNetInstrumentationOptions = null) - { - Guard.ThrowIfNull(builder); + Guard.ThrowIfNull(builder); - var aspnetOptions = new AspNetInstrumentationOptions(); - configureAspNetInstrumentationOptions?.Invoke(aspnetOptions); + var aspnetOptions = new AspNetInstrumentationOptions(); + configureAspNetInstrumentationOptions?.Invoke(aspnetOptions); - builder.AddInstrumentation(() => new AspNetInstrumentation(aspnetOptions)); - builder.AddSource(TelemetryHttpModule.AspNetSourceName); + builder.AddInstrumentation(() => new AspNetInstrumentation(aspnetOptions)); + builder.AddSource(TelemetryHttpModule.AspNetSourceName); - return builder; - } + return builder; } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs index b8242fffa52..130614390ca 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs @@ -25,522 +25,521 @@ using OpenTelemetry.Context.Propagation; using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +public class ActivityHelperTest : IDisposable { - public class ActivityHelperTest : IDisposable + private const string TraceParentHeaderName = "traceparent"; + private const string TraceStateHeaderName = "tracestate"; + private const string BaggageHeaderName = "baggage"; + private const string BaggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; + private const string TestActivityName = "Activity.Test"; + private readonly TextMapPropagator noopTextMapPropagator = new NoopTextMapPropagator(); + private ActivityListener activitySourceListener; + + public void Dispose() { - private const string TraceParentHeaderName = "traceparent"; - private const string TraceStateHeaderName = "tracestate"; - private const string BaggageHeaderName = "baggage"; - private const string BaggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; - private const string TestActivityName = "Activity.Test"; - private readonly TextMapPropagator noopTextMapPropagator = new NoopTextMapPropagator(); - private ActivityListener activitySourceListener; - - public void Dispose() - { - this.activitySourceListener?.Dispose(); - } + this.activitySourceListener?.Dispose(); + } - [Fact] - public void Has_Started_Returns_Correctly() - { - var context = HttpContextHelper.GetFakeHttpContext(); + [Fact] + public void Has_Started_Returns_Correctly() + { + var context = HttpContextHelper.GetFakeHttpContext(); - bool result = ActivityHelper.HasStarted(context, out Activity aspNetActivity); + bool result = ActivityHelper.HasStarted(context, out Activity aspNetActivity); - Assert.False(result); - Assert.Null(aspNetActivity); + Assert.False(result); + Assert.Null(aspNetActivity); - context.Items[ActivityHelper.ContextKey] = ActivityHelper.StartedButNotSampledObj; + context.Items[ActivityHelper.ContextKey] = ActivityHelper.StartedButNotSampledObj; - result = ActivityHelper.HasStarted(context, out aspNetActivity); + result = ActivityHelper.HasStarted(context, out aspNetActivity); - Assert.True(result); - Assert.Null(aspNetActivity); + Assert.True(result); + Assert.Null(aspNetActivity); - Activity activity = new Activity(TestActivityName); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = activity }; + Activity activity = new Activity(TestActivityName); + context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = activity }; - result = ActivityHelper.HasStarted(context, out aspNetActivity); + result = ActivityHelper.HasStarted(context, out aspNetActivity); - Assert.True(result); - Assert.NotNull(aspNetActivity); - Assert.Equal(activity, aspNetActivity); - } + Assert.True(result); + Assert.NotNull(aspNetActivity); + Assert.Equal(activity, aspNetActivity); + } - [Fact] - public async Task Can_Restore_Activity() + [Fact] + public async Task Can_Restore_Activity() + { + this.EnableListener(); + var context = HttpContextHelper.GetFakeHttpContext(); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Task testTask; + using (ExecutionContext.SuppressFlow()) { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Task testTask; - using (ExecutionContext.SuppressFlow()) + testTask = Task.Run(() => { - testTask = Task.Run(() => - { - Task.Yield(); - - Assert.Null(Activity.Current); + Task.Yield(); - ActivityHelper.RestoreContextIfNeeded(context); + Assert.Null(Activity.Current); - Assert.Same(Activity.Current, rootActivity); - }); - } + ActivityHelper.RestoreContextIfNeeded(context); - await testTask.ConfigureAwait(false); + Assert.Same(Activity.Current, rootActivity); + }); } - [Fact(Skip = "Temporarily disable until stable.")] - public async Task Can_Restore_Baggage() - { - this.EnableListener(); + await testTask.ConfigureAwait(false); + } - var requestHeaders = new Dictionary - { - { BaggageHeaderName, BaggageInHeader }, - }; + [Fact(Skip = "Temporarily disable until stable.")] + public async Task Can_Restore_Baggage() + { + this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator() }), context, null); + var requestHeaders = new Dictionary + { + { BaggageHeaderName, BaggageInHeader }, + }; - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + using var rootActivity = ActivityHelper.StartAspNetActivity(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator() }), context, null); - Task testTask; - using (ExecutionContext.SuppressFlow()) - { - testTask = Task.Run(() => - { - Task.Yield(); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); - Assert.Null(Activity.Current); - Assert.Equal(0, Baggage.Current.Count); + Task testTask; + using (ExecutionContext.SuppressFlow()) + { + testTask = Task.Run(() => + { + Task.Yield(); - ActivityHelper.RestoreContextIfNeeded(context); + Assert.Null(Activity.Current); + Assert.Equal(0, Baggage.Current.Count); - Assert.Same(Activity.Current, rootActivity); - Assert.Empty(rootActivity.Baggage); + ActivityHelper.RestoreContextIfNeeded(context); - Assert.Equal(2, Baggage.Current.Count); - Assert.Equal("789", Baggage.Current.GetBaggage("TestKey1")); - Assert.Equal("456", Baggage.Current.GetBaggage("TestKey2")); - }); - } + Assert.Same(Activity.Current, rootActivity); + Assert.Empty(rootActivity.Baggage); - await testTask.ConfigureAwait(false); + Assert.Equal(2, Baggage.Current.Count); + Assert.Equal("789", Baggage.Current.GetBaggage("TestKey1")); + Assert.Equal("456", Baggage.Current.GetBaggage("TestKey2")); + }); } - [Fact] - public void Can_Stop_Lost_Activity() - { - this.EnableListener(a => - { - Assert.NotNull(Activity.Current); - Assert.Equal(Activity.Current, a); - Assert.Equal(TelemetryHttpModule.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); + await testTask.ConfigureAwait(false); + } - Activity.Current = null; + [Fact] + public void Can_Stop_Lost_Activity() + { + this.EnableListener(a => + { + Assert.NotNull(Activity.Current); + Assert.Equal(Activity.Current, a); + Assert.Equal(TelemetryHttpModule.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + } - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } + [Fact] + public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() + { + this.EnableListener(); + ActivityHelper.RestoreContextIfNeeded(HttpContextHelper.GetFakeHttpContext()); - [Fact] - public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() - { - this.EnableListener(); - ActivityHelper.RestoreContextIfNeeded(HttpContextHelper.GetFakeHttpContext()); + Assert.Null(Activity.Current); + } - Assert.Null(Activity.Current); - } + [Fact] + public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() + { + this.EnableListener(); + var root = new Activity("root").Start(); - [Fact] - public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() - { - this.EnableListener(); - var root = new Activity("root").Start(); + var context = HttpContextHelper.GetFakeHttpContext(); + context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = root }; - var context = HttpContextHelper.GetFakeHttpContext(); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = root }; + ActivityHelper.RestoreContextIfNeeded(context); - ActivityHelper.RestoreContextIfNeeded(context); + Assert.Equal(root, Activity.Current); + } - Assert.Equal(root, Activity.Current); - } + [Fact] + public void Can_Stop_Activity_Without_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = new Activity(TestActivityName); + rootActivity.Start(); + context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = rootActivity }; + Thread.Sleep(100); + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + } - [Fact] - public void Can_Stop_Activity_Without_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = new Activity(TestActivityName); - rootActivity.Start(); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = rootActivity }; - Thread.Sleep(100); - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } + [Fact] + public void Can_Stop_Activity_With_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = new Activity(TestActivityName); + rootActivity.Start(); + context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = rootActivity }; + Thread.Sleep(100); + this.EnableListener(); + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + } - [Fact] - public void Can_Stop_Activity_With_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = new Activity(TestActivityName); - rootActivity.Start(); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = rootActivity }; - Thread.Sleep(100); - this.EnableListener(); - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } + [Fact] + public void Can_Stop_Root_Activity_With_All_Children() + { + this.EnableListener(); + var context = HttpContextHelper.GetFakeHttpContext(); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - [Fact] - public void Can_Stop_Root_Activity_With_All_Children() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + var child = new Activity("child").Start(); + new Activity("grandchild").Start(); - var child = new Activity("child").Start(); - new Activity("grandchild").Start(); + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + } - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } + [Fact] + public void Can_Stop_Root_While_Child_Is_Current() + { + this.EnableListener(); + var context = HttpContextHelper.GetFakeHttpContext(); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + var child = new Activity("child").Start(); - [Fact] - public void Can_Stop_Root_While_Child_Is_Current() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - var child = new Activity("child").Start(); + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + Assert.True(child.Duration == TimeSpan.Zero); + Assert.NotNull(Activity.Current); + Assert.Equal(Activity.Current, child); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + } - Assert.True(child.Duration == TimeSpan.Zero); - Assert.NotNull(Activity.Current); - Assert.Equal(Activity.Current, child); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } + [Fact] + public async Task Can_Stop_Root_Activity_If_It_Is_Broken() + { + this.EnableListener(); + var context = HttpContextHelper.GetFakeHttpContext(); + using var root = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + new Activity("child").Start(); - [Fact] - public async Task Can_Stop_Root_Activity_If_It_Is_Broken() + for (int i = 0; i < 2; i++) { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var root = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - new Activity("child").Start(); - - for (int i = 0; i < 2; i++) + await Task.Run(() => { - await Task.Run(() => - { - // when we enter this method, Current is 'child' activity - Activity.Current.Stop(); - - // here Current is 'parent', but only in this execution context - }); - } - - // when we return back here, in the 'parent' execution context - // Current is still 'child' activity - changes in child context (inside Task.Run) - // do not affect 'parent' context in which Task.Run is called. - // But 'child' Activity is stopped, thus consequent calls to Stop will - // not update Current - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, root, context, null); - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - Assert.Null(Activity.Current); - } + // when we enter this method, Current is 'child' activity + Activity.Current.Stop(); - [Fact] - public void Stop_Root_Activity_With_129_Nesting_Depth() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var root = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + // here Current is 'parent', but only in this execution context + }); + } - for (int i = 0; i < 129; i++) - { - new Activity("child" + i).Start(); - } + // when we return back here, in the 'parent' execution context + // Current is still 'child' activity - changes in child context (inside Task.Run) + // do not affect 'parent' context in which Task.Run is called. + // But 'child' Activity is stopped, thus consequent calls to Stop will + // not update Current + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, root, context, null); + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + Assert.Null(Activity.Current); + } - // can stop any activity regardless of the stack depth - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, root, context, null); + [Fact] + public void Stop_Root_Activity_With_129_Nesting_Depth() + { + this.EnableListener(); + var context = HttpContextHelper.GetFakeHttpContext(); + using var root = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - Assert.NotNull(Activity.Current); + for (int i = 0; i < 129; i++) + { + new Activity("child" + i).Start(); } - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + // can stop any activity regardless of the stack depth + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, root, context, null); - Assert.Null(rootActivity); - Assert.Equal(ActivityHelper.StartedButNotSampledObj, context.Items[ActivityHelper.ContextKey]); + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + Assert.NotNull(Activity.Current); + } - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableListener(onSample: (context) => ActivitySamplingResult.None); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + Assert.Null(rootActivity); + Assert.Equal(ActivityHelper.StartedButNotSampledObj, context.Items[ActivityHelper.ContextKey]); - Assert.Null(rootActivity); - Assert.Equal(ActivityHelper.StartedButNotSampledObj, context.Items[ActivityHelper.ContextKey]); + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + } - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableListener(onSample: (context) => ActivitySamplingResult.None); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + + Assert.Null(rootActivity); + Assert.Equal(ActivityHelper.StartedButNotSampledObj, context.Items[ActivityHelper.ContextKey]); + + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + Assert.Null(context.Items[ActivityHelper.ContextKey]); + } - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent() + [Fact] + public void Can_Create_RootActivity_From_W3C_Traceparent() + { + this.EnableListener(); + var requestHeaders = new Dictionary { - this.EnableListener(); - var requestHeaders = new Dictionary - { - { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; + { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new TraceContextPropagator(), context, null); + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + using var rootActivity = ActivityHelper.StartAspNetActivity(new TraceContextPropagator(), context, null); - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); // note: We're not using a parent-based sampler in this test so the recorded flag of traceparent is ignored. + Assert.NotNull(rootActivity); + Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); + Assert.True(rootActivity.Recorded); // note: We're not using a parent-based sampler in this test so the recorded flag of traceparent is ignored. - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); + Assert.Null(rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); - Assert.Equal(0, Baggage.Current.Count); - } + Assert.Equal(0, Baggage.Current.Count); + } - [Fact] - public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() + [Fact] + public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() + { + this.EnableListener(); + var requestHeaders = new Dictionary { - this.EnableListener(); - var requestHeaders = new Dictionary - { - { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { TraceStateHeaderName, "ts1=v1,ts2=v2" }, - }; + { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { TraceStateHeaderName, "ts1=v1,ts2=v2" }, + }; - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new TraceContextPropagator(), context, null); + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + using var rootActivity = ActivityHelper.StartAspNetActivity(new TraceContextPropagator(), context, null); - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); + Assert.NotNull(rootActivity); + Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01", rootActivity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); + Assert.True(rootActivity.Recorded); - Assert.Equal("ts1=v1,ts2=v2", rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); + Assert.Equal("ts1=v1,ts2=v2", rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); - Assert.Equal(0, Baggage.Current.Count); - } + Assert.Equal(0, Baggage.Current.Count); + } - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent_With_Baggage() + [Fact] + public void Can_Create_RootActivity_From_W3C_Traceparent_With_Baggage() + { + this.EnableListener(); + var requestHeaders = new Dictionary { - this.EnableListener(); - var requestHeaders = new Dictionary - { - { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - { BaggageHeaderName, BaggageInHeader }, - }; + { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + { BaggageHeaderName, BaggageInHeader }, + }; - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator() }), context, null); + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + using var rootActivity = ActivityHelper.StartAspNetActivity(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator() }), context, null); - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); // note: We're not using a parent-based sampler in this test so the recorded flag of traceparent is ignored. + Assert.NotNull(rootActivity); + Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); + Assert.True(rootActivity.Recorded); // note: We're not using a parent-based sampler in this test so the recorded flag of traceparent is ignored. - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); + Assert.Null(rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); - Assert.Equal(2, Baggage.Current.Count); - Assert.Equal("789", Baggage.Current.GetBaggage("TestKey1")); - Assert.Equal("456", Baggage.Current.GetBaggage("TestKey2")); + Assert.Equal(2, Baggage.Current.Count); + Assert.Equal("789", Baggage.Current.GetBaggage("TestKey1")); + Assert.Equal("456", Baggage.Current.GetBaggage("TestKey2")); - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); + ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - Assert.Equal(0, Baggage.Current.Count); - } + Assert.Equal(0, Baggage.Current.Count); + } - [Fact] - public void Can_Create_RootActivity_And_Start_Activity() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + [Fact] + public void Can_Create_RootActivity_And_Start_Activity() + { + this.EnableListener(); + var context = HttpContextHelper.GetFakeHttpContext(); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - Assert.NotNull(rootActivity); - Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); - } + Assert.NotNull(rootActivity); + Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); + } - [Fact] - public void Can_Create_RootActivity_And_Saved_In_HttContext() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); + [Fact] + public void Can_Create_RootActivity_And_Saved_In_HttContext() + { + this.EnableListener(); + var context = HttpContextHelper.GetFakeHttpContext(); + using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - Assert.NotNull(rootActivity); - Assert.Same(rootActivity, ((ActivityHelper.ContextHolder)context.Items[ActivityHelper.ContextKey])?.Activity); - } + Assert.NotNull(rootActivity); + Assert.Same(rootActivity, ((ActivityHelper.ContextHolder)context.Items[ActivityHelper.ContextKey])?.Activity); + } - [Fact] - public void Fire_Exception_Events() - { - int callbacksFired = 0; + [Fact] + public void Fire_Exception_Events() + { + int callbacksFired = 0; - var context = HttpContextHelper.GetFakeHttpContext(); + var context = HttpContextHelper.GetFakeHttpContext(); - Activity activity = new Activity(TestActivityName); + Activity activity = new Activity(TestActivityName); - ActivityHelper.WriteActivityException(activity, context, new InvalidOperationException(), (a, c, e) => { callbacksFired++; }); + ActivityHelper.WriteActivityException(activity, context, new InvalidOperationException(), (a, c, e) => { callbacksFired++; }); - ActivityHelper.WriteActivityException(null, context, new InvalidOperationException(), (a, c, e) => { callbacksFired++; }); + ActivityHelper.WriteActivityException(null, context, new InvalidOperationException(), (a, c, e) => { callbacksFired++; }); - // Callback should fire only for non-null activity - Assert.Equal(1, callbacksFired); - } + // Callback should fire only for non-null activity + Assert.Equal(1, callbacksFired); + } - private void EnableListener(Action onStarted = null, Action onStopped = null, Func onSample = null) - { - Debug.Assert(this.activitySourceListener == null, "Cannot attach multiple listeners in tests."); + private void EnableListener(Action onStarted = null, Action onStopped = null, Func onSample = null) + { + Debug.Assert(this.activitySourceListener == null, "Cannot attach multiple listeners in tests."); - this.activitySourceListener = new ActivityListener + this.activitySourceListener = new ActivityListener + { + ShouldListenTo = (activitySource) => activitySource.Name == TelemetryHttpModule.AspNetSourceName, + ActivityStarted = (a) => onStarted?.Invoke(a), + ActivityStopped = (a) => onStopped?.Invoke(a), + Sample = (ref ActivityCreationOptions options) => { - ShouldListenTo = (activitySource) => activitySource.Name == TelemetryHttpModule.AspNetSourceName, - ActivityStarted = (a) => onStarted?.Invoke(a), - ActivityStopped = (a) => onStopped?.Invoke(a), - Sample = (ref ActivityCreationOptions options) => + if (onSample != null) { - if (onSample != null) - { - return onSample(options.Parent); - } + return onSample(options.Parent); + } - return ActivitySamplingResult.AllDataAndRecorded; - }, - }; + return ActivitySamplingResult.AllDataAndRecorded; + }, + }; - ActivitySource.AddActivityListener(this.activitySourceListener); - } + ActivitySource.AddActivityListener(this.activitySourceListener); + } - private class TestHttpRequest : HttpRequestBase - { - private readonly NameValueCollection headers = new(); + private class TestHttpRequest : HttpRequestBase + { + private readonly NameValueCollection headers = new(); - public override NameValueCollection Headers => this.headers; + public override NameValueCollection Headers => this.headers; - public override UnvalidatedRequestValuesBase Unvalidated => new TestUnvalidatedRequestValues(this.headers); - } + public override UnvalidatedRequestValuesBase Unvalidated => new TestUnvalidatedRequestValues(this.headers); + } - private class TestUnvalidatedRequestValues : UnvalidatedRequestValuesBase + private class TestUnvalidatedRequestValues : UnvalidatedRequestValuesBase + { + public TestUnvalidatedRequestValues(NameValueCollection headers) { - public TestUnvalidatedRequestValues(NameValueCollection headers) - { - this.Headers = headers; - } - - public override NameValueCollection Headers { get; } + this.Headers = headers; } - private class TestHttpResponse : HttpResponseBase + public override NameValueCollection Headers { get; } + } + + private class TestHttpResponse : HttpResponseBase + { + } + + private class TestHttpServerUtility : HttpServerUtilityBase + { + private readonly HttpContextBase context; + + public TestHttpServerUtility(HttpContextBase context) { + this.context = context; } - private class TestHttpServerUtility : HttpServerUtilityBase + public override Exception GetLastError() { - private readonly HttpContextBase context; + return this.context.Error; + } + } - public TestHttpServerUtility(HttpContextBase context) - { - this.context = context; - } + private class TestHttpContext : HttpContextBase + { + private readonly Hashtable items; - public override Exception GetLastError() - { - return this.context.Error; - } + public TestHttpContext(Exception error = null) + { + this.Server = new TestHttpServerUtility(this); + this.items = new Hashtable(); + this.Error = error; } - private class TestHttpContext : HttpContextBase - { - private readonly Hashtable items; + public override HttpRequestBase Request { get; } = new TestHttpRequest(); - public TestHttpContext(Exception error = null) - { - this.Server = new TestHttpServerUtility(this); - this.items = new Hashtable(); - this.Error = error; - } + /// + public override IDictionary Items => this.items; - public override HttpRequestBase Request { get; } = new TestHttpRequest(); + public override Exception Error { get; } - /// - public override IDictionary Items => this.items; + public override HttpServerUtilityBase Server { get; } + } - public override Exception Error { get; } + private class NoopTextMapPropagator : TextMapPropagator + { + private static readonly PropagationContext DefaultPropagationContext = default; - public override HttpServerUtilityBase Server { get; } - } + public override ISet Fields => null; - private class NoopTextMapPropagator : TextMapPropagator + public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) { - private static readonly PropagationContext DefaultPropagationContext = default; - - public override ISet Fields => null; - - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) - { - return DefaultPropagationContext; - } + return DefaultPropagationContext; + } - public override void Inject(PropagationContext context, T carrier, Action setter) - { - } + public override void Inject(PropagationContext context, T carrier, Action setter) + { } } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs index e9920ef16bd..dd1864609b8 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs @@ -21,83 +21,82 @@ using System.Web; using System.Web.Hosting; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +internal class HttpContextHelper { - internal class HttpContextHelper + public static HttpContext GetFakeHttpContext(string page = "/page", string query = "", IDictionary headers = null) { - public static HttpContext GetFakeHttpContext(string page = "/page", string query = "", IDictionary headers = null) - { - Thread.GetDomain().SetData(".appPath", string.Empty); - Thread.GetDomain().SetData(".appVPath", string.Empty); + Thread.GetDomain().SetData(".appPath", string.Empty); + Thread.GetDomain().SetData(".appVPath", string.Empty); - var workerRequest = new SimpleWorkerRequestWithHeaders(page, query, new StringWriter(CultureInfo.InvariantCulture), headers); - var context = new HttpContext(workerRequest); - HttpContext.Current = context; - return context; - } + var workerRequest = new SimpleWorkerRequestWithHeaders(page, query, new StringWriter(CultureInfo.InvariantCulture), headers); + var context = new HttpContext(workerRequest); + HttpContext.Current = context; + return context; + } + + public static HttpContextBase GetFakeHttpContextBase(string page = "/page", string query = "", IDictionary headers = null) + { + var context = GetFakeHttpContext(page, query, headers); + return new HttpContextWrapper(context); + } - public static HttpContextBase GetFakeHttpContextBase(string page = "/page", string query = "", IDictionary headers = null) + private class SimpleWorkerRequestWithHeaders : SimpleWorkerRequest + { + private readonly IDictionary headers; + + public SimpleWorkerRequestWithHeaders(string page, string query, TextWriter output, IDictionary headers) + : base(page, query, output) { - var context = GetFakeHttpContext(page, query, headers); - return new HttpContextWrapper(context); + if (headers != null) + { + this.headers = headers; + } + else + { + this.headers = new Dictionary(); + } } - private class SimpleWorkerRequestWithHeaders : SimpleWorkerRequest + public override string[][] GetUnknownRequestHeaders() { - private readonly IDictionary headers; + List result = new List(); - public SimpleWorkerRequestWithHeaders(string page, string query, TextWriter output, IDictionary headers) - : base(page, query, output) + foreach (var header in this.headers) { - if (headers != null) - { - this.headers = headers; - } - else - { - this.headers = new Dictionary(); - } + result.Add(new string[] { header.Key, header.Value }); } - public override string[][] GetUnknownRequestHeaders() + var baseResult = base.GetUnknownRequestHeaders(); + if (baseResult != null) { - List result = new List(); - - foreach (var header in this.headers) - { - result.Add(new string[] { header.Key, header.Value }); - } - - var baseResult = base.GetUnknownRequestHeaders(); - if (baseResult != null) - { - result.AddRange(baseResult); - } - - return result.ToArray(); + result.AddRange(baseResult); } - public override string GetUnknownRequestHeader(string name) - { - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } + return result.ToArray(); + } - return base.GetUnknownRequestHeader(name); + public override string GetUnknownRequestHeader(string name) + { + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; } - public override string GetKnownRequestHeader(int index) - { - var name = GetKnownRequestHeaderName(index); + return base.GetUnknownRequestHeader(name); + } - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } + public override string GetKnownRequestHeader(int index) + { + var name = GetKnownRequestHeaderName(index); - return base.GetKnownRequestHeader(index); + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; } + + return base.GetKnownRequestHeader(index); } } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs index c0ddbe96b19..a232bb7bb41 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs @@ -19,17 +19,17 @@ using Microsoft.Web.XmlTransform; using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +public class WebConfigTransformTest { - public class WebConfigTransformTest - { - private const string InstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.install.xdt"; - private const string UninstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.uninstall.xdt"; + private const string InstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.install.xdt"; + private const string UninstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.uninstall.xdt"; - [Fact] - public void VerifyInstallationToBasicWebConfig() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToBasicWebConfig() + { + const string OriginalWebConfigContent = @" @@ -39,7 +39,7 @@ public void VerifyInstallationToBasicWebConfig() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -55,14 +55,14 @@ public void VerifyInstallationToBasicWebConfig() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyUpdateWithTypeRenamingWebConfig() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyUpdateWithTypeRenamingWebConfig() + { + const string OriginalWebConfigContent = @" @@ -76,7 +76,7 @@ public void VerifyUpdateWithTypeRenamingWebConfig() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -92,14 +92,14 @@ public void VerifyUpdateWithTypeRenamingWebConfig() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyUpdateNewerVersionWebConfig() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyUpdateNewerVersionWebConfig() + { + const string OriginalWebConfigContent = @" @@ -113,7 +113,7 @@ public void VerifyUpdateNewerVersionWebConfig() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -130,14 +130,14 @@ public void VerifyUpdateNewerVersionWebConfig() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyUpdateWithIntegratedModeWebConfig() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyUpdateWithIntegratedModeWebConfig() + { + const string OriginalWebConfigContent = @" @@ -152,7 +152,7 @@ public void VerifyUpdateWithIntegratedModeWebConfig() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -168,14 +168,14 @@ public void VerifyUpdateWithIntegratedModeWebConfig() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyUninstallationWithBasicWebConfig() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyUninstallationWithBasicWebConfig() + { + const string OriginalWebConfigContent = @" @@ -190,7 +190,7 @@ public void VerifyUninstallationWithBasicWebConfig() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -200,14 +200,14 @@ public void VerifyUninstallationWithBasicWebConfig() "; - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyUninstallWithIntegratedPrecondition() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyUninstallWithIntegratedPrecondition() + { + const string OriginalWebConfigContent = @" @@ -222,7 +222,7 @@ public void VerifyUninstallWithIntegratedPrecondition() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -232,14 +232,14 @@ public void VerifyUninstallWithIntegratedPrecondition() "; - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyUninstallationWithUserModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyUninstallationWithUserModules() + { + const string OriginalWebConfigContent = @" @@ -256,7 +256,7 @@ public void VerifyUninstallationWithUserModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -270,14 +270,14 @@ public void VerifyUninstallationWithUserModules() "; - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToWebConfigWithUserModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToWebConfigWithUserModules() + { + const string OriginalWebConfigContent = @" @@ -291,7 +291,7 @@ public void VerifyInstallationToWebConfigWithUserModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -309,16 +309,16 @@ public void VerifyInstallationToWebConfigWithUserModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToEmptyWebConfig() - { - const string OriginalWebConfigContent = @""; + [Fact] + public void VerifyInstallationToEmptyWebConfig() + { + const string OriginalWebConfigContent = @""; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -334,16 +334,16 @@ public void VerifyInstallationToEmptyWebConfig() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToWebConfigWithoutModules() - { - const string OriginalWebConfigContent = @""; + [Fact] + public void VerifyInstallationToWebConfigWithoutModules() + { + const string OriginalWebConfigContent = @""; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -359,51 +359,50 @@ public void VerifyInstallationToWebConfigWithoutModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } + private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } + private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + { + Assert.True( + XNode.DeepEquals( + transformedWebConfig.FirstNode, + XDocument.Parse(expectedConfigContent).FirstNode)); + } - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + { + XDocument result; + Stream stream = null; + try { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); + stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); + var document = new XmlTransformableDocument(); + using var transformation = new XmlTransformation(stream, null); + stream = null; + document.LoadXml(originalConfiguration); + transformation.Apply(document); + result = XDocument.Parse(document.OuterXml); } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + finally { - XDocument result; - Stream stream = null; - try + if (stream != null) { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using var transformation = new XmlTransformation(stream, null); - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); + stream.Dispose(); } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - - return result; } + + return result; } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs index e8415130cac..0e84dbb24b0 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs @@ -19,16 +19,16 @@ using Microsoft.Web.XmlTransform; using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +public class WebConfigWithLocationTagTransformTest { - public class WebConfigWithLocationTagTransformTest - { - private const string InstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.install.xdt"; + private const string InstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.install.xdt"; - [Fact] - public void VerifyInstallationWhenNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationWhenNonGlobalLocationTagExists() + { + const string OriginalWebConfigContent = @" @@ -39,7 +39,7 @@ public void VerifyInstallationWhenNonGlobalLocationTagExists() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -62,14 +62,14 @@ public void VerifyInstallationWhenNonGlobalLocationTagExists() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() + { + const string OriginalWebConfigContent = @" @@ -92,7 +92,7 @@ public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -121,14 +121,14 @@ public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() + { + const string OriginalWebConfigContent = @" @@ -146,7 +146,7 @@ public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -168,14 +168,14 @@ public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() + { + const string OriginalWebConfigContent = @" @@ -191,7 +191,7 @@ public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -213,14 +213,14 @@ public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithNoModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToLocationTagWithDotPathWithNoModules() + { + const string OriginalWebConfigContent = @" @@ -234,7 +234,7 @@ public void VerifyInstallationToLocationTagWithDotPathWithNoModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -256,14 +256,14 @@ public void VerifyInstallationToLocationTagWithDotPathWithNoModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() + { + const string OriginalWebConfigContent = @" @@ -273,7 +273,7 @@ public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -295,14 +295,14 @@ public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() + { + const string OriginalWebConfigContent = @" @@ -322,7 +322,7 @@ public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -346,14 +346,14 @@ public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() + { + const string OriginalWebConfigContent = @" @@ -369,7 +369,7 @@ public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() "; - const string ExpectedWebConfigContent = @" + const string ExpectedWebConfigContent = @" @@ -389,51 +389,50 @@ public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() "; - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } + private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } + private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + { + Assert.True( + XNode.DeepEquals( + transformedWebConfig.FirstNode, + XDocument.Parse(expectedConfigContent).FirstNode)); + } - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + { + XDocument result; + Stream stream = null; + try { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); + stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); + var document = new XmlTransformableDocument(); + using var transformation = new XmlTransformation(stream, null); + stream = null; + document.LoadXml(originalConfiguration); + transformation.Apply(document); + result = XDocument.Parse(document.OuterXml); } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + finally { - XDocument result; - Stream stream = null; - try - { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using var transformation = new XmlTransformation(stream, null); - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); - } - finally + if (stream != null) { - if (stream != null) - { - stream.Dispose(); - } + stream.Dispose(); } - - return result; } + + return result; } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/BasicTests.cs index d3ded5f6327..431f52f995a 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.Tests/BasicTests.cs @@ -18,15 +18,14 @@ using OpenTelemetry.Trace; using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +public class BasicTests { - public class BasicTests + [Fact] + public void AddAspNetInstrumentation_BadArgs() { - [Fact] - public void AddAspNetInstrumentation_BadArgs() - { - TracerProviderBuilder builder = null; - Assert.Throws(() => builder.AddAspNetInstrumentation()); - } + TracerProviderBuilder builder = null; + Assert.Throws(() => builder.AddAspNetInstrumentation()); } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/EventSourceTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/EventSourceTest.cs index 521e857bbaf..9c90b8ebeef 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.Tests/EventSourceTest.cs @@ -18,14 +18,13 @@ using OpenTelemetry.Tests; using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +public class EventSourceTest { - public class EventSourceTest + [Fact] + public void EventSourceTest_AspNetInstrumentationEventSource() { - [Fact] - public void EventSourceTest_AspNetInstrumentationEventSource() - { - EventSourceTestHelper.MethodsAreImplementedConsistentlyWithTheirAttributes(AspNetInstrumentationEventSource.Log); - } + EventSourceTestHelper.MethodsAreImplementedConsistentlyWithTheirAttributes(AspNetInstrumentationEventSource.Log); } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs index bc1588d001e..0e3bd5eef20 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs @@ -29,342 +29,341 @@ using OpenTelemetry.Trace; using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +public class HttpInListenerTests { - public class HttpInListenerTests + [Theory] + [InlineData("http://localhost/", "http://localhost/", 0, null)] + [InlineData("http://localhost/", "http://localhost/", 0, null, true)] + [InlineData("https://localhost/", "https://localhost/", 0, null)] + [InlineData("https://localhost/", "https://user:pass@localhost/", 0, null)] // Test URL sanitization + [InlineData("http://localhost:443/", "http://localhost:443/", 0, null)] // Test http over 443 + [InlineData("https://localhost:80/", "https://localhost:80/", 0, null)] // Test https over 80 + [InlineData("https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", 0, null)] // Test complex URL + [InlineData("https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://user:password@localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", 0, null)] // Test complex URL sanitization + [InlineData("http://localhost:80/Index", "http://localhost:80/Index", 1, "{controller}/{action}/{id}")] + [InlineData("https://localhost:443/about_attr_route/10", "https://localhost:443/about_attr_route/10", 2, "about_attr_route/{customerId}")] + [InlineData("http://localhost:1880/api/weatherforecast", "http://localhost:1880/api/weatherforecast", 3, "api/{controller}/{id}")] + [InlineData("https://localhost:1843/subroute/10", "https://localhost:1843/subroute/10", 4, "subroute/{customerId}")] + [InlineData("http://localhost/api/value", "http://localhost/api/value", 0, null, false, "/api/value")] // Request will be filtered + [InlineData("http://localhost/api/value", "http://localhost/api/value", 0, null, false, "{ThrowException}")] // Filter user code will throw an exception + [InlineData("http://localhost/", "http://localhost/", 0, null, false, null, true)] // Test RecordException option + public void AspNetRequestsAreCollectedSuccessfully( + string expectedUrl, + string url, + int routeType, + string routeTemplate, + bool setStatusToErrorInEnrich = false, + string filter = null, + bool recordException = false) { - [Theory] - [InlineData("http://localhost/", "http://localhost/", 0, null)] - [InlineData("http://localhost/", "http://localhost/", 0, null, true)] - [InlineData("https://localhost/", "https://localhost/", 0, null)] - [InlineData("https://localhost/", "https://user:pass@localhost/", 0, null)] // Test URL sanitization - [InlineData("http://localhost:443/", "http://localhost:443/", 0, null)] // Test http over 443 - [InlineData("https://localhost:80/", "https://localhost:80/", 0, null)] // Test https over 80 - [InlineData("https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", 0, null)] // Test complex URL - [InlineData("https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://user:password@localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", 0, null)] // Test complex URL sanitization - [InlineData("http://localhost:80/Index", "http://localhost:80/Index", 1, "{controller}/{action}/{id}")] - [InlineData("https://localhost:443/about_attr_route/10", "https://localhost:443/about_attr_route/10", 2, "about_attr_route/{customerId}")] - [InlineData("http://localhost:1880/api/weatherforecast", "http://localhost:1880/api/weatherforecast", 3, "api/{controller}/{id}")] - [InlineData("https://localhost:1843/subroute/10", "https://localhost:1843/subroute/10", 4, "subroute/{customerId}")] - [InlineData("http://localhost/api/value", "http://localhost/api/value", 0, null, false, "/api/value")] // Request will be filtered - [InlineData("http://localhost/api/value", "http://localhost/api/value", 0, null, false, "{ThrowException}")] // Filter user code will throw an exception - [InlineData("http://localhost/", "http://localhost/", 0, null, false, null, true)] // Test RecordException option - public void AspNetRequestsAreCollectedSuccessfully( - string expectedUrl, - string url, - int routeType, - string routeTemplate, - bool setStatusToErrorInEnrich = false, - string filter = null, - bool recordException = false) + IDisposable tracerProvider = null; + RouteData routeData; + switch (routeType) { - IDisposable tracerProvider = null; - RouteData routeData; - switch (routeType) - { - case 0: // WebForm, no route data. - routeData = new RouteData(); - break; - case 1: // Traditional MVC. - case 2: // Attribute routing MVC. - case 3: // Traditional WebAPI. - routeData = new RouteData() - { - Route = new Route(routeTemplate, null), - }; - break; - case 4: // Attribute routing WebAPI. - routeData = new RouteData(); - var value = new[] - { - new - { - Route = new - { - RouteTemplate = routeTemplate, - }, - }, - }; - routeData.Values.Add( - "MS_SubRoutes", - value); - break; - default: - throw new NotSupportedException(); - } - - var workerRequest = new Mock(); - workerRequest.Setup(wr => wr.GetKnownRequestHeader(It.IsAny())).Returns(i => - { - return i switch + case 0: // WebForm, no route data. + routeData = new RouteData(); + break; + case 1: // Traditional MVC. + case 2: // Attribute routing MVC. + case 3: // Traditional WebAPI. + routeData = new RouteData() { - 39 => "Test", // User-Agent - _ => null, + Route = new Route(routeTemplate, null), }; - }); - - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, url, string.Empty) + break; + case 4: // Attribute routing WebAPI. + routeData = new RouteData(); + var value = new[] { - RequestContext = new RequestContext() + new { - RouteData = routeData, - }, - }, - new HttpResponse(new StringWriter())); - - typeof(HttpRequest).GetField("_wr", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(HttpContext.Current.Request, workerRequest.Object); - - List exportedItems = new List(16); - - Sdk.SetDefaultTextMapPropagator(new TraceContextPropagator()); - using (tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation((options) => - { - options.Filter = httpContext => - { - Assert.True(Activity.Current.IsAllDataRequested); - if (string.IsNullOrEmpty(filter)) - { - return true; - } - - if (filter == "{ThrowException}") + Route = new { - throw new InvalidOperationException(); - } - - return httpContext.Request.Path != filter; - }; - - options.Enrich = GetEnrichmentAction(setStatusToErrorInEnrich ? Status.Error : default); + RouteTemplate = routeTemplate, + }, + }, + }; + routeData.Values.Add( + "MS_SubRoutes", + value); + break; + default: + throw new NotSupportedException(); + } - options.RecordException = recordException; - }) - .AddInMemoryExporter(exportedItems) - .Build()) + var workerRequest = new Mock(); + workerRequest.Setup(wr => wr.GetKnownRequestHeader(It.IsAny())).Returns(i => + { + return i switch { - using var inMemoryEventListener = new InMemoryEventListener(AspNetInstrumentationEventSource.Log); - - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); + 39 => "Test", // User-Agent + _ => null, + }; + }); - if (filter == "{ThrowException}") + HttpContext.Current = new HttpContext( + new HttpRequest(string.Empty, url, string.Empty) + { + RequestContext = new RequestContext() { - Assert.Single(inMemoryEventListener.Events.Where((e) => e.EventId == 2)); - } + RouteData = routeData, + }, + }, + new HttpResponse(new StringWriter())); + + typeof(HttpRequest).GetField("_wr", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(HttpContext.Current.Request, workerRequest.Object); + + List exportedItems = new List(16); + + Sdk.SetDefaultTextMapPropagator(new TraceContextPropagator()); + using (tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddAspNetInstrumentation((options) => + { + options.Filter = httpContext => + { + Assert.True(Activity.Current.IsAllDataRequested); + if (string.IsNullOrEmpty(filter)) + { + return true; + } + + if (filter == "{ThrowException}") + { + throw new InvalidOperationException(); + } + + return httpContext.Request.Path != filter; + }; + + options.Enrich = GetEnrichmentAction(setStatusToErrorInEnrich ? Status.Error : default); + + options.RecordException = recordException; + }) + .AddInMemoryExporter(exportedItems) + .Build()) + { + using var inMemoryEventListener = new InMemoryEventListener(AspNetInstrumentationEventSource.Log); - Assert.Equal(TelemetryHttpModule.AspNetActivityName, Activity.Current.OperationName); + var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - if (recordException) - { - ActivityHelper.WriteActivityException(activity, HttpContext.Current, new InvalidOperationException(), TelemetryHttpModule.Options.OnExceptionCallback); - } - - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); + if (filter == "{ThrowException}") + { + Assert.Single(inMemoryEventListener.Events.Where((e) => e.EventId == 2)); } - if (HttpContext.Current.Request.Path == filter || filter == "{ThrowException}") + Assert.Equal(TelemetryHttpModule.AspNetActivityName, Activity.Current.OperationName); + + if (recordException) { - Assert.Empty(exportedItems); - return; + ActivityHelper.WriteActivityException(activity, HttpContext.Current, new InvalidOperationException(), TelemetryHttpModule.Options.OnExceptionCallback); } - Assert.Single(exportedItems); + ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); + } - Activity span = exportedItems[0]; + if (HttpContext.Current.Request.Path == filter || filter == "{ThrowException}") + { + Assert.Empty(exportedItems); + return; + } - Assert.Equal(TelemetryHttpModule.AspNetActivityName, span.OperationName); - Assert.NotEqual(TimeSpan.Zero, span.Duration); + Assert.Single(exportedItems); - Assert.Equal(routeTemplate ?? HttpContext.Current.Request.Path, span.DisplayName); - Assert.Equal(ActivityKind.Server, span.Kind); - Assert.True(span.Duration != TimeSpan.Zero); + Activity span = exportedItems[0]; - Assert.Equal(200, span.GetTagValue(SemanticConventions.AttributeHttpStatusCode)); + Assert.Equal(TelemetryHttpModule.AspNetActivityName, span.OperationName); + Assert.NotEqual(TimeSpan.Zero, span.Duration); - var expectedUri = new Uri(expectedUrl); - var actualUrl = span.GetTagValue(SemanticConventions.AttributeHttpUrl); + Assert.Equal(routeTemplate ?? HttpContext.Current.Request.Path, span.DisplayName); + Assert.Equal(ActivityKind.Server, span.Kind); + Assert.True(span.Duration != TimeSpan.Zero); - Assert.Equal(expectedUri.ToString(), actualUrl); + Assert.Equal(200, span.GetTagValue(SemanticConventions.AttributeHttpStatusCode)); - // Url strips 80 or 443 if the scheme matches. - if ((expectedUri.Port == 80 && expectedUri.Scheme == "http") || (expectedUri.Port == 443 && expectedUri.Scheme == "https")) - { - Assert.DoesNotContain($":{expectedUri.Port}", actualUrl as string); - } - else - { - Assert.Contains($":{expectedUri.Port}", actualUrl as string); - } + var expectedUri = new Uri(expectedUrl); + var actualUrl = span.GetTagValue(SemanticConventions.AttributeHttpUrl); - // Host includes port if it isn't 80 or 443. - if (expectedUri.Port is 80 or 443) - { - Assert.Equal( - expectedUri.Host, - span.GetTagValue(SemanticConventions.AttributeHttpHost) as string); - } - else - { - Assert.Equal( - $"{expectedUri.Host}:{expectedUri.Port}", - span.GetTagValue(SemanticConventions.AttributeHttpHost) as string); - } + Assert.Equal(expectedUri.ToString(), actualUrl); - Assert.Equal(HttpContext.Current.Request.HttpMethod, span.GetTagValue(SemanticConventions.AttributeHttpMethod) as string); - Assert.Equal(HttpContext.Current.Request.Path, span.GetTagValue(SemanticConventions.AttributeHttpTarget) as string); - Assert.Equal(HttpContext.Current.Request.UserAgent, span.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string); + // Url strips 80 or 443 if the scheme matches. + if ((expectedUri.Port == 80 && expectedUri.Scheme == "http") || (expectedUri.Port == 443 && expectedUri.Scheme == "https")) + { + Assert.DoesNotContain($":{expectedUri.Port}", actualUrl as string); + } + else + { + Assert.Contains($":{expectedUri.Port}", actualUrl as string); + } - if (recordException) - { - var status = span.GetStatus(); - Assert.Equal(Status.Error.StatusCode, status.StatusCode); - Assert.Equal("Operation is not valid due to the current state of the object.", status.Description); - } - else if (setStatusToErrorInEnrich) - { - // This validates that users can override the - // status in Enrich. - Assert.Equal(Status.Error, span.GetStatus()); + // Host includes port if it isn't 80 or 443. + if (expectedUri.Port is 80 or 443) + { + Assert.Equal( + expectedUri.Host, + span.GetTagValue(SemanticConventions.AttributeHttpHost) as string); + } + else + { + Assert.Equal( + $"{expectedUri.Host}:{expectedUri.Port}", + span.GetTagValue(SemanticConventions.AttributeHttpHost) as string); + } - // Instrumentation is not expected to set status description - // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode - Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); - } - else - { - Assert.Equal(Status.Unset, span.GetStatus()); + Assert.Equal(HttpContext.Current.Request.HttpMethod, span.GetTagValue(SemanticConventions.AttributeHttpMethod) as string); + Assert.Equal(HttpContext.Current.Request.Path, span.GetTagValue(SemanticConventions.AttributeHttpTarget) as string); + Assert.Equal(HttpContext.Current.Request.UserAgent, span.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string); - // Instrumentation is not expected to set status description - // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode - Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); - } + if (recordException) + { + var status = span.GetStatus(); + Assert.Equal(Status.Error.StatusCode, status.StatusCode); + Assert.Equal("Operation is not valid due to the current state of the object.", status.Description); } + else if (setStatusToErrorInEnrich) + { + // This validates that users can override the + // status in Enrich. + Assert.Equal(Status.Error, span.GetStatus()); - [Theory] - [InlineData(SamplingDecision.Drop)] - [InlineData(SamplingDecision.RecordOnly)] - [InlineData(SamplingDecision.RecordAndSample)] - public void ExtractContextIrrespectiveOfSamplingDecision(SamplingDecision samplingDecision) + // Instrumentation is not expected to set status description + // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode + Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); + } + else { - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, "http://localhost/", string.Empty) + Assert.Equal(Status.Unset, span.GetStatus()); + + // Instrumentation is not expected to set status description + // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode + Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); + } + } + + [Theory] + [InlineData(SamplingDecision.Drop)] + [InlineData(SamplingDecision.RecordOnly)] + [InlineData(SamplingDecision.RecordAndSample)] + public void ExtractContextIrrespectiveOfSamplingDecision(SamplingDecision samplingDecision) + { + HttpContext.Current = new HttpContext( + new HttpRequest(string.Empty, "http://localhost/", string.Empty) + { + RequestContext = new RequestContext() { - RequestContext = new RequestContext() - { - RouteData = new RouteData(), - }, + RouteData = new RouteData(), }, - new HttpResponse(new StringWriter())); + }, + new HttpResponse(new StringWriter())); - bool isPropagatorCalled = false; - var propagator = new Mock(); - propagator.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())) - .Returns(() => - { - isPropagatorCalled = true; - return default; - }); - - var activityProcessor = new Mock>(); - Sdk.SetDefaultTextMapPropagator(propagator.Object); - using (var tracerProvider = Sdk.CreateTracerProviderBuilder() - .SetSampler(new TestSampler(samplingDecision)) - .AddAspNetInstrumentation() - .AddProcessor(activityProcessor.Object).Build()) + bool isPropagatorCalled = false; + var propagator = new Mock(); + propagator.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())) + .Returns(() => { - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); - } + isPropagatorCalled = true; + return default; + }); - Assert.True(isPropagatorCalled); + var activityProcessor = new Mock>(); + Sdk.SetDefaultTextMapPropagator(propagator.Object); + using (var tracerProvider = Sdk.CreateTracerProviderBuilder() + .SetSampler(new TestSampler(samplingDecision)) + .AddAspNetInstrumentation() + .AddProcessor(activityProcessor.Object).Build()) + { + var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); + ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); } - [Fact] - public void ExtractContextIrrespectiveOfTheFilterApplied() - { - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, "http://localhost/", string.Empty) + Assert.True(isPropagatorCalled); + } + + [Fact] + public void ExtractContextIrrespectiveOfTheFilterApplied() + { + HttpContext.Current = new HttpContext( + new HttpRequest(string.Empty, "http://localhost/", string.Empty) + { + RequestContext = new RequestContext() { - RequestContext = new RequestContext() - { - RouteData = new RouteData(), - }, + RouteData = new RouteData(), }, - new HttpResponse(new StringWriter())); + }, + new HttpResponse(new StringWriter())); - bool isPropagatorCalled = false; - var propagator = new Mock(); - propagator.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())) - .Returns(() => - { - isPropagatorCalled = true; - return default; - }); - - bool isFilterCalled = false; - var activityProcessor = new Mock>(); - Sdk.SetDefaultTextMapPropagator(propagator.Object); - using (var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation(options => - { - options.Filter = context => - { - isFilterCalled = true; - return false; - }; - }) - .AddProcessor(activityProcessor.Object).Build()) + bool isPropagatorCalled = false; + var propagator = new Mock(); + propagator.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())) + .Returns(() => { - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); - } + isPropagatorCalled = true; + return default; + }); - Assert.True(isFilterCalled); - Assert.True(isPropagatorCalled); + bool isFilterCalled = false; + var activityProcessor = new Mock>(); + Sdk.SetDefaultTextMapPropagator(propagator.Object); + using (var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddAspNetInstrumentation(options => + { + options.Filter = context => + { + isFilterCalled = true; + return false; + }; + }) + .AddProcessor(activityProcessor.Object).Build()) + { + var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); + ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); } - private static Action GetEnrichmentAction(Status statusToBeSet) + Assert.True(isFilterCalled); + Assert.True(isPropagatorCalled); + } + + private static Action GetEnrichmentAction(Status statusToBeSet) + { + void EnrichAction(Activity activity, string method, object obj) { - void EnrichAction(Activity activity, string method, object obj) + Assert.True(activity.IsAllDataRequested); + switch (method) { - Assert.True(activity.IsAllDataRequested); - switch (method) - { - case "OnStartActivity": - Assert.True(obj is HttpRequest); - break; + case "OnStartActivity": + Assert.True(obj is HttpRequest); + break; - case "OnStopActivity": - Assert.True(obj is HttpResponse); - if (statusToBeSet != default) - { - activity.SetStatus(statusToBeSet); - } + case "OnStopActivity": + Assert.True(obj is HttpResponse); + if (statusToBeSet != default) + { + activity.SetStatus(statusToBeSet); + } - break; + break; - default: - break; - } + default: + break; } - - return EnrichAction; } - private class TestSampler : Sampler - { - private readonly SamplingDecision samplingDecision; + return EnrichAction; + } - public TestSampler(SamplingDecision samplingDecision) - { - this.samplingDecision = samplingDecision; - } + private class TestSampler : Sampler + { + private readonly SamplingDecision samplingDecision; - public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) - { - return new SamplingResult(this.samplingDecision); - } + public TestSampler(SamplingDecision samplingDecision) + { + this.samplingDecision = samplingDecision; + } + + public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) + { + return new SamplingResult(this.samplingDecision); } } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInMetricsListenerTests.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInMetricsListenerTests.cs index b9fbbbeebeb..f4d27ffd1b3 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInMetricsListenerTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInMetricsListenerTests.cs @@ -22,24 +22,24 @@ using OpenTelemetry.Trace; using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests; + +public class HttpInMetricsListenerTests { - public class HttpInMetricsListenerTests + [Fact] + public void HttpDurationMetricIsEmitted() { - [Fact] - public void HttpDurationMetricIsEmitted() - { - string url = "http://localhost/api/value"; - double duration = 0; - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, url, string.Empty), - new HttpResponse(new StringWriter())); - - // This is to enable activity creation - // as it is created using activitysource inside TelemetryHttpModule - // TODO: This should not be needed once the dependency on activity is removed from metrics - using var traceprovider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation(opts => opts.Enrich + string url = "http://localhost/api/value"; + double duration = 0; + HttpContext.Current = new HttpContext( + new HttpRequest(string.Empty, url, string.Empty), + new HttpResponse(new StringWriter())); + + // This is to enable activity creation + // as it is created using activitysource inside TelemetryHttpModule + // TODO: This should not be needed once the dependency on activity is removed from metrics + using var traceprovider = Sdk.CreateTracerProviderBuilder() + .AddAspNetInstrumentation(opts => opts.Enrich = (activity, eventName, rawObject) => { if (eventName.Equals("OnStopActivity")) @@ -47,66 +47,65 @@ public void HttpDurationMetricIsEmitted() duration = activity.Duration.TotalMilliseconds; } }) - .Build(); + .Build(); - var exportedItems = new List(); - using var meterprovider = Sdk.CreateMeterProviderBuilder() - .AddAspNetInstrumentation() - .AddInMemoryExporter(exportedItems) - .Build(); + var exportedItems = new List(); + using var meterprovider = Sdk.CreateMeterProviderBuilder() + .AddAspNetInstrumentation() + .AddInMemoryExporter(exportedItems) + .Build(); - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); + var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); + ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); - meterprovider.ForceFlush(); + meterprovider.ForceFlush(); - var metricPoints = new List(); - foreach (var p in exportedItems[0].GetMetricPoints()) - { - metricPoints.Add(p); - } + var metricPoints = new List(); + foreach (var p in exportedItems[0].GetMetricPoints()) + { + metricPoints.Add(p); + } - Assert.Single(metricPoints); + Assert.Single(metricPoints); - var metricPoint = metricPoints[0]; + var metricPoint = metricPoints[0]; - var count = metricPoint.GetHistogramCount(); - var sum = metricPoint.GetHistogramSum(); + var count = metricPoint.GetHistogramCount(); + var sum = metricPoint.GetHistogramSum(); - Assert.Equal(MetricType.Histogram, exportedItems[0].MetricType); - Assert.Equal("http.server.duration", exportedItems[0].Name); - Assert.Equal(1L, count); - Assert.Equal(duration, sum); + Assert.Equal(MetricType.Histogram, exportedItems[0].MetricType); + Assert.Equal("http.server.duration", exportedItems[0].Name); + Assert.Equal(1L, count); + Assert.Equal(duration, sum); - Assert.Equal(3, metricPoints[0].Tags.Count); - string httpMethod = null; - int httpStatusCode = 0; - string httpScheme = null; + Assert.Equal(3, metricPoints[0].Tags.Count); + string httpMethod = null; + int httpStatusCode = 0; + string httpScheme = null; - foreach (var tag in metricPoints[0].Tags) + foreach (var tag in metricPoints[0].Tags) + { + if (tag.Key == SemanticConventions.AttributeHttpMethod) { - if (tag.Key == SemanticConventions.AttributeHttpMethod) - { - httpMethod = (string)tag.Value; - continue; - } - - if (tag.Key == SemanticConventions.AttributeHttpStatusCode) - { - httpStatusCode = (int)tag.Value; - continue; - } + httpMethod = (string)tag.Value; + continue; + } - if (tag.Key == SemanticConventions.AttributeHttpScheme) - { - httpScheme = (string)tag.Value; - continue; - } + if (tag.Key == SemanticConventions.AttributeHttpStatusCode) + { + httpStatusCode = (int)tag.Value; + continue; } - Assert.Equal("GET", httpMethod); - Assert.Equal(200, httpStatusCode); - Assert.Equal("http", httpScheme); + if (tag.Key == SemanticConventions.AttributeHttpScheme) + { + httpScheme = (string)tag.Value; + continue; + } } + + Assert.Equal("GET", httpMethod); + Assert.Equal(200, httpStatusCode); + Assert.Equal("http", httpScheme); } }