/// </summary>
internal sealed class DiagnosticsHandler : DelegatingHandler
{
- /// <summary>
- /// DiagnosticHandler constructor
- /// </summary>
- /// <param name="innerHandler">Inner handler: Windows or Unix implementation of HttpMessageHandler.
- /// Note that DiagnosticHandler is the latest in the pipeline </param>
- public DiagnosticsHandler(HttpMessageHandler innerHandler) : base(innerHandler)
- {
- }
+ private const string Namespace = "System.Net.Http";
+ private const string RequestWriteNameDeprecated = Namespace + ".Request";
+ private const string ResponseWriteNameDeprecated = Namespace + ".Response";
+ private const string ExceptionEventName = Namespace + ".Exception";
+ private const string ActivityName = Namespace + ".HttpRequestOut";
+ private const string ActivityStartName = ActivityName + ".Start";
+ private const string ActivityStopName = ActivityName + ".Stop";
- internal static bool IsEnabled()
- {
- // check if there is a parent Activity (and propagation is not suppressed)
- // or if someone listens to HttpHandlerDiagnosticListener
- return IsGloballyEnabled() && (Activity.Current != null || Settings.s_diagnosticListener.IsEnabled());
- }
+ private static readonly DiagnosticListener s_diagnosticListener = new("HttpHandlerDiagnosticListener");
+ private static readonly ActivitySource s_activitySource = new(Namespace);
+
+ public static bool IsGloballyEnabled { get; } = GetEnableActivityPropagationValue();
- internal static bool IsGloballyEnabled()
+ private static bool GetEnableActivityPropagationValue()
{
- return Settings.s_activityPropagationEnabled;
- }
+ // First check for the AppContext switch, giving it priority over the environment variable.
+ if (AppContext.TryGetSwitch(Namespace + ".EnableActivityPropagation", out bool enableActivityPropagation))
+ {
+ return enableActivityPropagation;
+ }
- // SendAsyncCore returns already completed ValueTask for when async: false is passed.
- // Internally, it calls the synchronous Send method of the base class.
- protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) =>
- SendAsyncCore(request, async: false, cancellationToken).AsTask().GetAwaiter().GetResult();
+ // AppContext switch wasn't used. Check the environment variable to determine which handler should be used.
+ string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_HTTP_ENABLEACTIVITYPROPAGATION");
+ if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0")))
+ {
+ // Suppress Activity propagation.
+ return false;
+ }
- protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
- SendAsyncCore(request, async: true, cancellationToken).AsTask();
+ // Defaults to enabling Activity propagation.
+ return true;
+ }
- private async ValueTask<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, bool async,
- CancellationToken cancellationToken)
+ public DiagnosticsHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
- // HttpClientHandler is responsible to call static DiagnosticsHandler.IsEnabled() before forwarding request here.
- // It will check if propagation is on (because parent Activity exists or there is a listener) or off (forcibly disabled)
- // This code won't be called unless consumer unsubscribes from DiagnosticListener right after the check.
- // So some requests happening right after subscription starts might not be instrumented. Similarly,
- // when consumer unsubscribes, extra requests might be instrumented
+ Debug.Assert(IsGloballyEnabled);
+ }
- if (request == null)
+ private static bool ShouldLogDiagnostics(HttpRequestMessage request, out Activity? activity)
+ {
+ if (request is null)
{
throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest);
}
- Activity? activity = null;
- DiagnosticListener diagnosticListener = Settings.s_diagnosticListener;
+ activity = null;
+
+ if (s_activitySource.HasListeners())
+ {
+ activity = s_activitySource.CreateActivity(ActivityName, ActivityKind.Client);
+ }
- // if there is no listener, but propagation is enabled (with previous IsEnabled() check)
- // do not write any events just start/stop Activity and propagate Ids
- if (!diagnosticListener.IsEnabled())
+ if (activity is null)
{
- activity = new Activity(DiagnosticsHandlerLoggingStrings.ActivityName);
- activity.Start();
- InjectHeaders(activity, request);
+ bool diagnosticListenerEnabled = s_diagnosticListener.IsEnabled();
- try
+ if (Activity.Current is not null || (diagnosticListenerEnabled && s_diagnosticListener.IsEnabled(ActivityName, request)))
{
- return async ?
- await base.SendAsync(request, cancellationToken).ConfigureAwait(false) :
- base.Send(request, cancellationToken);
+ // If a diagnostics listener is enabled for the Activity, always create one
+ activity = new Activity(ActivityName);
}
- finally
+ else
{
- activity.Stop();
+ // There is no Activity, but we may still want to use the instrumented SendAsyncCore if diagnostic listeners are interested in other events
+ return diagnosticListenerEnabled;
}
}
- Guid loggingRequestId = Guid.Empty;
+ activity.Start();
- // There is a listener. Check if listener wants to be notified about HttpClient Activities
- if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityName, request))
+ if (s_diagnosticListener.IsEnabled(ActivityStartName))
{
- activity = new Activity(DiagnosticsHandlerLoggingStrings.ActivityName);
+ Write(ActivityStartName, new ActivityStartData(request));
+ }
- // Only send start event to users who subscribed for it, but start activity anyway
- if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityStartName))
- {
- StartActivity(diagnosticListener, activity, new ActivityStartData(request));
- }
- else
- {
- activity.Start();
- }
+ InjectHeaders(activity, request);
+
+ return true;
+ }
+
+ protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (ShouldLogDiagnostics(request, out Activity? activity))
+ {
+ ValueTask<HttpResponseMessage> sendTask = SendAsyncCore(request, activity, async: false, cancellationToken);
+ return sendTask.IsCompleted ?
+ sendTask.Result :
+ sendTask.AsTask().GetAwaiter().GetResult();
}
- // try to write System.Net.Http.Request event (deprecated)
- if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated))
+ else
{
- long timestamp = Stopwatch.GetTimestamp();
- loggingRequestId = Guid.NewGuid();
- Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated,
- new RequestData(request, loggingRequestId, timestamp));
+ return base.Send(request, cancellationToken);
}
+ }
- // If we are on at all, we propagate current activity information
- Activity? currentActivity = Activity.Current;
- if (currentActivity != null)
+ protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (ShouldLogDiagnostics(request, out Activity? activity))
+ {
+ return SendAsyncCore(request, activity, async: true, cancellationToken).AsTask();
+ }
+ else
{
- InjectHeaders(currentActivity, request);
+ return base.SendAsync(request, cancellationToken);
+ }
+ }
+
+ private async ValueTask<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, Activity? activity, bool async, CancellationToken cancellationToken)
+ {
+ Guid loggingRequestId = default;
+
+ if (s_diagnosticListener.IsEnabled(RequestWriteNameDeprecated))
+ {
+ loggingRequestId = Guid.NewGuid();
+ Write(RequestWriteNameDeprecated, new RequestData(request, loggingRequestId, Stopwatch.GetTimestamp()));
}
HttpResponseMessage? response = null;
catch (OperationCanceledException)
{
taskStatus = TaskStatus.Canceled;
-
- // we'll report task status in HttpRequestOut.Stop
throw;
}
catch (Exception ex)
{
- taskStatus = TaskStatus.Faulted;
-
- if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ExceptionEventName))
+ if (s_diagnosticListener.IsEnabled(ExceptionEventName))
{
- // If request was initially instrumented, Activity.Current has all necessary context for logging
- // Request is passed to provide some context if instrumentation was disabled and to avoid
- // extensive Activity.Tags usage to tunnel request properties
- Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ExceptionEventName, new ExceptionData(ex, request));
+ Write(ExceptionEventName, new ExceptionData(ex, request));
}
+
+ taskStatus = TaskStatus.Faulted;
throw;
}
finally
{
- // always stop activity if it was started
- if (activity != null)
+ if (activity is not null)
{
- StopActivity(diagnosticListener, activity, new ActivityStopData(
- response,
- // If request is failed or cancelled, there is no response, therefore no information about request;
- // pass the request in the payload, so consumers can have it in Stop for failed/canceled requests
- // and not retain all requests in Start
- request,
- taskStatus));
+ activity.SetEndTime(DateTime.UtcNow);
+
+ if (s_diagnosticListener.IsEnabled(ActivityStopName))
+ {
+ Write(ActivityStopName, new ActivityStopData(response, request, taskStatus));
+ }
+
+ activity.Stop();
}
- // Try to write System.Net.Http.Response event (deprecated)
- if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated))
+
+ if (s_diagnosticListener.IsEnabled(ResponseWriteNameDeprecated))
{
- long timestamp = Stopwatch.GetTimestamp();
- Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated,
- new ResponseData(
- response,
- loggingRequestId,
- timestamp,
- taskStatus));
+ Write(ResponseWriteNameDeprecated, new ResponseData(response, loggingRequestId, Stopwatch.GetTimestamp(), taskStatus));
}
}
}
- #region private
-
private sealed class ActivityStartData
{
// matches the properties selected in https://github.com/dotnet/diagnostics/blob/ffd0254da3bcc47847b1183fa5453c0877020abd/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/HttpRequestSourceConfiguration.cs#L36-L40
public override string ToString() => $"{{ {nameof(Response)} = {Response}, {nameof(LoggingRequestId)} = {LoggingRequestId}, {nameof(Timestamp)} = {Timestamp}, {nameof(RequestTaskStatus)} = {RequestTaskStatus} }}";
}
- private static class Settings
- {
- private const string EnableActivityPropagationEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_ENABLEACTIVITYPROPAGATION";
- private const string EnableActivityPropagationAppCtxSettingName = "System.Net.Http.EnableActivityPropagation";
-
- public static readonly bool s_activityPropagationEnabled = GetEnableActivityPropagationValue();
-
- private static bool GetEnableActivityPropagationValue()
- {
- // First check for the AppContext switch, giving it priority over the environment variable.
- if (AppContext.TryGetSwitch(EnableActivityPropagationAppCtxSettingName, out bool enableActivityPropagation))
- {
- return enableActivityPropagation;
- }
-
- // AppContext switch wasn't used. Check the environment variable to determine which handler should be used.
- string? envVar = Environment.GetEnvironmentVariable(EnableActivityPropagationEnvironmentVariableSettingName);
- if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0")))
- {
- // Suppress Activity propagation.
- return false;
- }
-
- // Defaults to enabling Activity propagation.
- return true;
- }
-
- public static readonly DiagnosticListener s_diagnosticListener =
- new DiagnosticListener(DiagnosticsHandlerLoggingStrings.DiagnosticListenerName);
- }
-
private static void InjectHeaders(Activity currentActivity, HttpRequestMessage request)
{
+ const string TraceParentHeaderName = "traceparent";
+ const string TraceStateHeaderName = "tracestate";
+ const string RequestIdHeaderName = "Request-Id";
+ const string CorrelationContextHeaderName = "Correlation-Context";
+
if (currentActivity.IdFormat == ActivityIdFormat.W3C)
{
- if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName))
+ if (!request.Headers.Contains(TraceParentHeaderName))
{
- request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName, currentActivity.Id);
+ request.Headers.TryAddWithoutValidation(TraceParentHeaderName, currentActivity.Id);
if (currentActivity.TraceStateString != null)
{
- request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceStateHeaderName, currentActivity.TraceStateString);
+ request.Headers.TryAddWithoutValidation(TraceStateHeaderName, currentActivity.TraceStateString);
}
}
}
else
{
- if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName))
+ if (!request.Headers.Contains(RequestIdHeaderName))
{
- request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName, currentActivity.Id);
+ request.Headers.TryAddWithoutValidation(RequestIdHeaderName, currentActivity.Id);
}
}
baggage.Add(new NameValueHeaderValue(WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)).ToString());
}
while (e.MoveNext());
- request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.CorrelationContextHeaderName, baggage);
+ request.Headers.TryAddWithoutValidation(CorrelationContextHeaderName, baggage);
}
}
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = "The values being passed into Write have the commonly used properties being preserved with DynamicDependency.")]
- private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
- DiagnosticSource diagnosticSource,
- string name,
- T value)
- {
- diagnosticSource.Write(name, value);
- }
-
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
- Justification = "The args being passed into StartActivity have the commonly used properties being preserved with DynamicDependency.")]
- private static Activity StartActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
- DiagnosticSource diagnosticSource,
- Activity activity,
- T? args)
+ private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T value)
{
- return diagnosticSource.StartActivity(activity, args);
+ s_diagnosticListener.Write(name, value);
}
-
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
- Justification = "The args being passed into StopActivity have the commonly used properties being preserved with DynamicDependency.")]
- private static void StopActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
- DiagnosticSource diagnosticSource,
- Activity activity,
- T? args)
- {
- diagnosticSource.StopActivity(activity, args);
- }
-
- #endregion
}
}
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
+using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Net.Test.Common;
GetProperty<HttpRequestMessage>(kvp.Value, "Request");
TaskStatus status = GetProperty<TaskStatus>(kvp.Value, "RequestTaskStatus");
Assert.Equal(TaskStatus.Canceled, status);
- activityStopTcs.SetResult();;
+ activityStopTcs.SetResult();
}
});
activityStopResponseLogged = GetProperty<HttpResponseMessage>(kvp.Value, "Response");
TaskStatus requestStatus = GetProperty<TaskStatus>(kvp.Value, "RequestTaskStatus");
Assert.Equal(TaskStatus.RanToCompletion, requestStatus);
- activityStopTcs.SetResult();;
+ activityStopTcs.SetResult();
}
});
Assert.Contains("goodkey=bad%2Fvalue", correlationContext);
TaskStatus requestStatus = GetProperty<TaskStatus>(kvp.Value, "RequestTaskStatus");
Assert.Equal(TaskStatus.RanToCompletion, requestStatus);
- activityStopTcs.SetResult();;
+ activityStopTcs.SetResult();
}
else if (kvp.Key.Equals("System.Net.Http.Exception"))
{
Assert.False(request.Headers.TryGetValues("traceparent", out var _));
Assert.False(request.Headers.TryGetValues("tracestate", out var _));
- activityStopTcs.SetResult();;
+ activityStopTcs.SetResult();
}
});
}
else if (kvp.Key.Equals("System.Net.Http.HttpRequestOut.Stop"))
{
- activityStopTcs.SetResult();;
+ activityStopTcs.SetResult();
}
});
GetProperty<HttpRequestMessage>(kvp.Value, "Request");
TaskStatus requestStatus = GetProperty<TaskStatus>(kvp.Value, "RequestTaskStatus");
Assert.Equal(TaskStatus.Faulted, requestStatus);
- activityStopTcs.SetResult();;
+ activityStopTcs.SetResult();
}
else if (kvp.Key.Equals("System.Net.Http.Exception"))
{
GetProperty<HttpRequestMessage>(kvp.Value, "Request");
TaskStatus requestStatus = GetProperty<TaskStatus>(kvp.Value, "RequestTaskStatus");
Assert.Equal(TaskStatus.Faulted, requestStatus);
- activityStopTcs.SetResult();;
+ activityStopTcs.SetResult();
}
else if (kvp.Key.Equals("System.Net.Http.Exception"))
{
}, UseVersion.ToString(), TestAsync.ToString()).Dispose();
}
- [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
- public void SendAsync_ExpectedActivityPropagationWithoutListener()
- {
- RemoteExecutor.Invoke(async (useVersion, testAsync) =>
- {
- Activity parent = new Activity("parent").Start();
-
- await GetFactoryForVersion(useVersion).CreateClientAndServerAsync(
- async uri =>
- {
- await GetAsync(useVersion, testAsync, uri);
- },
- async server =>
- {
- HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync();
- AssertHeadersAreInjected(requestData, parent);
- });
- }, UseVersion.ToString(), TestAsync.ToString()).Dispose();
- }
-
- [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
- public void SendAsync_ExpectedActivityPropagationWithoutListenerOrParentActivity()
- {
- RemoteExecutor.Invoke(async (useVersion, testAsync) =>
- {
- await GetFactoryForVersion(useVersion).CreateClientAndServerAsync(
- async uri =>
- {
- await GetAsync(useVersion, testAsync, uri);
- },
- async server =>
- {
- HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync();
- AssertNoHeadersAreInjected(requestData);
- });
- }, UseVersion.ToString(), TestAsync.ToString()).Dispose();
- }
-
[ConditionalTheory(nameof(EnableActivityPropagationEnvironmentVariableIsNotSetAndRemoteExecutorSupported))]
[InlineData("true")]
[InlineData("1")]
}, UseVersion.ToString(), TestAsync.ToString(), switchValue.ToString()).Dispose();
}
+ [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ [InlineData(true, true, true)] // Activity was set and ActivitySource created an Activity
+ [InlineData(true, false, true)] // Activity was set and ActivitySource created an Activity
+ [InlineData(true, null, true)] // Activity was set and ActivitySource created an Activity
+ [InlineData(true, true, false)] // Activity was set, ActivitySource chose not to create an Activity, so one was manually created
+ [InlineData(true, false, false)] // Activity was set, ActivitySource chose not to create an Activity, so one was manually created
+ [InlineData(true, null, false)] // Activity was set, ActivitySource chose not to create an Activity, so one was manually created
+ [InlineData(true, true, null)] // Activity was set, ActivitySource had no listeners, so an Activity was manually created
+ [InlineData(true, false, null)] // Activity was set, ActivitySource had no listeners, so an Activity was manually created
+ [InlineData(true, null, null)] // Activity was set, ActivitySource had no listeners, so an Activity was manually created
+ [InlineData(false, true, true)] // DiagnosticListener requested an Activity and ActivitySource created an Activity
+ [InlineData(false, true, false)] // DiagnosticListener requested an Activity, ActivitySource chose not to create an Activity, so one was manually created
+ [InlineData(false, true, null)] // DiagnosticListener requested an Activity, ActivitySource had no listeners, so an Activity was manually created
+ [InlineData(false, false, true)] // No Activity is set, DiagnosticListener does not want one, but ActivitySource created an Activity
+ [InlineData(false, false, false)] // No Activity is set, DiagnosticListener does not want one and ActivitySource chose not to create an Activity
+ [InlineData(false, false, null)] // No Activity is set, DiagnosticListener does not want one and ActivitySource has no listeners
+ [InlineData(false, null, true)] // No Activity is set, there is no DiagnosticListener, but ActivitySource created an Activity
+ [InlineData(false, null, false)] // No Activity is set, there is no DiagnosticListener and ActivitySource chose not to create an Activity
+ [InlineData(false, null, null)] // No Activity is set, there is no DiagnosticListener and ActivitySource has no listeners
+ public void SendAsync_ActivityIsCreatedIfRequested(bool currentActivitySet, bool? diagnosticListenerActivityEnabled, bool? activitySourceCreatesActivity)
+ {
+ string parameters = $"{currentActivitySet},{diagnosticListenerActivityEnabled},{activitySourceCreatesActivity}";
+
+ RemoteExecutor.Invoke(async (useVersion, testAsync, parametersString) =>
+ {
+ bool?[] parameters = parametersString.Split(',').Select(p => p.Length == 0 ? (bool?)null : bool.Parse(p)).ToArray();
+ bool currentActivitySet = parameters[0].Value;
+ bool? diagnosticListenerActivityEnabled = parameters[1];
+ bool? activitySourceCreatesActivity = parameters[2];
+
+ bool madeASamplingDecision = false;
+ if (activitySourceCreatesActivity.HasValue)
+ {
+ ActivitySource.AddActivityListener(new ActivityListener
+ {
+ ShouldListenTo = _ => true,
+ Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
+ {
+ madeASamplingDecision = true;
+ return activitySourceCreatesActivity.Value ? ActivitySamplingResult.AllData : ActivitySamplingResult.None;
+ }
+ });
+ }
+
+ bool listenerCallbackWasCalled = false;
+ IDisposable listenerSubscription = new MemoryStream(); // Dummy disposable
+ if (diagnosticListenerActivityEnabled.HasValue)
+ {
+ var diagnosticListenerObserver = new FakeDiagnosticListenerObserver(_ => listenerCallbackWasCalled = true);
+
+ diagnosticListenerObserver.Enable(name => !name.Contains("HttpRequestOut") || diagnosticListenerActivityEnabled.Value);
+
+ listenerSubscription = DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver);
+ }
+
+ Activity activity = currentActivitySet ? new Activity("parent").Start() : null;
+
+ if (!currentActivitySet)
+ {
+ // Listen to new activity creations if an Activity was created without a parent
+ // (when a DiagnosticListener forced one to be created)
+ ActivitySource.AddActivityListener(new ActivityListener
+ {
+ ShouldListenTo = _ => true,
+ ActivityStarted = created =>
+ {
+ Assert.Null(activity);
+ activity = created;
+ }
+ });
+ }
+
+ using (listenerSubscription)
+ {
+ await GetFactoryForVersion(useVersion).CreateClientAndServerAsync(
+ async uri =>
+ {
+ await GetAsync(useVersion, testAsync, uri);
+ },
+ async server =>
+ {
+ HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync();
+
+ if (currentActivitySet || diagnosticListenerActivityEnabled == true || activitySourceCreatesActivity == true)
+ {
+ Assert.NotNull(activity);
+ AssertHeadersAreInjected(requestData, activity);
+ }
+ else
+ {
+ AssertNoHeadersAreInjected(requestData);
+
+ if (!currentActivitySet)
+ {
+ Assert.Null(activity);
+ }
+ }
+ });
+ }
+
+ Assert.Equal(activitySourceCreatesActivity.HasValue, madeASamplingDecision);
+ Assert.Equal(diagnosticListenerActivityEnabled.HasValue, listenerCallbackWasCalled);
+ }, UseVersion.ToString(), TestAsync.ToString(), parameters).Dispose();
+ }
+
private static T GetProperty<T>(object obj, string propertyName)
{
Type t = obj.GetType();