Skip to content

Commit ac738ba

Browse files
tippmar-nrchynesNRnr-ahemsath
authored
feat: Instrumentation for Amazon Simple Queuing Service (AWSSDK.SQS) (#2620)
Adds instrumentation for Amazon SQS, including distributed tracing support (Cross-Application Tracing (CAT) is not supported). --------- Co-authored-by: chynesNR <[email protected]> Co-authored-by: Alex Hemsath <[email protected]>
1 parent b24d2c9 commit ac738ba

File tree

37 files changed

+1490
-10
lines changed

37 files changed

+1490
-10
lines changed

.github/workflows/scripts/nugetSlackNotifications/packageInfo.json

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
{
3030
"packageName": "amazon.lambda.sqsevents"
3131
},
32+
{
33+
"packageName": "awssdk.sqs"
34+
},
3235
{
3336
"packageName": "elasticsearch.net"
3437
},

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,6 @@ tests/TestResults/*
162162
/src/Agent/_profilerBuild/x86-Release/NewRelic.Profiler.dll
163163
/tests/Agent/IntegrationTests/ContainerApplications/.env
164164

165+
/tests/Agent/IntegrationTests/LocalStack/volume
166+
*.env
167+
/tests/Agent/IntegrationTests/ContainerApplications/volume/cache

FullAgent.sln

+8
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Home", "src\Agent\NewRelic\
149149
{279F8AD0-C959-476F-BD58-3581D9A33238} = {279F8AD0-C959-476F-BD58-3581D9A33238}
150150
{2E6CF650-CB50-453D-830A-D00F0540FC2C} = {2E6CF650-CB50-453D-830A-D00F0540FC2C}
151151
{2FB30555-65A4-43D7-82AA-56BF203D3A96} = {2FB30555-65A4-43D7-82AA-56BF203D3A96}
152+
{37262C22-6A3A-4AD7-AB78-3853D2B2931D} = {37262C22-6A3A-4AD7-AB78-3853D2B2931D}
152153
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} = {3D69B4C9-FD16-461F-95AF-6FCA6EAA914E}
153154
{44434B8F-EE14-49B0-855D-6EA0B48048BF} = {44434B8F-EE14-49B0-855D-6EA0B48048BF}
154155
{4F5D77F3-B41A-44A7-AF10-2D5462CE0162} = {4F5D77F3-B41A-44A7-AF10-2D5462CE0162}
@@ -211,6 +212,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestSerializationHelpers",
211212
EndProject
212213
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestSerializationHelpers.Test", "tests\Agent\Shared\TestSerializationHelpers.Test\TestSerializationHelpers.Test.csproj", "{DC3E4801-A54A-42A4-AC45-DBD2F0CAE438}"
213214
EndProject
215+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AwsSdk", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\AwsSdk\AwsSdk.csproj", "{37262C22-6A3A-4AD7-AB78-3853D2B2931D}"
216+
EndProject
214217
Global
215218
GlobalSection(SolutionConfigurationPlatforms) = preSolution
216219
Debug|Any CPU = Debug|Any CPU
@@ -441,6 +444,10 @@ Global
441444
{DC3E4801-A54A-42A4-AC45-DBD2F0CAE438}.Debug|Any CPU.Build.0 = Debug|Any CPU
442445
{DC3E4801-A54A-42A4-AC45-DBD2F0CAE438}.Release|Any CPU.ActiveCfg = Release|Any CPU
443446
{DC3E4801-A54A-42A4-AC45-DBD2F0CAE438}.Release|Any CPU.Build.0 = Release|Any CPU
447+
{37262C22-6A3A-4AD7-AB78-3853D2B2931D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
448+
{37262C22-6A3A-4AD7-AB78-3853D2B2931D}.Debug|Any CPU.Build.0 = Debug|Any CPU
449+
{37262C22-6A3A-4AD7-AB78-3853D2B2931D}.Release|Any CPU.ActiveCfg = Release|Any CPU
450+
{37262C22-6A3A-4AD7-AB78-3853D2B2931D}.Release|Any CPU.Build.0 = Release|Any CPU
444451
EndGlobalSection
445452
GlobalSection(SolutionProperties) = preSolution
446453
HideSolutionNode = FALSE
@@ -508,6 +515,7 @@ Global
508515
{C26170C8-0489-42F8-9579-EE8A06D65CC4} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
509516
{173B1B8E-51D9-4639-88E9-B08065C2B47B} = {E5B988C0-5D19-407E-8210-71FFB90C579A}
510517
{DC3E4801-A54A-42A4-AC45-DBD2F0CAE438} = {E5B988C0-5D19-407E-8210-71FFB90C579A}
518+
{37262C22-6A3A-4AD7-AB78-3853D2B2931D} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
511519
EndGlobalSection
512520
GlobalSection(ExtensibilityGlobals) = postSolution
513521
EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35

build/ArtifactBuilder/CoreAgentComponents.cs

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ protected override void CreateAgentComponents()
5757
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AspNetCore6Plus.dll",
5858
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Bedrock.dll",
5959
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsLambda.dll",
60+
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll",
6061
};
6162

6263
var wrapperXmls = new[]
@@ -82,6 +83,7 @@ protected override void CreateAgentComponents()
8283
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AspNetCore6Plus.Instrumentation.xml",
8384
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Bedrock.Instrumentation.xml",
8485
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsLambda.Instrumentation.xml",
86+
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml",
8587
};
8688

8789
ExtensionXsd = $@"{SourceHomeBuilderPath}\extensions\extension.xsd";

build/ArtifactBuilder/FrameworkAgentComponents.cs

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ protected override void CreateAgentComponents()
6464
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.MassTransitLegacy.dll",
6565
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Kafka.dll",
6666
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Bedrock.dll",
67+
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll",
6768
};
6869

6970
var wrapperXmls = new[]
@@ -103,6 +104,7 @@ protected override void CreateAgentComponents()
103104
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.MassTransitLegacy.Instrumentation.xml",
104105
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Kafka.Instrumentation.xml",
105106
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Bedrock.Instrumentation.xml",
107+
$@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml",
106108
};
107109

108110
ExtensionXsd = $@"{SourceHomeBuilderPath}\extensions\extension.xsd";

src/Agent/MsiInstaller/Installer/Product.wxs

+12
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ SPDX-License-Identifier: Apache-2.0
394394
<Component Id="BedrockWrapperComponent" Guid="{C913E912-97D1-4042-8E11-A35E04C6A6E5}">
395395
<File Id="BedrockWrapperFile" Name="NewRelic.Providers.Wrapper.Bedrock.dll" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.Bedrock.dll" />
396396
</Component>
397+
<Component Id="AwsSdkWrapperComponent" Guid="{00D58C03-D517-41CA-BBAD-DAC605EADD3E}">
398+
<File Id="AwsSdkWrapperFile" Name="NewRelic.Providers.Wrapper.AwsSdk.dll" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll" />
399+
</Component>
397400
</ComponentGroup>
398401

399402
<ComponentGroup Id="CoreNewRelic.Agent.Extensions" Directory="CoreProgramFilesExtensionsFolder">
@@ -463,6 +466,9 @@ SPDX-License-Identifier: Apache-2.0
463466
<Component Id="CoreAwsLambdaWrapperComponent" Guid="{778F507F-97D7-48B2-BE51-46D6DAB2D234}">
464467
<File Id="CoreAwsLambdaWrapperFile" Name="NewRelic.Providers.Wrapper.AwsLambda.dll" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.AwsLambda.dll"/>
465468
</Component>
469+
<Component Id="CoreAwsSdkWrapperComponent" Guid="{7C5E31CB-BA85-4E00-BA1B-99FA0B42E414}">
470+
<File Id="CoreAwsSdkWrapperFile" Name="NewRelic.Providers.Wrapper.AwsSdk.dll" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll" />
471+
</Component>
466472
</ComponentGroup>
467473

468474
<!-- Wrapper Instrumentation Files-->
@@ -572,6 +578,9 @@ SPDX-License-Identifier: Apache-2.0
572578
<Component Id="BedrockInstrumentationComponent" Guid="{D6F0A2D6-D8D5-4C61-8056-1C9AA4D07132}">
573579
<File Id="BedrockInstrumentationFile" Name="NewRelic.Providers.Wrapper.Bedrock.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.Bedrock.Instrumentation.xml" />
574580
</Component>
581+
<Component Id="AwsSdkInstrumentationComponent" Guid="{E719FAC8-8D69-41EA-9395-B71868DA7449}">
582+
<File Id="AwsSdkInstrumentationFile" Name="NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml" />
583+
</Component>
575584
</ComponentGroup>
576585

577586
<ComponentGroup Id="CoreNewRelic.Agent.Extensions.Instrumentation" Directory="CoreExtensionsFolder">
@@ -638,6 +647,9 @@ SPDX-License-Identifier: Apache-2.0
638647
<Component Id="CoreAwsLambdaInstrumentationComponent" Guid="{FD3BEB1B-B093-4FF6-A1FA-76E621CF26D6}">
639648
<File Id="CoreAwsLambdaInstrumentationFile" Name="NewRelic.Providers.Wrapper.AwsLambda.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.AwsLambda.Instrumentation.xml"/>
640649
</Component>
650+
<Component Id="CoreAwsSdkInstrumentationComponent" Guid="{CAB15FAE-EF3F-4998-9376-56DA4E11BAEF}">
651+
<File Id="CoreAwsSdkInstrumentationFile" Name="NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml"/>
652+
</Component>
641653
</ComponentGroup>
642654

643655
<!-- Extensions XSD-->

src/Agent/NewRelic/Agent/Core/Agent.cs

+17
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,23 @@ public void RecordLlmEvent(string eventType, IDictionary<string, object> attribu
477477
_customEventTransformer.Transform(eventType, attributes, transaction.Priority);
478478
}
479479

480+
public List<string> GetConfiguredDTHeaders()
481+
{
482+
List<string> headers = [];
483+
if (_configurationService.Configuration.DistributedTracingEnabled)
484+
{
485+
headers.Add(Constants.TraceParentHeaderKey);
486+
headers.Add(Constants.TraceStateHeaderKey);
487+
488+
if (!_configurationService.Configuration.ExcludeNewrelicHeader)
489+
{
490+
headers.Add(Constants.DistributedTracePayloadKeyAllLower);
491+
}
492+
}
493+
494+
return headers;
495+
}
496+
480497
public ISimpleSchedulingService SimpleSchedulingService
481498
{
482499
get { return _simpleSchedulingService; }

src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd

+1-1
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,7 @@
12241224
<xs:attribute name="excludeNewrelicHeader" type="xs:boolean" default="false">
12251225
<xs:annotation>
12261226
<xs:documentation>
1227-
Set this to true to disable adding of the newrelic header to outgoing requests. It is disabled by default.
1227+
Set this to true to exclude the newrelic header from outgoing requests. It is included by default.
12281228
</xs:documentation>
12291229
</xs:annotation>
12301230
</xs:attribute>

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/IAgentExperimental.cs

+2
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,7 @@ public interface IAgentExperimental
5151
ISimpleSchedulingService SimpleSchedulingService { get; }
5252

5353
void RecordLlmEvent(string eventType, IDictionary<string, object> attributes);
54+
55+
List<string> GetConfiguredDTHeaders();
5456
}
5557
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Concurrent;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using NewRelic.Agent.Api;
10+
using NewRelic.Agent.Api.Experimental;
11+
using NewRelic.Agent.Extensions.Providers.Wrapper;
12+
using NewRelic.Reflection;
13+
14+
namespace NewRelic.Agent.Extensions.AwsSdk
15+
{
16+
public static class SqsHelper
17+
{
18+
private static ConcurrentDictionary<Type, Func<object, IDictionary>> _getMessageAttributes = new();
19+
private static Func<object> _messageAttributeValueTypeFactory;
20+
21+
public const string VendorName = "SQS";
22+
23+
private class SqsAttributes
24+
{
25+
public string QueueName { get; }
26+
public string CloudId { get; }
27+
public string Region { get; }
28+
29+
// https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue
30+
public SqsAttributes(string url)
31+
{
32+
if (string.IsNullOrEmpty(url))
33+
{
34+
return;
35+
}
36+
37+
var parts = url.Split('/');
38+
if (parts.Length < 5)
39+
{
40+
return;
41+
}
42+
43+
CloudId = parts[3];
44+
QueueName = parts[4];
45+
46+
var subdomain = parts[2].Split('.');
47+
if (subdomain.Length < 2)
48+
{
49+
return;
50+
}
51+
52+
// subdomain[0] should always be "sqs"
53+
Region = subdomain[1];
54+
}
55+
}
56+
public static ISegment GenerateSegment(ITransaction transaction, MethodCall methodCall, string url, MessageBrokerAction action)
57+
{
58+
var attr = new SqsAttributes(url);
59+
var segment = transaction.StartMessageBrokerSegment(methodCall, MessageBrokerDestinationType.Queue, action, VendorName, attr.QueueName);
60+
segment.GetExperimentalApi().MakeLeaf();
61+
62+
return segment;
63+
}
64+
65+
// SQS allows a maximum of 10 message attributes
66+
private const int MaxSQSMessageAttributes = 10;
67+
68+
public static void InsertDistributedTraceHeaders(ITransaction transaction, object sendMessageRequest, int dtHeaderCount)
69+
{
70+
var headersInserted = 0;
71+
72+
var setHeaders = new Action<object, string, string>((smr, key, value) =>
73+
{
74+
var getMessageAttributes = _getMessageAttributes.GetOrAdd(smr.GetType(), t => VisibilityBypasser.Instance.GeneratePropertyAccessor<IDictionary>(t, "MessageAttributes"));
75+
var messageAttributes = getMessageAttributes(smr);
76+
77+
// if we can't add all DT headers, don't add any
78+
if ((messageAttributes.Count + dtHeaderCount - headersInserted) > MaxSQSMessageAttributes)
79+
return;
80+
81+
// create a new MessageAttributeValue instance
82+
var messageAttributeValueTypeFactory = _messageAttributeValueTypeFactory ??= VisibilityBypasser.Instance.GenerateTypeFactory(smr.GetType().Assembly.FullName, "Amazon.SQS.Model.MessageAttributeValue");
83+
object newMessageAttributeValue = messageAttributeValueTypeFactory.Invoke();
84+
85+
var dataTypePropertySetter = VisibilityBypasser.Instance.GeneratePropertySetter<string>(newMessageAttributeValue, "DataType");
86+
dataTypePropertySetter("String");
87+
88+
var stringValuePropertySetter = VisibilityBypasser.Instance.GeneratePropertySetter<string>(newMessageAttributeValue, "StringValue");
89+
stringValuePropertySetter(value);
90+
91+
messageAttributes.Add(key, newMessageAttributeValue);
92+
93+
++headersInserted;
94+
});
95+
96+
transaction.InsertDistributedTraceHeaders(sendMessageRequest, setHeaders);
97+
98+
}
99+
public static void AcceptDistributedTraceHeaders(ITransaction transaction, dynamic messageAttributes)
100+
{
101+
var getHeaders = new Func<IDictionary, string, IEnumerable<string>>((maDict, key) =>
102+
{
103+
if (!maDict.Contains(key))
104+
return [];
105+
106+
return [(string)((dynamic)maDict[key]).StringValue];
107+
});
108+
109+
transaction.AcceptDistributedTraceHeaders((IDictionary)messageAttributes, getHeaders, TransportType.Queue);
110+
111+
}
112+
}
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>net462;netstandard2.0</TargetFrameworks>
4+
<AssemblyName>NewRelic.Providers.Wrapper.AwsSdk</AssemblyName>
5+
<RootNamespace>NewRelic.Providers.Wrapper.AwsSdk</RootNamespace>
6+
<Description>AWS SDK Wrapper Provider for New Relic .NET Agent</Description>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Content Include="Instrumentation.xml">
10+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
11+
</Content>
12+
</ItemGroup>
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
15+
</ItemGroup>
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\..\NewRelic.Agent.Extensions\NewRelic.Agent.Extensions.csproj" />
18+
</ItemGroup>
19+
</Project>

0 commit comments

Comments
 (0)