diff --git a/src/ReverseProxy/Abstractions/ClusterDiscovery/Contract/ProxyHttpClientOptions.cs b/src/ReverseProxy/Abstractions/ClusterDiscovery/Contract/ProxyHttpClientOptions.cs index 24838e798..e24f15014 100644 --- a/src/ReverseProxy/Abstractions/ClusterDiscovery/Contract/ProxyHttpClientOptions.cs +++ b/src/ReverseProxy/Abstractions/ClusterDiscovery/Contract/ProxyHttpClientOptions.cs @@ -17,6 +17,8 @@ public sealed class ProxyHttpClientOptions public int? MaxConnectionsPerServer { get; set; } + public bool? PropagateActivityContext { get; set; } + // TODO: Add this property once we have migrated to SDK version that supports it. //public bool? EnableMultipleHttp2Connections { get; set; } @@ -28,7 +30,8 @@ internal ProxyHttpClientOptions DeepClone() DangerousAcceptAnyServerCertificate = DangerousAcceptAnyServerCertificate, // TODO: Clone certificate? ClientCertificate = ClientCertificate, - MaxConnectionsPerServer = MaxConnectionsPerServer + MaxConnectionsPerServer = MaxConnectionsPerServer, + PropagateActivityContext = PropagateActivityContext, }; } @@ -47,7 +50,8 @@ internal static bool Equals(ProxyHttpClientOptions options1, ProxyHttpClientOpti return options1.SslProtocols == options2.SslProtocols && Equals(options1.ClientCertificate, options2.ClientCertificate) && options1.DangerousAcceptAnyServerCertificate == options2.DangerousAcceptAnyServerCertificate - && options1.MaxConnectionsPerServer == options2.MaxConnectionsPerServer; + && options1.MaxConnectionsPerServer == options2.MaxConnectionsPerServer + && options1.PropagateActivityContext == options2.PropagateActivityContext; } private static bool Equals(X509Certificate2 certificate1, X509Certificate2 certificate2) diff --git a/src/ReverseProxy/Configuration/ConfigurationConfigProvider.cs b/src/ReverseProxy/Configuration/ConfigurationConfigProvider.cs index 23a243482..284356afe 100644 --- a/src/ReverseProxy/Configuration/ConfigurationConfigProvider.cs +++ b/src/ReverseProxy/Configuration/ConfigurationConfigProvider.cs @@ -380,7 +380,8 @@ private ProxyHttpClientOptions CreateProxyHttpClientOptions(IConfigurationSectio SslProtocols = sslProtocols, DangerousAcceptAnyServerCertificate = section.ReadBool(nameof(ProxyHttpClientOptions.DangerousAcceptAnyServerCertificate)) ?? true, ClientCertificate = clientCertificate, - MaxConnectionsPerServer = section.ReadInt32(nameof(ProxyHttpClientOptions.MaxConnectionsPerServer)) + MaxConnectionsPerServer = section.ReadInt32(nameof(ProxyHttpClientOptions.MaxConnectionsPerServer)), + PropagateActivityContext = section.ReadBool(nameof(ProxyHttpClientOptions.PropagateActivityContext)) }; } diff --git a/src/ReverseProxy/Service/Management/ProxyConfigManager.cs b/src/ReverseProxy/Service/Management/ProxyConfigManager.cs index cb80f15d7..14c16ad5f 100644 --- a/src/ReverseProxy/Service/Management/ProxyConfigManager.cs +++ b/src/ReverseProxy/Service/Management/ProxyConfigManager.cs @@ -512,7 +512,8 @@ private ClusterProxyHttpClientOptions ConvertProxyHttpClientOptions(ProxyHttpCli httpClientOptions.SslProtocols, httpClientOptions.DangerousAcceptAnyServerCertificate, httpClientOptions.ClientCertificate, - httpClientOptions.MaxConnectionsPerServer); + httpClientOptions.MaxConnectionsPerServer, + httpClientOptions.PropagateActivityContext); } public void Dispose() diff --git a/src/ReverseProxy/Service/Proxy/Infrastructure/ProxyHttpClientFactory.cs b/src/ReverseProxy/Service/Proxy/Infrastructure/ProxyHttpClientFactory.cs index 6000accd0..99efe80dd 100644 --- a/src/ReverseProxy/Service/Proxy/Infrastructure/ProxyHttpClientFactory.cs +++ b/src/ReverseProxy/Service/Proxy/Infrastructure/ProxyHttpClientFactory.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging; +using Microsoft.ReverseProxy.Telemetry; namespace Microsoft.ReverseProxy.Service.Proxy.Infrastructure { @@ -65,6 +66,12 @@ public HttpMessageInvoker CreateClient(ProxyHttpClientContext context) } Log.ProxyClientCreated(_logger, context.ClusterId); + + if (newClientOptions.PropagateActivityContext.GetValueOrDefault(true)) + { + return new HttpMessageInvoker(new ActivityPropagationHandler(handler), disposeHandler: true); + } + return new HttpMessageInvoker(handler, disposeHandler: true); } diff --git a/src/ReverseProxy/Service/RuntimeModel/ClusterProxyHttpClientOptions.cs b/src/ReverseProxy/Service/RuntimeModel/ClusterProxyHttpClientOptions.cs index c4c3aaa71..9e4da1943 100644 --- a/src/ReverseProxy/Service/RuntimeModel/ClusterProxyHttpClientOptions.cs +++ b/src/ReverseProxy/Service/RuntimeModel/ClusterProxyHttpClientOptions.cs @@ -14,12 +14,14 @@ public ClusterProxyHttpClientOptions( SslProtocols? sslProtocols, bool acceptAnyServerCertificate, X509Certificate2 clientCertificate, - int? maxConnectionsPerServer) + int? maxConnectionsPerServer, + bool? propagateActivityContext) { SslProtocols = sslProtocols; DangerousAcceptAnyServerCertificate = acceptAnyServerCertificate; ClientCertificate = clientCertificate; MaxConnectionsPerServer = maxConnectionsPerServer; + PropagateActivityContext = propagateActivityContext; } public SslProtocols? SslProtocols { get; } @@ -30,6 +32,8 @@ public ClusterProxyHttpClientOptions( public int? MaxConnectionsPerServer { get; } + public bool? PropagateActivityContext { get; } + // TODO: Add this property once we have migrated to SDK version that supports it. //public bool? EnableMultipleHttp2Connections { get; } @@ -43,7 +47,8 @@ public bool Equals(ClusterProxyHttpClientOptions other) return SslProtocols == other.SslProtocols && DangerousAcceptAnyServerCertificate == other.DangerousAcceptAnyServerCertificate && EqualityComparer.Default.Equals(ClientCertificate, other.ClientCertificate) && - MaxConnectionsPerServer == other.MaxConnectionsPerServer; + MaxConnectionsPerServer == other.MaxConnectionsPerServer && + PropagateActivityContext == other.PropagateActivityContext; } public override int GetHashCode() diff --git a/src/ReverseProxy/Telemetry/ActivityPropagationHandler.cs b/src/ReverseProxy/Telemetry/ActivityPropagationHandler.cs new file mode 100644 index 000000000..f63eb4dd3 --- /dev/null +++ b/src/ReverseProxy/Telemetry/ActivityPropagationHandler.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.ReverseProxy.Telemetry +{ + /// + /// ActivityPropagationHandler propagates the current Activity to the downstream service + /// + public sealed class ActivityPropagationHandler : DelegatingHandler + { + private const string RequestIdHeaderName = "Request-Id"; + private const string CorrelationContextHeaderName = "Correlation-Context"; + + private const string TraceParentHeaderName = "traceparent"; + private const string TraceStateHeaderName = "tracestate"; + + public ActivityPropagationHandler(HttpMessageHandler innerHandler) : base(innerHandler) + { + } + + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + // This handler is conditionally inserted by the ProxyHttpClientFactory based on the configuration + // If inserted it will insert the necessary headers to propagate the current activity context to + // the downstream service, if there is a current activity + + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + // If we are on at all, we propagate current activity information + var currentActivity = Activity.Current; + if (currentActivity != null) + { + InjectHeaders(currentActivity, request); + } + + return base.SendAsync(request, cancellationToken); + } + + private void InjectHeaders(Activity currentActivity, HttpRequestMessage request) + { + if (currentActivity.IdFormat == ActivityIdFormat.W3C) + { + request.Headers.Remove(TraceParentHeaderName); + request.Headers.Remove(TraceStateHeaderName); + + request.Headers.TryAddWithoutValidation(TraceParentHeaderName, currentActivity.Id); + if (currentActivity.TraceStateString != null) + { + request.Headers.TryAddWithoutValidation(TraceStateHeaderName, currentActivity.TraceStateString); + } + } + else + { + request.Headers.Remove(RequestIdHeaderName); + request.Headers.TryAddWithoutValidation(RequestIdHeaderName, currentActivity.Id); + } + + // we expect baggage to be empty or contain a few items + using (var e = currentActivity.Baggage.GetEnumerator()) + { + if (e.MoveNext()) + { + var baggage = new List(); + do + { + var item = e.Current; + baggage.Add(new NameValueHeaderValue(Uri.EscapeDataString(item.Key), Uri.EscapeDataString(item.Value)).ToString()); + } + while (e.MoveNext()); + request.Headers.TryAddWithoutValidation(CorrelationContextHeaderName, baggage); + } + } + } + } +} diff --git a/test/ReverseProxy.Tests/Common/MockHttpHandler.cs b/test/ReverseProxy.Tests/Common/MockHttpHandler.cs new file mode 100644 index 000000000..a00040c5a --- /dev/null +++ b/test/ReverseProxy.Tests/Common/MockHttpHandler.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.ReverseProxy.Common.Tests +{ + internal class MockHttpHandler : HttpMessageHandler + { + private readonly Func> _func; + + public MockHttpHandler(Func> func) + { + _func = func ?? throw new ArgumentNullException(nameof(func)); + } + + public static HttpMessageInvoker CreateClient(Func> func) + { + var handler = new MockHttpHandler(func); + return new HttpMessageInvoker(handler); + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return _func(request, cancellationToken); + } + } +} diff --git a/test/ReverseProxy.Tests/Configuration/ConfigurationConfigProviderTests.cs b/test/ReverseProxy.Tests/Configuration/ConfigurationConfigProviderTests.cs index 98b70497b..c18903493 100644 --- a/test/ReverseProxy.Tests/Configuration/ConfigurationConfigProviderTests.cs +++ b/test/ReverseProxy.Tests/Configuration/ConfigurationConfigProviderTests.cs @@ -88,6 +88,7 @@ public class ConfigurationConfigProviderTests SslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12, MaxConnectionsPerServer = 10, DangerousAcceptAnyServerCertificate = true, + PropagateActivityContext = true, }, HttpRequest = new ProxyHttpRequestOptions() { @@ -227,7 +228,8 @@ public class ConfigurationConfigProviderTests ""Location"": null, ""AllowInvalid"": null }, - ""MaxConnectionsPerServer"": 10 + ""MaxConnectionsPerServer"": 10, + ""PropagateActivityContext"": true, }, ""HttpRequest"": { ""RequestTimeout"": ""00:01:00"", @@ -618,6 +620,7 @@ private void VerifyValidAbstractConfig(IProxyConfig validConfig, X509Certificate Assert.Equal(cluster1.SessionAffinity.Settings, abstractCluster1.SessionAffinity.Settings); Assert.Same(certificate, abstractCluster1.HttpClient.ClientCertificate); Assert.Equal(cluster1.HttpClient.MaxConnectionsPerServer, abstractCluster1.HttpClient.MaxConnectionsPerServer); + Assert.Equal(cluster1.HttpClient.PropagateActivityContext, abstractCluster1.HttpClient.PropagateActivityContext); Assert.Equal(SslProtocols.Tls11 | SslProtocols.Tls12, abstractCluster1.HttpClient.SslProtocols); Assert.Equal(cluster1.HttpRequest.RequestTimeout, abstractCluster1.HttpRequest.RequestTimeout); Assert.Equal(HttpVersion.Version10, abstractCluster1.HttpRequest.Version); diff --git a/test/ReverseProxy.Tests/Service/Proxy/HttpProxyTests.cs b/test/ReverseProxy.Tests/Service/Proxy/HttpProxyTests.cs index 1eeb25cda..ae85a64db 100644 --- a/test/ReverseProxy.Tests/Service/Proxy/HttpProxyTests.cs +++ b/test/ReverseProxy.Tests/Service/Proxy/HttpProxyTests.cs @@ -1606,27 +1606,6 @@ private static string StreamToString(Stream stream) return reader.ReadToEnd(); } - private class MockHttpHandler : HttpMessageHandler - { - private readonly Func> func; - - private MockHttpHandler(Func> func) - { - this.func = func ?? throw new ArgumentNullException(nameof(func)); - } - - public static HttpMessageInvoker CreateClient(Func> func) - { - var handler = new MockHttpHandler(func); - return new HttpMessageInvoker(handler); - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return func(request, cancellationToken); - } - } - private class DuplexStream : Stream { public DuplexStream(Stream readStream, Stream writeStream) diff --git a/test/ReverseProxy.Tests/Service/Proxy/Infrastructure/ProxyHttpClientFactoryTests.cs b/test/ReverseProxy.Tests/Service/Proxy/Infrastructure/ProxyHttpClientFactoryTests.cs index f9ae89677..f3ad9687d 100644 --- a/test/ReverseProxy.Tests/Service/Proxy/Infrastructure/ProxyHttpClientFactoryTests.cs +++ b/test/ReverseProxy.Tests/Service/Proxy/Infrastructure/ProxyHttpClientFactoryTests.cs @@ -11,6 +11,7 @@ using Microsoft.ReverseProxy.Common.Tests; using Microsoft.ReverseProxy.RuntimeModel; using Microsoft.ReverseProxy.Service.Proxy.Infrastructure; +using Microsoft.ReverseProxy.Telemetry; using Microsoft.ReverseProxy.Utilities.Tests; using Xunit; @@ -44,7 +45,7 @@ public void CreateClient_Works() public void CreateClient_ApplySslProtocols_Success() { var factory = new ProxyHttpClientFactory(Mock>().Object); - var options = new ClusterProxyHttpClientOptions(SslProtocols.Tls12 | SslProtocols.Tls13, default, default, default); + var options = new ClusterProxyHttpClientOptions(SslProtocols.Tls12 | SslProtocols.Tls13, default, default, default, default); var client = factory.CreateClient(new ProxyHttpClientContext { NewOptions = options }); var handler = GetHandler(client); @@ -58,7 +59,7 @@ public void CreateClient_ApplySslProtocols_Success() public void CreateClient_ApplyDangerousAcceptAnyServerCertificate_Success() { var factory = new ProxyHttpClientFactory(Mock>().Object); - var options = new ClusterProxyHttpClientOptions(default, true, default, default); + var options = new ClusterProxyHttpClientOptions(default, true, default, default, default); var client = factory.CreateClient(new ProxyHttpClientContext { NewOptions = options }); var handler = GetHandler(client); @@ -74,7 +75,7 @@ public void CreateClient_ApplyClientCertificate_Success() { var factory = new ProxyHttpClientFactory(Mock>().Object); var certificate = TestResources.GetTestCertificate(); - var options = new ClusterProxyHttpClientOptions(default, default, certificate, default); + var options = new ClusterProxyHttpClientOptions(default, default, certificate, default, default); var client = factory.CreateClient(new ProxyHttpClientContext { NewOptions = options }); var handler = GetHandler(client); @@ -89,7 +90,7 @@ public void CreateClient_ApplyClientCertificate_Success() public void CreateClient_ApplyMaxConnectionsPerServer_Success() { var factory = new ProxyHttpClientFactory(Mock>().Object); - var options = new ClusterProxyHttpClientOptions(default, default, default, 22); + var options = new ClusterProxyHttpClientOptions(default, default, default, 22, default); var client = factory.CreateClient(new ProxyHttpClientContext { NewOptions = options }); var handler = GetHandler(client); @@ -99,14 +100,26 @@ public void CreateClient_ApplyMaxConnectionsPerServer_Success() VerifyDefaultValues(handler, "MaxConnectionsPerServer"); } + [Fact] + public void CreateClient_ApplyPropagateActivityContext_Success() + { + var factory = new ProxyHttpClientFactory(Mock>().Object); + var options = new ClusterProxyHttpClientOptions(default, default, default, default, false); + var client = factory.CreateClient(new ProxyHttpClientContext { NewOptions = options }); + + var handler = GetHandler(client, expectActivityPropagationHandler: false); + + Assert.NotNull(handler); + } + [Fact] public void CreateClient_OldClientExistsNoConfigChange_ReturnsOldInstance() { var factory = new ProxyHttpClientFactory(Mock>().Object); var oldClient = new HttpMessageInvoker(new SocketsHttpHandler()); var clientCertificate = TestResources.GetTestCertificate(); - var oldOptions = new ClusterProxyHttpClientOptions(SslProtocols.Tls11 | SslProtocols.Tls12, true, clientCertificate, 10); - var newOptions = new ClusterProxyHttpClientOptions(SslProtocols.Tls11 | SslProtocols.Tls12, true, clientCertificate, 10); + var oldOptions = new ClusterProxyHttpClientOptions(SslProtocols.Tls11 | SslProtocols.Tls12, true, clientCertificate, 10, true); + var newOptions = new ClusterProxyHttpClientOptions(SslProtocols.Tls11 | SslProtocols.Tls12, true, clientCertificate, 10, true); var oldMetadata = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }; var newMetadata = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }; var context = new ProxyHttpClientContext { ClusterId = "cluster1", OldOptions = oldOptions, OldMetadata = oldMetadata, OldClient = oldClient, NewOptions = newOptions, NewMetadata = newMetadata }; @@ -137,41 +150,53 @@ public static IEnumerable GetChangedHttpClientOptions() return new[] { new object[] { - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null), - new ClusterProxyHttpClientOptions(SslProtocols.Tls11 | SslProtocols.Tls12, true, clientCertificate, null) + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null, false), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11 | SslProtocols.Tls12, true, clientCertificate, null, false) }, new object[] { - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null), - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, false, clientCertificate, null) + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null, false), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, false, clientCertificate, null, false) }, new object[] { - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null), - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, null) + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null, false), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, null, false) }, new object[] { - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, null), - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null) + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, null, false), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null, false) }, new object[] { - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, null), - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, 10) + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, null, false), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, 10, false) }, new object[] { - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, 10), - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null) + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, 10, false), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, null, false) }, new object[] { - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, 10), - new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, 20) + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, 10, false), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, clientCertificate, 20, false) }, + new object[] { + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, 10, true), + new ClusterProxyHttpClientOptions(SslProtocols.Tls11, true, null, 10, false) + } }; } - public static SocketsHttpHandler GetHandler(HttpMessageInvoker client) + public static SocketsHttpHandler GetHandler(HttpMessageInvoker client, bool expectActivityPropagationHandler = true) { var handlerFieldInfo = typeof(HttpMessageInvoker).GetFields(BindingFlags.Instance | BindingFlags.NonPublic).Single(f => f.Name == "_handler"); - var result = (SocketsHttpHandler)handlerFieldInfo.GetValue(client); - return result; + var handler = handlerFieldInfo.GetValue(client); + + if (handler is ActivityPropagationHandler diagnosticsHandler) + { + Assert.True(expectActivityPropagationHandler); + return (SocketsHttpHandler)diagnosticsHandler.InnerHandler; + } + + Assert.False(expectActivityPropagationHandler); + return (SocketsHttpHandler)handler; } private void VerifyDefaultValues(SocketsHttpHandler actualHandler, params string[] skippedExtractors) diff --git a/test/ReverseProxy.Tests/Telemetry/ActivityPropagationHandlerTests.cs b/test/ReverseProxy.Tests/Telemetry/ActivityPropagationHandlerTests.cs new file mode 100644 index 000000000..bee9076a1 --- /dev/null +++ b/test/ReverseProxy.Tests/Telemetry/ActivityPropagationHandlerTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.ReverseProxy.Common.Tests; +using Microsoft.ReverseProxy.Telemetry; +using Xunit; + +namespace Microsoft.ReverseProxy.Telemetry.Tests +{ + public class ActivityPropagationHandlerTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SendAsync_CurrentActivitySet_RequestHeadersSet(bool useW3CFormat) + { + const string TraceStateString = "CustomTraceStateString"; + string expectedId = null; + + var invoker = new HttpMessageInvoker(new ActivityPropagationHandler(new MockHttpHandler( + (HttpRequestMessage request, CancellationToken cancellationToken) => + { + var headers = request.Headers; + + Assert.True(headers.TryGetValues(useW3CFormat ? "traceparent" : "Request-Id", out var values)); + Assert.Equal(expectedId, Assert.Single(values)); + + if (useW3CFormat) + { + Assert.True(headers.TryGetValues("tracestate", out values)); + Assert.Equal(TraceStateString, Assert.Single(values)); + } + + Assert.True(headers.TryGetValues("Correlation-Context", out values)); + Assert.Equal("foo=bar", Assert.Single(values)); + + return Task.FromResult(null); + }))); + + var activity = new Activity("CustomOperation"); + + if (useW3CFormat) + { + activity.SetIdFormat(ActivityIdFormat.W3C); + activity.TraceStateString = TraceStateString; + activity.SetParentId("00-01234567890123456789012345678901-0123456789012345-01"); + } + else + { + activity.SetIdFormat(ActivityIdFormat.Hierarchical); + activity.SetParentId("|root"); + } + + activity.AddBaggage("foo", "bar"); + + activity.Start(); + expectedId = activity.Id; + + await invoker.SendAsync(new HttpRequestMessage(), CancellationToken.None); + + activity.Stop(); + } + } +}