diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSMessageAttributeHelper.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSMessageAttributeHelper.cs
new file mode 100644
index 0000000000..5d2b76539a
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSMessageAttributeHelper.cs
@@ -0,0 +1,82 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Linq;
+using Amazon.Runtime;
+using Amazon.Runtime.Internal;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+
+internal class AWSMessageAttributeHelper
+{
+ // SQS/SNS message attributes collection size limit according to
+ // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html and
+ // https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html
+ private const int MaxMessageAttributes = 10;
+
+ private readonly IAWSMessageAttributeFormatter attributeFormatter;
+
+ internal AWSMessageAttributeHelper(IAWSMessageAttributeFormatter attributeFormatter)
+ {
+ this.attributeFormatter = attributeFormatter ?? throw new ArgumentNullException(nameof(attributeFormatter));
+ }
+
+ internal bool TryAddParameter(ParameterCollection parameters, string name, string value)
+ {
+ var index = this.GetNextAttributeIndex(parameters, name);
+ if (!index.HasValue)
+ {
+ return false;
+ }
+ else if (index >= MaxMessageAttributes)
+ {
+ // TODO: Add logging (event source).
+ return false;
+ }
+
+ var attributePrefix = this.attributeFormatter.AttributeNamePrefix + $".{index.Value}";
+ parameters.Add(attributePrefix + ".Name", name.Trim());
+ parameters.Add(attributePrefix + ".Value.DataType", "String");
+ parameters.Add(attributePrefix + ".Value.StringValue", value.Trim());
+
+ return true;
+ }
+
+ private int? GetNextAttributeIndex(ParameterCollection parameters, string name)
+ {
+ var names = parameters.Where(a => this.attributeFormatter.AttributeNameRegex.IsMatch(a.Key));
+ if (!names.Any())
+ {
+ return 1;
+ }
+
+ int? index = 0;
+ foreach (var nameAttribute in names)
+ {
+ if (nameAttribute.Value is StringParameterValue param && param.Value == name)
+ {
+ index = null;
+ break;
+ }
+
+ var currentIndex = this.attributeFormatter.GetAttributeIndex(nameAttribute.Key);
+ index = (currentIndex ?? 0) > index ? currentIndex : index;
+ }
+
+ return ++index;
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSMessagingUtils.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSMessagingUtils.cs
new file mode 100644
index 0000000000..b46ec0fa8b
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSMessagingUtils.cs
@@ -0,0 +1,74 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using Amazon.Runtime;
+using SNS = Amazon.SimpleNotificationService.Model;
+using SQS = Amazon.SQS.Model;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+
+internal static class AWSMessagingUtils
+{
+ private static readonly AWSMessageAttributeHelper SqsAttributeHelper = new(new SqsMessageAttributeFormatter());
+ private static readonly AWSMessageAttributeHelper SnsAttributeHelper = new(new SnsMessageAttributeFormatter());
+
+ internal static void SqsMessageAttributeSetter(IRequestContext context, string name, string value)
+ {
+ var parameters = context.Request?.ParameterCollection;
+ if (parameters == null ||
+ !parameters.ContainsKey("MessageBody") ||
+ context.OriginalRequest is not SQS::SendMessageRequest originalRequest)
+ {
+ return;
+ }
+
+ // Add trace data to parameters collection of the request.
+ if (SqsAttributeHelper.TryAddParameter(parameters, name, value))
+ {
+ // Add trace data to message attributes dictionary of the original request.
+ // This dictionary must be in sync with parameters collection to pass through the MD5 hash matching check.
+ if (!originalRequest.MessageAttributes.ContainsKey(name))
+ {
+ originalRequest.MessageAttributes.Add(
+ name, new SQS::MessageAttributeValue
+ { DataType = "String", StringValue = value });
+ }
+ }
+ }
+
+ internal static void SnsMessageAttributeSetter(IRequestContext context, string name, string value)
+ {
+ var parameters = context.Request?.ParameterCollection;
+ if (parameters == null ||
+ !parameters.ContainsKey("Message") ||
+ context.OriginalRequest is not SNS::PublishRequest originalRequest)
+ {
+ return;
+ }
+
+ if (SnsAttributeHelper.TryAddParameter(parameters, name, value))
+ {
+ // Add trace data to message attributes dictionary of the original request.
+ // This dictionary must be in sync with parameters collection to pass through the MD5 hash matching check.
+ if (!originalRequest.MessageAttributes.ContainsKey(name))
+ {
+ originalRequest.MessageAttributes.Add(
+ name, new SNS::MessageAttributeValue
+ { DataType = "String", StringValue = value });
+ }
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSServiceHelper.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSServiceHelper.cs
index b36964b4ea..8c11883331 100644
--- a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSServiceHelper.cs
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSServiceHelper.cs
@@ -14,7 +14,6 @@
// limitations under the License.
//
-using System;
using System.Collections.Generic;
using Amazon.Runtime;
@@ -24,8 +23,8 @@ internal class AWSServiceHelper
{
internal static IReadOnlyDictionary ServiceParameterMap = new Dictionary()
{
- { DynamoDbService, "TableName" },
- { SQSService, "QueueUrl" },
+ { AWSServiceType.DynamoDbService, "TableName" },
+ { AWSServiceType.SQSService, "QueueUrl" },
};
internal static IReadOnlyDictionary ParameterAttributeMap = new Dictionary()
@@ -34,9 +33,6 @@ internal class AWSServiceHelper
{ "QueueUrl", AWSSemanticConventions.AttributeAWSSQSQueueUrl },
};
- private const string DynamoDbService = "DynamoDBv2";
- private const string SQSService = "SQS";
-
internal static string GetAWSServiceName(IRequestContext requestContext)
=> Utils.RemoveAmazonPrefixFromServiceName(requestContext.Request.ServiceName);
@@ -47,7 +43,4 @@ internal static string GetAWSOperationName(IRequestContext requestContext)
var operationName = Utils.RemoveSuffix(completeRequestName, suffix);
return operationName;
}
-
- internal static bool IsDynamoDbService(string service)
- => DynamoDbService.Equals(service, StringComparison.OrdinalIgnoreCase);
}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSServiceType.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSServiceType.cs
new file mode 100644
index 0000000000..24223f6f4b
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSServiceType.cs
@@ -0,0 +1,35 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+
+internal class AWSServiceType
+{
+ internal const string DynamoDbService = "DynamoDBv2";
+ internal const string SQSService = "SQS";
+ internal const string SNSService = "SimpleNotificationService";
+
+ internal static bool IsDynamoDbService(string service)
+ => DynamoDbService.Equals(service, StringComparison.OrdinalIgnoreCase);
+
+ internal static bool IsSqsService(string service)
+ => SQSService.Equals(service, StringComparison.OrdinalIgnoreCase);
+
+ internal static bool IsSnsService(string service)
+ => SNSService.Equals(service, StringComparison.OrdinalIgnoreCase);
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSTracingPipelineHandler.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSTracingPipelineHandler.cs
index bbce612cf6..427e043c6b 100644
--- a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSTracingPipelineHandler.cs
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/AWSTracingPipelineHandler.cs
@@ -196,10 +196,18 @@ private void AddRequestSpecificInformation(Activity activity, IRequestContext re
}
}
- if (AWSServiceHelper.IsDynamoDbService(service))
+ if (AWSServiceType.IsDynamoDbService(service))
{
activity.SetTag(SemanticConventions.AttributeDbSystem, AWSSemanticConventions.AttributeValueDynamoDb);
}
+ else if (AWSServiceType.IsSqsService(service))
+ {
+ Propagators.DefaultTextMapPropagator.Inject(new PropagationContext(activity.Context, Baggage.Current), requestContext, AWSMessagingUtils.SqsMessageAttributeSetter);
+ }
+ else if (AWSServiceType.IsSnsService(service))
+ {
+ Propagators.DefaultTextMapPropagator.Inject(new PropagationContext(activity.Context, Baggage.Current), requestContext, AWSMessagingUtils.SnsMessageAttributeSetter);
+ }
}
private void AddStatusCodeToActivity(Activity activity, int status_code)
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/IAWSMessageAttributeFormatter.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/IAWSMessageAttributeFormatter.cs
new file mode 100644
index 0000000000..536dbe8b50
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/IAWSMessageAttributeFormatter.cs
@@ -0,0 +1,27 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Text.RegularExpressions;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+internal interface IAWSMessageAttributeFormatter
+{
+ Regex AttributeNameRegex { get; }
+
+ string AttributeNamePrefix { get; }
+
+ int? GetAttributeIndex(string attributeName);
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/SnsMessageAttributeFormatter.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/SnsMessageAttributeFormatter.cs
new file mode 100644
index 0000000000..439dc469c8
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/SnsMessageAttributeFormatter.cs
@@ -0,0 +1,34 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Text.RegularExpressions;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+
+internal class SnsMessageAttributeFormatter : IAWSMessageAttributeFormatter
+{
+ Regex IAWSMessageAttributeFormatter.AttributeNameRegex => new(@"MessageAttributes\.entry\.\d+\.Name");
+
+ string IAWSMessageAttributeFormatter.AttributeNamePrefix => "MessageAttributes.entry";
+
+ int? IAWSMessageAttributeFormatter.GetAttributeIndex(string attributeName)
+ {
+ var parts = attributeName.Split('.');
+ return (parts.Length >= 3 && int.TryParse(parts[2], out int index)) ?
+ index :
+ null;
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/SqsMessageAttributeFormatter.cs b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/SqsMessageAttributeFormatter.cs
new file mode 100644
index 0000000000..d3bfc9e4e8
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/Implementation/SqsMessageAttributeFormatter.cs
@@ -0,0 +1,34 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Text.RegularExpressions;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+
+internal class SqsMessageAttributeFormatter : IAWSMessageAttributeFormatter
+{
+ Regex IAWSMessageAttributeFormatter.AttributeNameRegex => new(@"MessageAttribute\.\d+\.Name");
+
+ string IAWSMessageAttributeFormatter.AttributeNamePrefix => "MessageAttribute";
+
+ int? IAWSMessageAttributeFormatter.GetAttributeIndex(string attributeName)
+ {
+ var parts = attributeName.Split('.');
+ return (parts.Length >= 2 && int.TryParse(parts[1], out int index)) ?
+ index :
+ null;
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.AWS/OpenTelemetry.Contrib.Instrumentation.AWS.csproj b/src/OpenTelemetry.Contrib.Instrumentation.AWS/OpenTelemetry.Contrib.Instrumentation.AWS.csproj
index 1a7b34718c..38b0be96b9 100644
--- a/src/OpenTelemetry.Contrib.Instrumentation.AWS/OpenTelemetry.Contrib.Instrumentation.AWS.csproj
+++ b/src/OpenTelemetry.Contrib.Instrumentation.AWS/OpenTelemetry.Contrib.Instrumentation.AWS.csproj
@@ -8,12 +8,14 @@
+
+
-
-
+
+
diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs
index bda6d7677b..c5b794ffd0 100644
--- a/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs
+++ b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs
@@ -20,6 +20,8 @@
using System.Linq;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
+using Amazon.Lambda.SNSEvents;
+using Amazon.Lambda.SQSEvents;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Contrib.Extensions.AWSXRay.Trace;
@@ -74,6 +76,18 @@ internal static ActivityContext ExtractParentContext(TInput input)
case APIGatewayHttpApiV2ProxyRequest apiGatewayHttpApiV2ProxyRequest:
propagationContext = Propagators.DefaultTextMapPropagator.Extract(default, apiGatewayHttpApiV2ProxyRequest, GetHeaderValues);
break;
+ case SQSEvent sqsEvent:
+ propagationContext = AWSMessagingUtils.ExtractParentContext(sqsEvent);
+ break;
+ case SQSEvent.SQSMessage sqsMessage:
+ propagationContext = AWSMessagingUtils.ExtractParentContext(sqsMessage);
+ break;
+ case SNSEvent snsEvent:
+ propagationContext = AWSMessagingUtils.ExtractParentContext(snsEvent);
+ break;
+ case SNSEvent.SNSRecord snsRecord:
+ propagationContext = AWSMessagingUtils.ExtractParentContext(snsRecord);
+ break;
}
return propagationContext.ActivityContext;
diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSMessagingUtils.cs b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSMessagingUtils.cs
new file mode 100644
index 0000000000..f9663b9f88
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSMessagingUtils.cs
@@ -0,0 +1,141 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Collections.Generic;
+using System.Linq;
+using Amazon.Lambda.SNSEvents;
+using Amazon.Lambda.SQSEvents;
+using Newtonsoft.Json;
+using OpenTelemetry.Context.Propagation;
+
+namespace OpenTelemetry.Instrumentation.AWSLambda.Implementation;
+
+internal class AWSMessagingUtils
+{
+ // SNS attribute types: https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html
+ private const string SnsAttributeTypeString = "String";
+ private const string SnsAttributeTypeStringArray = "String.Array";
+ private const string SnsMessageAttributes = "MessageAttributes";
+
+ internal static PropagationContext ExtractParentContext(SQSEvent sqsEvent)
+ {
+ if (sqsEvent == null)
+ {
+ return default;
+ }
+
+ // We assume there can be only one parent that's why we consider only a single (the last) record as the carrier.
+ var message = sqsEvent.Records.LastOrDefault();
+ return ExtractParentContext(message);
+ }
+
+ internal static PropagationContext ExtractParentContext(SQSEvent.SQSMessage sqsMessage)
+ {
+ if (sqsMessage == null)
+ {
+ return default;
+ }
+
+ // SQS subscribed to SNS topic with raw delivery disabled case, i.e. SNS record serialized into SQS body.
+ // https://docs.aws.amazon.com/sns/latest/dg/sns-large-payload-raw-message-delivery.html
+ SNSEvent.SNSMessage snsMessage = GetSnsMessage(sqsMessage);
+ return snsMessage != null ?
+ ExtractParentContext(snsMessage) :
+ Propagators.DefaultTextMapPropagator.Extract(default, sqsMessage.MessageAttributes, SqsMessageAttributeGetter);
+ }
+
+ internal static PropagationContext ExtractParentContext(SNSEvent snsEvent)
+ {
+ if (snsEvent == null)
+ {
+ return default;
+ }
+
+ // We assume there can be only one parent that's why we consider only a single (the last) record as the carrier.
+ var record = snsEvent.Records.LastOrDefault();
+ return ExtractParentContext(record);
+ }
+
+ internal static PropagationContext ExtractParentContext(SNSEvent.SNSRecord record)
+ {
+ return (record?.Sns != null) ?
+ Propagators.DefaultTextMapPropagator.Extract(default, record.Sns.MessageAttributes, SnsMessageAttributeGetter) :
+ default;
+ }
+
+ internal static PropagationContext ExtractParentContext(SNSEvent.SNSMessage message)
+ {
+ return (message != null) ?
+ Propagators.DefaultTextMapPropagator.Extract(default, message.MessageAttributes, SnsMessageAttributeGetter) :
+ default;
+ }
+
+ private static IEnumerable SqsMessageAttributeGetter(IDictionary attributes, string attributeName)
+ {
+ SQSEvent.MessageAttribute attribute = attributes.GetValueByKeyIgnoringCase(attributeName);
+ if (attribute == null)
+ {
+ return null;
+ }
+
+ return attribute.StringValue != null ?
+ new[] { attribute.StringValue } :
+ attribute.StringListValues;
+ }
+
+ private static IEnumerable SnsMessageAttributeGetter(IDictionary attributes, string attributeName)
+ {
+ SNSEvent.MessageAttribute attribute = attributes.GetValueByKeyIgnoringCase(attributeName);
+ if (attribute == null)
+ {
+ return null;
+ }
+
+ switch (attribute.Type)
+ {
+ case SnsAttributeTypeString when attribute.Value != null:
+ return new[] { attribute.Value };
+ case SnsAttributeTypeStringArray when attribute.Value != null:
+ // Multiple values are stored as CSV (https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html).
+ return attribute.Value.Split(',');
+ default:
+ return null;
+ }
+ }
+
+ private static SNSEvent.SNSMessage GetSnsMessage(SQSEvent.SQSMessage sqsMessage)
+ {
+ SNSEvent.SNSMessage snsMessage = null;
+
+ var body = sqsMessage.Body;
+ if (body != null &&
+ body.TrimStart().StartsWith("{") &&
+ body.Contains(SnsMessageAttributes))
+ {
+ try
+ {
+ snsMessage = JsonConvert.DeserializeObject(body);
+ }
+ catch (JsonException)
+ {
+ // TODO: log exception.
+ return null;
+ }
+ }
+
+ return snsMessage;
+ }
+}
diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj b/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj
index 14a98b2675..e2ae6b6736 100644
--- a/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj
+++ b/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj
@@ -10,13 +10,15 @@
+
+
-
+
diff --git a/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Implementation/AWSMessageAttributeHelperTests.cs b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Implementation/AWSMessageAttributeHelperTests.cs
new file mode 100644
index 0000000000..adc4c70167
--- /dev/null
+++ b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Implementation/AWSMessageAttributeHelperTests.cs
@@ -0,0 +1,94 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Collections.Generic;
+using Amazon.Runtime.Internal;
+using OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+using Xunit;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Tests.Implementation;
+
+public class AWSMessageAttributeHelperTests
+{
+ [Theory]
+ [InlineData(AWSServiceType.SQSService)]
+ [InlineData(AWSServiceType.SNSService)]
+ public void TryAddParameter_CollectionSizeReachesLimit_ParameterNotAdded(string serviceType)
+ {
+ var helper = TestsHelper.CreateAttributeHelper(serviceType);
+ var parameters = new ParameterCollection();
+ parameters.AddStringParameters(TestsHelper.GetNamePrefix(serviceType), 10);
+
+ var added = helper.TryAddParameter(parameters, "testName", "testValue");
+
+ Assert.False(added, "Expected parameter not to be added.");
+ Assert.Equal(30, parameters.Count);
+ }
+
+ [Theory]
+ [InlineData(AWSServiceType.SQSService)]
+ [InlineData(AWSServiceType.SNSService)]
+ public void TryAddParameter_EmptyCollection_ParameterAdded(string serviceType)
+ {
+ var helper = TestsHelper.CreateAttributeHelper(serviceType);
+ var parameters = new ParameterCollection();
+ var expectedParameters = new List>()
+ {
+ new KeyValuePair("testName", "testValue"),
+ };
+
+ var added = helper.TryAddParameter(parameters, "testName", "testValue");
+
+ Assert.True(added, "Expected parameter to be added.");
+ TestsHelper.AssertStringParameters(expectedParameters, parameters, TestsHelper.GetNamePrefix(serviceType));
+ }
+
+ [Theory]
+ [InlineData(AWSServiceType.SQSService)]
+ [InlineData(AWSServiceType.SNSService)]
+ public void TryAddParameter_CollectionWithSingleParameter_SecondParameterAdded(string serviceType)
+ {
+ var helper = TestsHelper.CreateAttributeHelper(serviceType);
+ var parameters = new ParameterCollection();
+ parameters.AddStringParameter("testNameA", "testValueA", TestsHelper.GetNamePrefix(serviceType), 1);
+
+ var expectedParameters = new List>()
+ {
+ new KeyValuePair("testNameA", "testValueA"),
+ new KeyValuePair("testNameB", "testValueB"),
+ };
+
+ var added = helper.TryAddParameter(parameters, "testNameB", "testValueB");
+
+ Assert.True(added, "Expected parameter to be added.");
+ TestsHelper.AssertStringParameters(expectedParameters, parameters, TestsHelper.GetNamePrefix(serviceType));
+ }
+
+ [Theory]
+ [InlineData(AWSServiceType.SQSService)]
+ [InlineData(AWSServiceType.SNSService)]
+ public void TryAddParameter_ParameterPresentInCollection_ParameterNotAdded(string serviceType)
+ {
+ var helper = TestsHelper.CreateAttributeHelper(serviceType);
+ var parameters = new ParameterCollection();
+ parameters.AddStringParameter("testNameA", "testValueA", TestsHelper.GetNamePrefix(serviceType), 1);
+
+ var added = helper.TryAddParameter(parameters, "testNameA", "testValueA");
+
+ Assert.False(added, "Expected parameter not to be added.");
+ Assert.Equal(3, parameters.Count);
+ }
+}
diff --git a/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Implementation/TestsHelper.cs b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Implementation/TestsHelper.cs
new file mode 100644
index 0000000000..b41f2acbf5
--- /dev/null
+++ b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Implementation/TestsHelper.cs
@@ -0,0 +1,82 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Generic;
+using Amazon.Runtime;
+using Amazon.Runtime.Internal;
+using OpenTelemetry.Contrib.Instrumentation.AWS.Implementation;
+using Xunit;
+
+namespace OpenTelemetry.Contrib.Instrumentation.AWS.Tests.Implementation;
+internal static class TestsHelper
+{
+ internal static AWSMessageAttributeHelper CreateAttributeHelper(string serviceType)
+ {
+ return serviceType switch
+ {
+ AWSServiceType.SQSService => new(new SqsMessageAttributeFormatter()),
+ AWSServiceType.SNSService => new(new SnsMessageAttributeFormatter()),
+ _ => throw new NotSupportedException($"Tests for service type {serviceType} not supported."),
+ };
+ }
+
+ internal static string GetNamePrefix(string serviceType)
+ {
+ return serviceType switch
+ {
+ AWSServiceType.SQSService => "MessageAttribute",
+ AWSServiceType.SNSService => "MessageAttributes.entry",
+ _ => throw new NotSupportedException($"Tests for service type {serviceType} not supported."),
+ };
+ }
+
+ internal static void AddStringParameter(this ParameterCollection parameters, string name, string value, string namePrefix, int index)
+ {
+ var prefix = $"{namePrefix}.{index}";
+ parameters.Add($"{prefix}.Name", name);
+ parameters.Add($"{prefix}.Value.DataType", "String");
+ parameters.Add($"{prefix}.Value.StringValue", value);
+ }
+
+ internal static void AddStringParameters(this ParameterCollection parameters, string namePrefix, int count)
+ {
+ for (int i = 1; i <= count; i++)
+ {
+ AddStringParameter(parameters, $"name{i}", $"value{i}", namePrefix, i);
+ }
+ }
+
+ internal static void AssertStringParameters(List> expectedParameters, ParameterCollection actualParameters, string namePrefix)
+ {
+ Assert.Equal(expectedParameters.Count * 3, actualParameters.Count);
+
+ for (int i = 0; i < expectedParameters.Count; i++)
+ {
+ var prefix = $"{namePrefix}.{i + 1}";
+ static string Value(ParameterValue p) => (p as StringParameterValue).Value;
+
+ Assert.True(actualParameters.ContainsKey($"{prefix}.Name"));
+ Assert.Equal(expectedParameters[i].Key, Value(actualParameters[$"{prefix}.Name"]));
+
+ Assert.True(actualParameters.ContainsKey($"{prefix}.Value.DataType"));
+ Assert.Equal("String", Value(actualParameters[$"{prefix}.Value.DataType"]));
+
+ Assert.True(actualParameters.ContainsKey($"{prefix}.Value.StringValue"));
+ Assert.Equal(expectedParameters[i].Value, Value(actualParameters[$"{prefix}.Value.StringValue"]));
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/OpenTelemetry.Contrib.Instrumentation.AWS.Tests.csproj b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/OpenTelemetry.Contrib.Instrumentation.AWS.Tests.csproj
index e8fa57bb14..a30b9e351f 100644
--- a/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/OpenTelemetry.Contrib.Instrumentation.AWS.Tests.csproj
+++ b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/OpenTelemetry.Contrib.Instrumentation.AWS.Tests.csproj
@@ -8,11 +8,10 @@
+
-
-
-
+
all
diff --git a/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Tools/Utils.cs b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Tools/Utils.cs
index 635f5efabf..65fd24ff65 100644
--- a/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Tools/Utils.cs
+++ b/test/OpenTelemetry.Contrib.Instrumentation.AWS.Tests/Tools/Utils.cs
@@ -71,6 +71,6 @@ public static IEnumerable FindResourceName(Predicate match)
public static object GetTagValue(Activity activity, string tagName)
{
- return Implementation.Utils.GetTagValue(activity, tagName);
+ return AWS.Implementation.Utils.GetTagValue(activity, tagName);
}
}