From ec5de822083a40fb58341ed230d8d8f554bdf575 Mon Sep 17 00:00:00 2001
From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com>
Date: Thu, 31 Oct 2024 16:46:26 -0500
Subject: [PATCH 01/10] WIP - instrumentation for Azure Service Bus.
---
FullAgent.sln | 12 +-
.../AzureServiceBus/AzureServiceBus.csproj | 22 ++++
.../AzureServiceBusReceiveWrapper.cs | 117 ++++++++++++++++++
.../AzureServiceBusSendWrapper.cs | 66 ++++++++++
.../AzureServiceBus/Instrumentation.xml | 31 +++++
5 files changed, 246 insertions(+), 2 deletions(-)
create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj
create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs
create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs
create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml
diff --git a/FullAgent.sln b/FullAgent.sln
index 45d2bc0a90..d0e2164e47 100644
--- a/FullAgent.sln
+++ b/FullAgent.sln
@@ -152,6 +152,7 @@ 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}
@@ -219,7 +220,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunction", "src\Agent\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublicApiChangeTests", "tests\Agent\UnitTests\PublicApiChangeTests\PublicApiChangeTests.csproj", "{A8F6EFEA-1C31-4461-A7B4-25C30D954EE2}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memcached", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Memcached\Memcached.csproj", "{5D74E5C5-9BA3-423B-86F7-14C2D1A14661}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memcached", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Memcached\Memcached.csproj", "{5D74E5C5-9BA3-423B-86F7-14C2D1A14661}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureServiceBus", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\AzureServiceBus\AzureServiceBus.csproj", "{4078E594-E738-48F7-A7ED-B208ADD04900}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -467,6 +470,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,10 +545,11 @@ 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
- EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35
SolutionGuid = {D8B98070-6B8E-403C-A07F-A3F2E4A3A3D0}
+ EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35
EndGlobalSection
GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = FullAgent.vsmdi
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..94956c0bb9
--- /dev/null
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs
@@ -0,0 +1,117 @@
+// 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 : IWrapper
+{
+ private const string BrokerVendorName = "AzureServiceBus";
+ private static ConcurrentDictionary> _getResultFromGenericTask = new();
+
+ public bool IsTransactionRequired => false;
+
+ public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo)
+ {
+ var canWrap = instrumentedMethodInfo.RequestedWrapperName.Equals(nameof(AzureServiceBusReceiveWrapper));
+ return new CanWrapResponse(canWrap);
+ }
+
+ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
+ {
+ dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget;
+ string queueName = serviceBusReceiver.EntityPath;
+ string identifier = serviceBusReceiver.Identifier;
+ string fqns = serviceBusReceiver.FullyQualifiedNamespace;
+
+ // create a transaction
+ transaction = agent.CreateTransaction(
+ destinationType: MessageBrokerDestinationType.Queue,
+ brokerVendorName: BrokerVendorName,
+ destination: queueName);
+
+ if (instrumentedMethodCall.IsAsync)
+ {
+ transaction.AttachToAsync();
+ transaction.DetachFromPrimary(); //Remove from thread-local type storage
+ }
+
+ // start a message broker segment
+ var segment = transaction.StartMessageBrokerSegment(
+ instrumentedMethodCall.MethodCall,
+ MessageBrokerDestinationType.Queue,
+ MessageBrokerAction.Consume,
+ BrokerVendorName, queueName);
+
+ // return an async delegate
+ return Delegates.GetAsyncDelegateFor(
+ agent,
+ segment,
+ true,
+ HandleResponse,
+ TaskContinuationOptions.ExecuteSynchronously);
+
+ void HandleResponse(Task responseTask)
+ {
+ try
+ {
+ if (responseTask.IsFaulted)
+ {
+ // TODO: handle error here?
+ return;
+ }
+
+ // get the first message from the response and extract DT headers
+ // per https://github.com/Azure/azure-sdk-for-net/issues/33652#issuecomment-1451320679
+ // the headers are in the ApplicationProperties dictionary
+ dynamic resultObj = GetTaskResult(responseTask);
+ if (resultObj != null && resultObj.Count > 0)
+ {
+ var msg = resultObj[0];
+ if (msg.ApplicationProperties is ReadOnlyDictionary applicationProperties)
+ {
+ transaction.AcceptDistributedTraceHeaders(applicationProperties, ProcessHeaders, TransportType.Queue);
+ }
+ }
+ }
+ finally
+ {
+ segment.End();
+ transaction.End();
+ }
+ }
+ }
+
+ private 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 GetTaskResult(object task)
+ {
+ if (((Task)task).IsFaulted)
+ {
+ return null;
+ }
+
+ var getResponse = _getResultFromGenericTask.GetOrAdd(task.GetType(), t => VisibilityBypasser.Instance.GeneratePropertyAccessor