diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj
index fbaf1d950a196..477cb08f059a4 100644
--- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj
+++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj
@@ -38,6 +38,7 @@
+
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs
index 6436c1595b46e..ed82d0b37d299 100644
--- a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs
+++ b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs
@@ -53,20 +53,19 @@ private static bool IsEnabled()
s_diagnosticListener.IsEnabled();
}
- private static Activity? CreateActivity(HttpRequestMessage requestMessage)
+ private static Activity? StartActivity(HttpRequestMessage request)
{
Activity? activity = null;
if (s_activitySource.HasListeners())
{
- activity = s_activitySource.CreateActivity(DiagnosticsHandlerLoggingStrings.ActivityName, ActivityKind.Client);
+ activity = s_activitySource.StartActivity(DiagnosticsHandlerLoggingStrings.ActivityName, ActivityKind.Client);
}
- if (activity is null)
+ if (activity is null &&
+ (Activity.Current is not null ||
+ s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityName, request)))
{
- if (Activity.Current is not null || s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityName, requestMessage))
- {
- activity = new Activity(DiagnosticsHandlerLoggingStrings.ActivityName);
- }
+ activity = new Activity(DiagnosticsHandlerLoggingStrings.ActivityName).Start();
}
return activity;
@@ -109,12 +108,30 @@ private async ValueTask SendAsyncCore(HttpRequestMessage re
DiagnosticListener diagnosticListener = s_diagnosticListener;
Guid loggingRequestId = Guid.Empty;
- Activity? activity = CreateActivity(request);
+ Activity? activity = StartActivity(request);
- // Start activity anyway if it was created.
if (activity is not null)
{
- activity.Start();
+ // https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-spans.md#name
+ activity.DisplayName = HttpMethod.GetKnownMethod(request.Method.Method)?.Method ?? "HTTP";
+
+ if (activity.IsAllDataRequested)
+ {
+ // Add standard tags known before sending the request.
+ KeyValuePair methodTag = DiagnosticsHelper.GetMethodTag(request.Method, out bool isUnknownMethod);
+ activity.SetTag(methodTag.Key, methodTag.Value);
+ if (isUnknownMethod)
+ {
+ activity.SetTag("http.request.method_original", request.Method.Method);
+ }
+
+ if (request.RequestUri is Uri requestUri && requestUri.IsAbsoluteUri)
+ {
+ activity.SetTag("server.address", requestUri.Host);
+ activity.SetTag("server.port", requestUri.Port);
+ activity.SetTag("url.full", DiagnosticsHelper.GetRedactedUriString(requestUri));
+ }
+ }
// Only send start event to users who subscribed for it.
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityStartName))
@@ -141,6 +158,7 @@ private async ValueTask SendAsyncCore(HttpRequestMessage re
}
HttpResponseMessage? response = null;
+ Exception? exception = null;
TaskStatus taskStatus = TaskStatus.RanToCompletion;
try
{
@@ -159,6 +177,7 @@ await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false)
catch (Exception ex)
{
taskStatus = TaskStatus.Faulted;
+ exception = ex;
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ExceptionEventName))
{
@@ -176,6 +195,25 @@ await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false)
{
activity.SetEndTime(DateTime.UtcNow);
+ if (activity.IsAllDataRequested)
+ {
+ // Add standard tags known at request completion.
+ if (response is not null)
+ {
+ activity.SetTag("http.response.status_code", DiagnosticsHelper.GetBoxedStatusCode((int)response.StatusCode));
+ activity.SetTag("network.protocol.version", DiagnosticsHelper.GetProtocolVersionString(response.Version));
+ }
+
+ if (DiagnosticsHelper.TryGetErrorType(response, exception, out string? errorType))
+ {
+ activity.SetTag("error.type", errorType);
+
+ // The presence of error.type indicates that the conditions for setting Error status are also met.
+ // https://github.com/open-telemetry/semantic-conventions/blob/v1.26.0/docs/http/http-spans.md#status
+ activity.SetStatus(ActivityStatusCode.Error);
+ }
+ }
+
// Only send stop event to users who subscribed for it.
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityStopName))
{
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHelper.cs b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHelper.cs
new file mode 100644
index 0000000000000..3622edfee0cb8
--- /dev/null
+++ b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHelper.cs
@@ -0,0 +1,121 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+
+namespace System.Net.Http
+{
+ internal static class DiagnosticsHelper
+ {
+ internal static string GetRedactedUriString(Uri uri)
+ {
+ Debug.Assert(uri.IsAbsoluteUri);
+
+ if (GlobalHttpSettings.DiagnosticsHandler.DisableUriRedaction)
+ {
+ return uri.AbsoluteUri;
+ }
+
+ string pathAndQuery = uri.PathAndQuery;
+ int queryIndex = pathAndQuery.IndexOf('?');
+
+ bool redactQuery = queryIndex >= 0 && // Query is present.
+ queryIndex < pathAndQuery.Length - 1; // Query is not empty.
+
+ return (redactQuery, uri.IsDefaultPort) switch
+ {
+ (true, true) => $"{uri.Scheme}://{uri.Host}{pathAndQuery.AsSpan(0, queryIndex + 1)}*",
+ (true, false) => $"{uri.Scheme}://{uri.Host}:{uri.Port}{pathAndQuery.AsSpan(0, queryIndex + 1)}*",
+ (false, true) => $"{uri.Scheme}://{uri.Host}{pathAndQuery}",
+ (false, false) => $"{uri.Scheme}://{uri.Host}:{uri.Port}{pathAndQuery}"
+ };
+ }
+
+ internal static KeyValuePair GetMethodTag(HttpMethod method, out bool isUnknownMethod)
+ {
+ // Return canonical names for known methods and "_OTHER" for unknown ones.
+ HttpMethod? known = HttpMethod.GetKnownMethod(method.Method);
+ isUnknownMethod = known is null;
+ return new KeyValuePair("http.request.method", isUnknownMethod ? "_OTHER" : known!.Method);
+ }
+
+ internal static string GetProtocolVersionString(Version httpVersion) => (httpVersion.Major, httpVersion.Minor) switch
+ {
+ (1, 0) => "1.0",
+ (1, 1) => "1.1",
+ (2, 0) => "2",
+ (3, 0) => "3",
+ _ => httpVersion.ToString()
+ };
+
+ public static bool TryGetErrorType(HttpResponseMessage? response, Exception? exception, out string? errorType)
+ {
+ if (response is not null)
+ {
+ int statusCode = (int)response.StatusCode;
+
+ // In case the status code indicates a client or a server error, return the string representation of the status code.
+ // See the paragraph Status and the definition of 'error.type' in
+ // https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-spans.md
+ if (statusCode >= 400 && statusCode <= 599)
+ {
+ errorType = GetErrorStatusCodeString(statusCode);
+ return true;
+ }
+ }
+
+ if (exception is null)
+ {
+ errorType = null;
+ return false;
+ }
+
+ Debug.Assert(Enum.GetValues().Length == 12, "We need to extend the mapping in case new values are added to HttpRequestError.");
+ errorType = (exception as HttpRequestException)?.HttpRequestError switch
+ {
+ HttpRequestError.NameResolutionError => "name_resolution_error",
+ HttpRequestError.ConnectionError => "connection_error",
+ HttpRequestError.SecureConnectionError => "secure_connection_error",
+ HttpRequestError.HttpProtocolError => "http_protocol_error",
+ HttpRequestError.ExtendedConnectNotSupported => "extended_connect_not_supported",
+ HttpRequestError.VersionNegotiationError => "version_negotiation_error",
+ HttpRequestError.UserAuthenticationError => "user_authentication_error",
+ HttpRequestError.ProxyTunnelError => "proxy_tunnel_error",
+ HttpRequestError.InvalidResponse => "invalid_response",
+ HttpRequestError.ResponseEnded => "response_ended",
+ HttpRequestError.ConfigurationLimitExceeded => "configuration_limit_exceeded",
+
+ // Fall back to the exception type name in case of HttpRequestError.Unknown or when exception is not an HttpRequestException.
+ _ => exception.GetType().FullName!
+ };
+ return true;
+ }
+
+ private static object[]? s_boxedStatusCodes;
+ private static string[]? s_statusCodeStrings;
+
+#pragma warning disable CA1859 // we explictly box here
+ public static object GetBoxedStatusCode(int statusCode)
+ {
+ object[] boxes = LazyInitializer.EnsureInitialized(ref s_boxedStatusCodes, static () => new object[512]);
+
+ return (uint)statusCode < (uint)boxes.Length
+ ? boxes[statusCode] ??= statusCode
+ : statusCode;
+ }
+#pragma warning restore
+
+ private static string GetErrorStatusCodeString(int statusCode)
+ {
+ Debug.Assert(statusCode >= 400 && statusCode <= 599);
+
+ string[] strings = LazyInitializer.EnsureInitialized(ref s_statusCodeStrings, static () => new string[200]);
+ int index = statusCode - 400;
+ return (uint)index < (uint)strings.Length
+ ? strings[index] ??= statusCode.ToString()
+ : statusCode.ToString();
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs
index afeac67e38a5d..72c9a4a3fc52e 100644
--- a/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs
+++ b/src/libraries/System.Net.Http/src/System/Net/Http/GlobalHttpSettings.cs
@@ -14,6 +14,11 @@ internal static class DiagnosticsHandler
"System.Net.Http.EnableActivityPropagation",
"DOTNET_SYSTEM_NET_HTTP_ENABLEACTIVITYPROPAGATION",
true);
+
+ public static bool DisableUriRedaction { get; } = RuntimeSettingParser.QueryRuntimeSettingSwitch(
+ "System.Net.Http.DisableUriRedaction",
+ "DOTNET_SYSTEM_NET_HTTP_DISABLEURIREDACTION",
+ false);
}
internal static class SocketsHttpHandler
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs
index 942cd6ceaed36..152ad063a5b8a 100644
--- a/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs
+++ b/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs
@@ -109,11 +109,11 @@ private void RequestStop(HttpRequestMessage request, HttpResponseMessage? respon
if (response is not null)
{
- tags.Add("http.response.status_code", GetBoxedStatusCode((int)response.StatusCode));
- tags.Add("network.protocol.version", GetProtocolVersionString(response.Version));
+ tags.Add("http.response.status_code", DiagnosticsHelper.GetBoxedStatusCode((int)response.StatusCode));
+ tags.Add("network.protocol.version", DiagnosticsHelper.GetProtocolVersionString(response.Version));
}
- if (TryGetErrorType(response, exception, out string? errorType))
+ if (DiagnosticsHelper.TryGetErrorType(response, exception, out string? errorType))
{
tags.Add("error.type", errorType);
}
@@ -131,58 +131,6 @@ private void RequestStop(HttpRequestMessage request, HttpResponseMessage? respon
}
}
- private static bool TryGetErrorType(HttpResponseMessage? response, Exception? exception, out string? errorType)
- {
- if (response is not null)
- {
- int statusCode = (int)response.StatusCode;
-
- // In case the status code indicates a client or a server error, return the string representation of the status code.
- // See the paragraph Status and the definition of 'error.type' in
- // https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-spans.md
- if (statusCode >= 400 && statusCode <= 599)
- {
- errorType = GetErrorStatusCodeString(statusCode);
- return true;
- }
- }
-
- if (exception is null)
- {
- errorType = null;
- return false;
- }
-
- Debug.Assert(Enum.GetValues().Length == 12, "We need to extend the mapping in case new values are added to HttpRequestError.");
- errorType = (exception as HttpRequestException)?.HttpRequestError switch
- {
- HttpRequestError.NameResolutionError => "name_resolution_error",
- HttpRequestError.ConnectionError => "connection_error",
- HttpRequestError.SecureConnectionError => "secure_connection_error",
- HttpRequestError.HttpProtocolError => "http_protocol_error",
- HttpRequestError.ExtendedConnectNotSupported => "extended_connect_not_supported",
- HttpRequestError.VersionNegotiationError => "version_negotiation_error",
- HttpRequestError.UserAuthenticationError => "user_authentication_error",
- HttpRequestError.ProxyTunnelError => "proxy_tunnel_error",
- HttpRequestError.InvalidResponse => "invalid_response",
- HttpRequestError.ResponseEnded => "response_ended",
- HttpRequestError.ConfigurationLimitExceeded => "configuration_limit_exceeded",
-
- // Fall back to the exception type name in case of HttpRequestError.Unknown or when exception is not an HttpRequestException.
- _ => exception.GetType().FullName!
- };
- return true;
- }
-
- private static string GetProtocolVersionString(Version httpVersion) => (httpVersion.Major, httpVersion.Minor) switch
- {
- (1, 0) => "1.0",
- (1, 1) => "1.1",
- (2, 0) => "2",
- (3, 0) => "3",
- _ => httpVersion.ToString()
- };
-
private static TagList InitializeCommonTags(HttpRequestMessage request)
{
TagList tags = default;
@@ -197,43 +145,11 @@ private static TagList InitializeCommonTags(HttpRequestMessage request)
tags.Add("server.port", requestUri.Port);
}
}
- tags.Add(GetMethodTag(request.Method));
+ tags.Add(DiagnosticsHelper.GetMethodTag(request.Method, out _));
return tags;
}
- internal static KeyValuePair GetMethodTag(HttpMethod method)
- {
- // Return canonical names for known methods and "_OTHER" for unknown ones.
- HttpMethod? known = HttpMethod.GetKnownMethod(method.Method);
- return new KeyValuePair("http.request.method", known?.Method ?? "_OTHER");
- }
-
- private static object[]? s_boxedStatusCodes;
- private static string[]? s_statusCodeStrings;
-
-#pragma warning disable CA1859 // we explictly box here
- private static object GetBoxedStatusCode(int statusCode)
- {
- object[] boxes = LazyInitializer.EnsureInitialized(ref s_boxedStatusCodes, static () => new object[512]);
-
- return (uint)statusCode < (uint)boxes.Length
- ? boxes[statusCode] ??= statusCode
- : statusCode;
- }
-#pragma warning restore
-
- private static string GetErrorStatusCodeString(int statusCode)
- {
- Debug.Assert(statusCode >= 400 && statusCode <= 599);
-
- string[] strings = LazyInitializer.EnsureInitialized(ref s_statusCodeStrings, static () => new string[200]);
- int index = statusCode - 400;
- return (uint)index < (uint)strings.Length
- ? strings[index] ??= statusCode.ToString()
- : statusCode.ToString();
- }
-
private sealed class SharedMeter : Meter
{
public static Meter Instance { get; } = new SharedMeter();
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Metrics/SocketsHttpHandlerMetrics.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Metrics/SocketsHttpHandlerMetrics.cs
index bd37b3f66b805..f654136a1001c 100644
--- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Metrics/SocketsHttpHandlerMetrics.cs
+++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Metrics/SocketsHttpHandlerMetrics.cs
@@ -47,7 +47,7 @@ public void RequestLeftQueue(HttpRequestMessage request, HttpConnectionPool pool
tags.Add("server.port", pool.OriginAuthority.Port);
}
- tags.Add(MetricsHandler.GetMethodTag(request.Method));
+ tags.Add(DiagnosticsHelper.GetMethodTag(request.Method, out _));
RequestsQueueDuration.Record(duration.TotalSeconds, tags);
}
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs
index a46d376157fc1..4a067b778fb89 100644
--- a/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs
+++ b/src/libraries/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs
@@ -18,7 +18,7 @@
namespace System.Net.Http.Functional.Tests
{
- public abstract class DiagnosticsTest : HttpClientHandlerTestBase
+ public abstract class DiagnosticsTest : DiagnosticsTestBase
{
private const string EnableActivityPropagationEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_ENABLEACTIVITYPROPAGATION";
private const string EnableActivityPropagationAppCtxSettingName = "System.Net.Http.EnableActivityPropagation";
@@ -377,6 +377,206 @@ await GetFactoryForVersion(useVersion).CreateClientAndServerAsync(
}, UseVersion.ToString(), TestAsync.ToString(), idFormat.ToString()).DisposeAsync();
}
+ [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ [InlineData(200, "GET")]
+ [InlineData(404, "GET")]
+ [InlineData(200, "CUSTOM")]
+ public async Task SendAsync_DiagnosticListener_ExpectedTagsRecorded(int statusCode, string method)
+ {
+ await RemoteExecutor.Invoke(static async (useVersion, testAsync, statusCodeStr, method) =>
+ {
+ TaskCompletionSource activityStopTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ Activity parentActivity = new Activity("parent");
+ parentActivity.Start();
+
+ Uri? currentUri = null;
+ string? expectedUriFull = null;
+
+ var diagnosticListenerObserver = new FakeDiagnosticListenerObserver(kvp =>
+ {
+ Assert.NotNull(currentUri);
+
+ if (!kvp.Key.StartsWith("System.Net.Http.HttpRequestOut"))
+ {
+ return;
+ }
+ Activity activity = Activity.Current;
+ Assert.NotNull(activity);
+ Assert.Equal(parentActivity, Activity.Current.Parent);
+ IEnumerable> tags = activity.TagObjects;
+ Assert.Equal(method is "CUSTOM" ? "HTTP" : method, activity.DisplayName);
+ VerifyRequestTags(tags, currentUri, expectedUriFull, expectedMethodTag: method is "CUSTOM" ? "_OTHER" : method);
+ VerifyTag(tags, "http.request.method_original", method is "CUSTOM" ? method : null);
+
+ if (kvp.Key.EndsWith(".Stop"))
+ {
+ VerifyTag(tags, "network.protocol.version", GetVersionString(Version.Parse(useVersion)));
+ VerifyTag(tags, "http.response.status_code", int.Parse(statusCodeStr));
+
+ if (statusCodeStr != "200")
+ {
+ string errorType = (string)tags.Single(t => t.Key == "error.type").Value;
+ Assert.Equal(ActivityStatusCode.Error, activity.Status);
+ Assert.Equal(statusCodeStr, errorType);
+ }
+ else
+ {
+ Assert.Equal(ActivityStatusCode.Unset, activity.Status);
+ Assert.DoesNotContain(tags, t => t.Key == "error.type");
+ }
+
+ activityStopTcs.SetResult();
+ }
+ });
+
+ using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver))
+ {
+ diagnosticListenerObserver.Enable(s => s.Contains("HttpRequestOut"));
+
+ await GetFactoryForVersion(useVersion).CreateClientAndServerAsync(
+ async uri =>
+ {
+ uri = new Uri($"{uri.Scheme}://user:pass@{uri.Authority}/1/2/?a=1&b=2");
+ expectedUriFull = $"{uri.Scheme}://{uri.Authority}{uri.AbsolutePath}?*";
+ currentUri = uri;
+
+ using HttpClient client = new(CreateHttpClientHandler(allowAllCertificates: true));
+ using HttpRequestMessage request = CreateRequest(HttpMethod.Parse(method), uri, Version.Parse(useVersion), exactVersion: true);
+ await client.SendAsync(bool.Parse(testAsync), request);
+ await activityStopTcs.Task;
+ },
+ async server =>
+ {
+ HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync(
+ statusCode: (HttpStatusCode)int.Parse(statusCodeStr));
+ AssertHeadersAreInjected(requestData, parentActivity);
+ });
+ }
+ }, UseVersion.ToString(), TestAsync.ToString(), statusCode.ToString(), method).DisposeAsync();
+ }
+
+
+ [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ [InlineData(200, "GET")]
+ [InlineData(404, "GET")]
+ [InlineData(200, "CUSTOM")]
+ public async Task SendAsync_ActivitySource_ExpectedTagsRecorded(int statusCode, string method)
+ {
+ await RemoteExecutor.Invoke(static async (useVersion, testAsync, statusCodeStr, method) =>
+ {
+ TaskCompletionSource activityStopTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
+ Activity? activity = null;
+ Uri? currentUri = null;
+ string? expectedUriFull = null;
+
+ using ActivityListener listener = new ActivityListener()
+ {
+ ShouldListenTo = s => s.Name is "System.Net.Http",
+ Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData,
+ ActivityStopped = a =>
+ {
+ activity = a;
+ var tags = activity.TagObjects;
+ VerifyRequestTags(a.TagObjects, currentUri, expectedUriFull, expectedMethodTag: method is "CUSTOM" ? "_OTHER" : method);
+ VerifyTag(tags, "http.request.method_original", method is "CUSTOM" ? method : null);
+ VerifyTag(tags, "network.protocol.version", GetVersionString(Version.Parse(useVersion)));
+ VerifyTag(tags, "http.response.status_code", int.Parse(statusCodeStr));
+
+ if (statusCodeStr != "200")
+ {
+ string errorType = (string)tags.Single(t => t.Key == "error.type").Value;
+ Assert.Equal(ActivityStatusCode.Error, activity.Status);
+ Assert.Equal(statusCodeStr, errorType);
+ }
+ else
+ {
+ Assert.Equal(ActivityStatusCode.Unset, activity.Status);
+ Assert.DoesNotContain(tags, t => t.Key == "error.type");
+ }
+
+ activityStopTcs.SetResult();
+ }
+ };
+ ActivitySource.AddActivityListener(listener);
+
+ await GetFactoryForVersion(useVersion).CreateClientAndServerAsync(
+ async uri =>
+ {
+ uri = new Uri($"{uri.Scheme}://user:pass@{uri.Authority}/1/2/?a=1&b=2");
+ expectedUriFull = $"{uri.Scheme}://{uri.Authority}{uri.AbsolutePath}?*";
+ currentUri = uri;
+
+ using HttpClient client = new(CreateHttpClientHandler(allowAllCertificates: true));
+ using HttpRequestMessage request = CreateRequest(HttpMethod.Parse(method), uri, Version.Parse(useVersion), exactVersion: true);
+ await client.SendAsync(bool.Parse(testAsync), request);
+ await activityStopTcs.Task;
+ },
+ async server =>
+ {
+ await server.AcceptConnectionSendResponseAndCloseAsync(statusCode: (HttpStatusCode)int.Parse(statusCodeStr));
+ });
+
+ Assert.NotNull(activity);
+ }, UseVersion.ToString(), TestAsync.ToString(), statusCode.ToString(), method).DisposeAsync();
+ }
+
+ [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ public async Task SendAsync_DoNotSampleAllData_NoTagsRecorded()
+ {
+ await RemoteExecutor.Invoke(static async (useVersion, testAsync) =>
+ {
+ TaskCompletionSource activityStopTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
+ Activity? activity = null;
+ using ActivityListener listener = new ActivityListener()
+ {
+ ShouldListenTo = s => s.Name is "System.Net.Http",
+ Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.PropagationData,
+ ActivityStopped = a =>
+ {
+ activity = a;
+ activityStopTcs.SetResult();
+ }
+ };
+ ActivitySource.AddActivityListener(listener);
+
+ await GetFactoryForVersion(useVersion).CreateClientAndServerAsync(
+ async uri =>
+ {
+
+ await GetAsync(useVersion, testAsync, uri);
+ await activityStopTcs.Task;
+ },
+ async server =>
+ {
+ _ = await server.AcceptConnectionSendResponseAndCloseAsync();
+ });
+
+ Assert.NotNull(activity);
+ Assert.Empty(activity.TagObjects);
+ }, UseVersion.ToString(), TestAsync.ToString()).DisposeAsync();
+ }
+
+ protected static void VerifyRequestTags(IEnumerable> tags, Uri uri, string expectedUriFull, string expectedMethodTag = "GET")
+ {
+ VerifyTag(tags, "server.address", uri.Host);
+ VerifyTag(tags, "server.port", uri.Port);
+ VerifyTag(tags, "http.request.method", expectedMethodTag);
+ VerifyTag(tags, "url.full", expectedUriFull);
+ }
+
+ protected static void VerifyTag(KeyValuePair[] tags, string name, T value)
+ {
+ if (value is null)
+ {
+ Assert.DoesNotContain(tags, t => t.Key == name);
+ }
+ else
+ {
+ Assert.Equal(value, (T)tags.Single(t => t.Key == name).Value);
+ }
+ }
+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public async Task SendAsync_ExpectedDiagnosticSourceActivityLogging_InvalidBaggage()
{
@@ -988,7 +1188,6 @@ public static IEnumerable