diff --git a/.github/workflows/all_solutions.yml b/.github/workflows/all_solutions.yml index 481015bb8d..f8e744a46a 100644 --- a/.github/workflows/all_solutions.yml +++ b/.github/workflows/all_solutions.yml @@ -423,6 +423,7 @@ jobs: matrix: namespace: [ + AzureServiceBus, CosmosDB, Couchbase, Elasticsearch, diff --git a/FullAgent.sln b/FullAgent.sln index 113ac8211a..e54f549c3a 100644 --- a/FullAgent.sln +++ b/FullAgent.sln @@ -152,10 +152,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Home", "src\Agent\NewRelic\ {338AD83A-ED68-438A-8FB1-E93A3AE87EA8} = {338AD83A-ED68-438A-8FB1-E93A3AE87EA8} {37262C22-6A3A-4AD7-AB78-3853D2B2931D} = {37262C22-6A3A-4AD7-AB78-3853D2B2931D} {3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} = {3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} + {4078E594-E738-48F7-A7ED-B208ADD04900} = {4078E594-E738-48F7-A7ED-B208ADD04900} {44434B8F-EE14-49B0-855D-6EA0B48048BF} = {44434B8F-EE14-49B0-855D-6EA0B48048BF} {4F5D77F3-B41A-44A7-AF10-2D5462CE0162} = {4F5D77F3-B41A-44A7-AF10-2D5462CE0162} {570429FD-C785-4673-82DF-643D06B6DC53} = {570429FD-C785-4673-82DF-643D06B6DC53} {5BBEEC11-B753-4631-BCDD-43BE73B5CCAC} = {5BBEEC11-B753-4631-BCDD-43BE73B5CCAC} + {5D74E5C5-9BA3-423B-86F7-14C2D1A14661} = {5D74E5C5-9BA3-423B-86F7-14C2D1A14661} {614988AD-7C73-4E71-8F95-520D5F485984} = {614988AD-7C73-4E71-8F95-520D5F485984} {64C7F267-5185-4AB7-9EB5-0183D8BB0171} = {64C7F267-5185-4AB7-9EB5-0183D8BB0171} {686AE051-BC8C-4AEC-803D-237AEB807CA9} = {686AE051-BC8C-4AEC-803D-237AEB807CA9} @@ -221,6 +223,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublicApiChangeTests", "tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memcached", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Memcached\Memcached.csproj", "{5D74E5C5-9BA3-423B-86F7-14C2D1A14661}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureServiceBus", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\AzureServiceBus\AzureServiceBus.csproj", "{4078E594-E738-48F7-A7ED-B208ADD04900}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -467,6 +471,10 @@ Global {5D74E5C5-9BA3-423B-86F7-14C2D1A14661}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D74E5C5-9BA3-423B-86F7-14C2D1A14661}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D74E5C5-9BA3-423B-86F7-14C2D1A14661}.Release|Any CPU.Build.0 = Release|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -538,6 +546,7 @@ Global {338AD83A-ED68-438A-8FB1-E93A3AE87EA8} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} {A8F6EFEA-1C31-4461-A7B4-25C30D954EE2} = {E5B988C0-5D19-407E-8210-71FFB90C579A} {5D74E5C5-9BA3-423B-86F7-14C2D1A14661} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} + {4078E594-E738-48F7-A7ED-B208ADD04900} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8B98070-6B8E-403C-A07F-A3F2E4A3A3D0} diff --git a/build/ArtifactBuilder/CoreAgentComponents.cs b/build/ArtifactBuilder/CoreAgentComponents.cs index 94bbafde91..6067173c07 100644 --- a/build/ArtifactBuilder/CoreAgentComponents.cs +++ b/build/ArtifactBuilder/CoreAgentComponents.cs @@ -60,6 +60,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.dll", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.dll", }; var wrapperXmls = new[] @@ -88,6 +89,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.Instrumentation.xml", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.Instrumentation.xml", }; ExtensionXsd = $@"{SourceHomeBuilderPath}\extensions\extension.xsd"; diff --git a/build/ArtifactBuilder/FrameworkAgentComponents.cs b/build/ArtifactBuilder/FrameworkAgentComponents.cs index 7f6bd9666a..f50969353c 100644 --- a/build/ArtifactBuilder/FrameworkAgentComponents.cs +++ b/build/ArtifactBuilder/FrameworkAgentComponents.cs @@ -67,6 +67,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.dll", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.dll", }; var wrapperXmls = new[] @@ -109,6 +110,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.Instrumentation.xml", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.Instrumentation.xml", }; ExtensionXsd = $@"{SourceHomeBuilderPath}\extensions\extension.xsd"; diff --git a/src/Agent/MsiInstaller/Installer/Product.wxs b/src/Agent/MsiInstaller/Installer/Product.wxs index 83adb71f46..c61a5f6c77 100644 --- a/src/Agent/MsiInstaller/Installer/Product.wxs +++ b/src/Agent/MsiInstaller/Installer/Product.wxs @@ -404,6 +404,9 @@ SPDX-License-Identifier: Apache-2.0 + + + @@ -481,6 +484,9 @@ SPDX-License-Identifier: Apache-2.0 + + + @@ -600,6 +606,9 @@ SPDX-License-Identifier: Apache-2.0 + + + @@ -675,6 +684,9 @@ SPDX-License-Identifier: Apache-2.0 + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs index c8fd50cfaf..6bcf07aebc 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs @@ -92,6 +92,9 @@ public interface ITransaction /// /// /// + /// + /// + /// /// /// an opaque object that will be needed when you want to end the segment. ISegment StartMessageBrokerSegment(MethodCall methodCall, MessageBrokerDestinationType destinationType, diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj new file mode 100644 index 0000000000..9e5be87e32 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj @@ -0,0 +1,22 @@ + + + net462;netstandard2.0 + NewRelic.Providers.Wrapper.AzureServiceBus + NewRelic.Providers.Wrapper.AzureServiceBus + Azure Service Bus Wrapper Provider for New Relic .NET Agent + + + + + PreserveNewest + + + + + + + + + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs new file mode 100644 index 0000000000..01e58c33f8 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs @@ -0,0 +1,143 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using NewRelic.Agent.Api; +using NewRelic.Agent.Extensions.Providers.Wrapper; +using NewRelic.Reflection; + +namespace NewRelic.Providers.Wrapper.AzureServiceBus; + +public class AzureServiceBusReceiveWrapper : AzureServiceBusWrapperBase +{ + private static readonly ConcurrentDictionary> _getResultFromGenericTask = new(); + + public override CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) + { + var canWrap = instrumentedMethodInfo.RequestedWrapperName.Equals(nameof(AzureServiceBusReceiveWrapper)); + return new CanWrapResponse(canWrap); + } + + public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) + { + dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; + string queueName = serviceBusReceiver.EntityPath; // some-queue-name + string fqns = serviceBusReceiver.FullyQualifiedNamespace; // some-service-bus-entity.servicebus.windows.net + + MessageBrokerAction action = + instrumentedMethodCall.MethodCall.Method.MethodName switch + { + "ReceiveMessagesAsync" => MessageBrokerAction.Consume, + "ReceiveDeferredMessagesAsync" => MessageBrokerAction.Consume, + "PeekMessagesInternalAsync" => MessageBrokerAction.Peek, + "AbandonMessageAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? Abandon sends the message back to the queue for re-delivery + "CompleteMessageAsync" => MessageBrokerAction.Consume, + "DeadLetterInternalAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? + "DeferMessageAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? + "RenewMessageLockAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? + _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected instrumented method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") + }; + + // start a message broker segment + var segment = transaction.StartMessageBrokerSegment( + instrumentedMethodCall.MethodCall, + MessageBrokerDestinationType.Queue, + action, + BrokerVendorName, + queueName, + serverAddress: fqns ); + + return instrumentedMethodCall.IsAsync + ? + // return an async delegate + Delegates.GetAsyncDelegateFor( + agent, + segment, + false, + HandleResponse, + TaskContinuationOptions.ExecuteSynchronously) + : Delegates.GetDelegateFor( + onFailure: transaction.NoticeError, + onComplete: segment.End, + onSuccess: ExtractDTHeadersIfAvailable); + + void HandleResponse(Task responseTask) + { + try + { + if (responseTask.IsFaulted) + { + transaction.NoticeError(responseTask.Exception); + return; + } + + var resultObj = GetTaskResultFromObject(responseTask); + ExtractDTHeadersIfAvailable(resultObj); + } + finally + { + segment.End(); + } + } + + + + void ExtractDTHeadersIfAvailable(object resultObj) + { + if (resultObj != null) + { + switch (instrumentedMethodCall.MethodCall.Method.MethodName) + { + case "ReceiveMessagesAsync": + case "ReceiveDeferredMessagesAsync": + case "PeekMessagesInternalAsync": + // the response contains a list of messages. + // get the first message from the response and extract DT headers + dynamic messages = resultObj; + if (messages.Count > 0) + { + var msg = messages[0]; + if (msg.ApplicationProperties is ReadOnlyDictionary applicationProperties) + { + transaction.AcceptDistributedTraceHeaders(applicationProperties, ProcessHeaders, TransportType.Queue); + } + } + break; + } + } + IEnumerable ProcessHeaders(ReadOnlyDictionary applicationProperties, string key) + { + var headerValues = new List(); + foreach (var item in applicationProperties) + { + if (item.Key.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + headerValues.Add(item.Value as string); + } + } + + return headerValues; + } + } + } + + private static object GetTaskResultFromObject(object taskObj) + { + var task = taskObj as Task; + if (task == null) + { + return null; + } + if (task.IsFaulted) + { + return null; + } + + var getResponse = _getResultFromGenericTask.GetOrAdd(task.GetType(), t => VisibilityBypasser.Instance.GeneratePropertyAccessor(t, "Result")); + return getResponse(task); + } +} diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs new file mode 100644 index 0000000000..e8d8b4a9a7 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs @@ -0,0 +1,65 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NewRelic.Agent.Api; +using NewRelic.Agent.Extensions.Providers.Wrapper; + +namespace NewRelic.Providers.Wrapper.AzureServiceBus; + +public class AzureServiceBusSendWrapper : AzureServiceBusWrapperBase +{ + public override CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) + { + var canWrap = instrumentedMethodInfo.RequestedWrapperName.Equals(nameof(AzureServiceBusSendWrapper)); + return new CanWrapResponse(canWrap); + } + + public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) + { + dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; + string queueName = serviceBusReceiver.EntityPath; // some-queue-name + string fqns = serviceBusReceiver.FullyQualifiedNamespace; // some-service-bus-entity.servicebus.windows.net + + // determine message broker action based on method name + MessageBrokerAction action = + instrumentedMethodCall.MethodCall.Method.MethodName switch + { + "SendMessagesAsync" => MessageBrokerAction.Produce, + "ScheduleMessagesAsync" => MessageBrokerAction.Produce, + "CancelScheduledMessagesAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? + _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected instrumented method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") + }; + + // start a message broker segment + var segment = transaction.StartMessageBrokerSegment( + instrumentedMethodCall.MethodCall, + MessageBrokerDestinationType.Queue, + action, + BrokerVendorName, + queueName, + serverAddress: fqns); + + if (action == MessageBrokerAction.Produce) + { + dynamic messages = instrumentedMethodCall.MethodCall.MethodArguments[0]; + + // iterate all messages that are being sent, + // insert DT headers into each message + foreach (var message in messages) + { + if (message.ApplicationProperties is IDictionary applicationProperties) + transaction.InsertDistributedTraceHeaders(applicationProperties, ProcessHeaders); + } + + void ProcessHeaders(IDictionary applicationProperties, string key, string value) + { + applicationProperties.Add(key, value); + } + } + + return instrumentedMethodCall.IsAsync ? Delegates.GetAsyncDelegateFor(agent, segment) : Delegates.GetDelegateFor(segment); + } +} diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs new file mode 100644 index 0000000000..97d9332c93 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs @@ -0,0 +1,20 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NewRelic.Agent.Api; +using NewRelic.Agent.Extensions.Providers.Wrapper; + +namespace NewRelic.Providers.Wrapper.AzureServiceBus +{ + public abstract class AzureServiceBusWrapperBase : IWrapper + { + protected const string BrokerVendorName = "AzureServiceBus"; + + public bool IsTransactionRequired => true; // only instrument service bus methods if we're already in a transaction + + public abstract CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo); + + public abstract AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent,ITransaction transaction); + + } +} diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml new file mode 100644 index 0000000000..c1ea3db422 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/README.md b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/README.md new file mode 100644 index 0000000000..c31da8e241 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/README.md @@ -0,0 +1,2 @@ + +Based largely on https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/TROUBLESHOOTING.md#distributed-tracing diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs index b590784f64..62105257df 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs @@ -176,10 +176,11 @@ public void ForceSqlTraces() "explainThreshold", "1"); } - public void SetLogLevel(string level) + public NewRelicConfigModifier SetLogLevel(string level) { CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "log" }, "level", level); + return this; } public void LogToConsole() diff --git a/tests/Agent/IntegrationTests/Shared/AzureServiceBusConfiguration.cs b/tests/Agent/IntegrationTests/Shared/AzureServiceBusConfiguration.cs new file mode 100644 index 0000000000..282441ca92 --- /dev/null +++ b/tests/Agent/IntegrationTests/Shared/AzureServiceBusConfiguration.cs @@ -0,0 +1,33 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace NewRelic.Agent.IntegrationTests.Shared +{ + public class AzureServiceBusConfiguration + { + private static string _connectionString; + + public static string ConnectionString + { + get + { + if (_connectionString == null) + { + try + { + var testConfiguration = IntegrationTestConfiguration.GetIntegrationTestConfiguration("AzureServiceBusTests"); + _connectionString = testConfiguration["ConnectionString"]; + } + catch (Exception ex) + { + throw new Exception("Azure Service Bus configuration is invalid.", ex); + } + } + + return _connectionString; + } + } + } +} diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj index b72937d49d..5614a05652 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj @@ -8,6 +8,9 @@ + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj index 5e11abdd2e..3449479fc6 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -91,7 +91,7 @@ - + @@ -260,7 +260,7 @@ - + @@ -275,6 +275,15 @@ + + + + + + + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs new file mode 100644 index 0000000000..397e2f0953 --- /dev/null +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs @@ -0,0 +1,146 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus.Administration; +using NewRelic.Agent.IntegrationTests.Shared; +using NewRelic.Agent.IntegrationTests.Shared.ReflectionHelpers; +using NewRelic.Api.Agent; + +namespace MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus; + +[Library] +internal class AzureServiceBusExerciser +{ + [LibraryMethod] + public static async Task InitializeQueue(string queueName) + { + var adminClient = new ServiceBusAdministrationClient(AzureServiceBusConfiguration.ConnectionString); + // if the queue exists, delete it and re-create it + if (await adminClient.QueueExistsAsync(queueName)) + { + await adminClient.DeleteQueueAsync(queueName); + } + await adminClient.CreateQueueAsync(queueName); + + } + + [LibraryMethod] + public static async Task DeleteQueue(string queueName) + { + var adminClient = new ServiceBusAdministrationClient(AzureServiceBusConfiguration.ConnectionString); + if (await adminClient.QueueExistsAsync(queueName)) + { + await adminClient.DeleteQueueAsync(queueName); + } + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ExerciseMultipleReceiveOperationsOnAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + + await receiver.PeekMessageAsync(); + + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); + + // renew message lock + await receiver.RenewMessageLockAsync(receivedMessage); + + // defer the message + await receiver.DeferMessageAsync(receivedMessage); + + // receive the deferred message + var deferredMessage = await receiver.ReceiveDeferredMessageAsync(receivedMessage.SequenceNumber); + + // complete the message + await receiver.CompleteMessageAsync(deferredMessage); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ScheduleAndReceiveAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + await using var sender = client.CreateSender(queueName); + + var message = new ServiceBusMessage("Hello world!"); + await sender.ScheduleMessageAsync(message, DateTime.UtcNow.AddSeconds(5)); + + await Task.Delay(TimeSpan.FromSeconds(10)); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete }); + await receiver.ReceiveMessageAsync(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ReceiveAndDeadLetterAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); + + // dead-letter the message + await receiver.DeadLetterMessageAsync(receivedMessage); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ReceiveAndAbandonAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); + + // abandon the message - it'll go back on the queue + await receiver.AbandonMessageAsync(receivedMessage); + + // receive the message again and complete it to remove it from the queue + var receivedMessage2 = await receiver.ReceiveMessageAsync(); + await receiver.CompleteMessageAsync(receivedMessage2); } + + + private static async Task SendAMessage(ServiceBusClient client, string queueName, string messageBody) + { + await using var sender = client.CreateSender(queueName); + var message = new ServiceBusMessage(messageBody); + await sender.SendMessageAsync(message); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task SendAndReceiveAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete }); + await receiver.ReceiveMessageAsync(); + } +} diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln b/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln index ee5a75db36..f7c614bfa8 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln @@ -5,8 +5,17 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnboundedIntegrationTests", "UnboundedIntegrationTests\UnboundedIntegrationTests.csproj", "{1E17AA8A-B8BA-4A64-892C-B58151293142}" ProjectSection(ProjectDependencies) = postProject + {1D93182F-CF1D-47A1-BC0F-4E27DE9ACEF9} = {1D93182F-CF1D-47A1-BC0F-4E27DE9ACEF9} + {3E8423E9-1FBF-451F-8117-88CE3DADEF7F} = {3E8423E9-1FBF-451F-8117-88CE3DADEF7F} + {5350D3F3-CE09-4992-9756-ACCC92A9129C} = {5350D3F3-CE09-4992-9756-ACCC92A9129C} {5CF71C91-EE5E-4F8D-8FEE-AE497A32BF8B} = {5CF71C91-EE5E-4F8D-8FEE-AE497A32BF8B} + {81A13A3C-2008-4756-8A5F-1E1B4F97CCAD} = {81A13A3C-2008-4756-8A5F-1E1B4F97CCAD} + {8D07A4F7-70FD-40D4-AA95-BA9A98F14CD0} = {8D07A4F7-70FD-40D4-AA95-BA9A98F14CD0} + {A8308E64-6659-4EFB-A9C4-0C48D4C99A61} = {A8308E64-6659-4EFB-A9C4-0C48D4C99A61} + {B64766CA-8731-493F-8417-61A70F783EB7} = {B64766CA-8731-493F-8417-61A70F783EB7} + {C544A8F0-A222-42AF-91C8-9F556C293C11} = {C544A8F0-A222-42AF-91C8-9F556C293C11} {EA98CDD2-655D-4239-9B9F-44EB806DEE53} = {EA98CDD2-655D-4239-9B9F-44EB806DEE53} + {F35861EC-9861-4776-AF51-9C2B7F1DBA5C} = {F35861EC-9861-4776-AF51-9C2B7F1DBA5C} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestHelpers", "IntegrationTestHelpers\IntegrationTestHelpers.csproj", "{9AABBBB0-32FC-4DA3-A38C-96A50A7ABAC1}" @@ -140,7 +149,7 @@ Global {C544A8F0-A222-42AF-91C8-9F556C293C11} = {129FF113-1F3A-4DAA-8D6F-287DF67A68FA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {656848E2-BFD2-4092-9328-C6CDF39B1274} EnterpriseLibraryConfigurationToolBinariesPathV6 = packages\EnterpriseLibrary.Common.6.0.1304.0\lib\NET45;packages\EnterpriseLibrary.Data.6.0.1304.0\lib\NET45 + SolutionGuid = {656848E2-BFD2-4092-9328-C6CDF39B1274} EndGlobalSection EndGlobal diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs new file mode 100644 index 0000000000..9b8f41c568 --- /dev/null +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs @@ -0,0 +1,184 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using NewRelic.Agent.IntegrationTestHelpers; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; +using NewRelic.Testing.Assertions; +using Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.UnboundedIntegrationTests.AzureServiceBus; + +public abstract class AzureServiceBusTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture +{ + private readonly TFixture _fixture; + private readonly string _queueName; + + protected AzureServiceBusTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + { + _fixture = fixture; + _fixture.SetTimeout(TimeSpan.FromMinutes(1)); + _fixture.TestLogger = output; + + _queueName = $"test-queue-{Guid.NewGuid()}"; + + _fixture.AddCommand($"AzureServiceBusExerciser InitializeQueue {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ExerciseMultipleReceiveOperationsOnAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ScheduleAndReceiveAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ReceiveAndDeadLetterAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ReceiveAndAbandonAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser DeleteQueue {_queueName}"); + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + + configModifier + .SetLogLevel("finest") + .EnableDistributedTrace() + .ForceTransactionTraces() + .ConfigureFasterMetricsHarvestCycle(20) + .ConfigureFasterSpanEventsHarvestCycle(20) + .ConfigureFasterTransactionTracesHarvestCycle(25) + ; + } + ); + + _fixture.Initialize(); + } + + private readonly string _metricScopeBase = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus.AzureServiceBusExerciser"; + + [Fact] + public void Test() + { + var metrics = _fixture.AgentLog.GetMetrics().ToList(); + + var expectedMetrics = new List + { + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 4}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, + + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 10}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 5, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 3, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, + + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1 }, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage" }, + + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 2 }, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage" }, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage" }, + }; + + var exerciseMultipleReceiveOperationsOnAMessageTransactionEvent = _fixture.AgentLog.TryGetTransactionEvent($"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"); + var scheduleAndReceiveAMessageTransactionEvent = _fixture.AgentLog.TryGetTransactionEvent($"{_metricScopeBase}/ScheduleAndReceiveAMessage"); + + var expectedTransactionTraceSegments = new List + { + $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}" + }; + + var transactionSample = _fixture.AgentLog.TryGetTransactionSample($"{_metricScopeBase}/ScheduleAndReceiveAMessage"); // this will always be the slowest transaction + + var queueProduceSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}"); + var queueConsumeSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}"); + var queuePeekSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}"); + var queuePurgeSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}"); + + var expectedProduceAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + var expectedConsumeAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + + var expectedPeekAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + var expectedPurgeAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + var expectedIntrinsicAttributes = new List { "span.kind", }; + + Assertions.MetricsExist(expectedMetrics, metrics); + + NrAssert.Multiple( + () => Assert.True(exerciseMultipleReceiveOperationsOnAMessageTransactionEvent != null, "ExerciseMultipleReceiveOperationsOnAMessageTransactionEvent should not be null"), + () => Assert.True(scheduleAndReceiveAMessageTransactionEvent != null, "ScheduleAndReceiveAMessageTransactionEvent should not be null"), + () => Assert.True(transactionSample != null, "transactionSample should not be null"), + () => Assertions.TransactionTraceSegmentsExist(expectedTransactionTraceSegments, transactionSample), + + () => Assertions.SpanEventHasAttributes(expectedProduceAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queueProduceSpanEvents), + () => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queueProduceSpanEvents), + + () => Assertions.SpanEventHasAttributes(expectedConsumeAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queueConsumeSpanEvents), + () => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queueConsumeSpanEvents), + + () => Assertions.SpanEventHasAttributes(expectedPeekAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queuePeekSpanEvents), + + () => Assertions.SpanEventHasAttributes(expectedPurgeAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queuePurgeSpanEvents) + ); + } +} + +[NetFrameworkTest] +public class AzureServiceBusTestsFWLatest : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetFrameworkTest] +public class AzureServiceBusTestsFW462 : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusTestsCoreOldest : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusTestsCoreLatest : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusW3CTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusW3CTests.cs new file mode 100644 index 0000000000..4c5d699d04 --- /dev/null +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusW3CTests.cs @@ -0,0 +1,119 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using NewRelic.Agent.IntegrationTestHelpers; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; +using NewRelic.Testing.Assertions; +using Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.UnboundedIntegrationTests.AzureServiceBus; + +public abstract class AzureServiceBusW3CTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture +{ + private readonly TFixture _fixture; + private readonly string _queueName; + + protected AzureServiceBusW3CTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + { + _fixture = fixture; + _fixture.SetTimeout(TimeSpan.FromMinutes(1)); + _fixture.TestLogger = output; + + _queueName = $"test-queue-{Guid.NewGuid()}"; + + _fixture.AddCommand($"AzureServiceBusExerciser InitializeQueue {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser SendAndReceiveAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser DeleteQueue {_queueName}"); + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + + configModifier.ForceTransactionTraces(); + + configModifier.SetOrDeleteDistributedTraceEnabled(true); + configModifier.SetOrDeleteSpanEventsEnabled(true); + } + ); + + _fixture.Initialize(); + } + + private readonly string _metricScopeBase = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus.AzureServiceBusExerciser"; + + [Fact] + public void Test() + { + // attributes + + var headerValueTx = _fixture.AgentLog.TryGetTransactionEvent($"{_metricScopeBase}/SendAndReceiveAMessage"); + + var spanEvents = _fixture.AgentLog.GetSpanEvents(); + + var produceSpan = spanEvents.Where(@event => @event.IntrinsicAttributes["name"].ToString().Contains("MessageBroker/AzureServiceBus/Queue/Produce/Named/")) + .FirstOrDefault(); + + var consumeSpan = spanEvents.Where(@event => @event.IntrinsicAttributes["name"].ToString().Contains("MessageBroker/AzureServiceBus/Queue/Consume/Named/")) + .FirstOrDefault(); + + Assert.Equal(headerValueTx.IntrinsicAttributes["guid"], produceSpan.IntrinsicAttributes["transactionId"]); + Assert.Equal(headerValueTx.IntrinsicAttributes["traceId"], produceSpan.IntrinsicAttributes["traceId"]); + Assert.True(AttributeComparer.IsEqualTo(headerValueTx.IntrinsicAttributes["priority"], produceSpan.IntrinsicAttributes["priority"]), + $"priority: expected: {headerValueTx.IntrinsicAttributes["priority"]}, actual: {produceSpan.IntrinsicAttributes["priority"]}"); + + Assert.Equal(headerValueTx.IntrinsicAttributes["guid"], consumeSpan.IntrinsicAttributes["transactionId"]); + Assert.Equal(headerValueTx.IntrinsicAttributes["traceId"], consumeSpan.IntrinsicAttributes["traceId"]); + Assert.True(AttributeComparer.IsEqualTo(headerValueTx.IntrinsicAttributes["priority"], consumeSpan.IntrinsicAttributes["priority"]), + $"priority: expected: {headerValueTx.IntrinsicAttributes["priority"]}, actual: {consumeSpan.IntrinsicAttributes["priority"]}"); + + // metrics + + var expectedMetrics = new List + { + new Assertions.ExpectedMetric { metricName = $"Supportability/DistributedTrace/CreatePayload/Success", callCount = 1}, + new Assertions.ExpectedMetric { metricName = $"Supportability/TraceContext/Create/Success", callCount = 1}, + }; + + var metrics = _fixture.AgentLog.GetMetrics(); + Assertions.MetricsExist(expectedMetrics, metrics); + } +} + +[NetFrameworkTest] +public class AzureServiceBusW3CTestsFWLatest : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetFrameworkTest] +public class AzureServiceBusW3CTestsFW462 : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusW3CTestsCoreOldest : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusW3CTestsCoreLatest : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +}