Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote target #745

Merged
merged 8 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ private AwsAttributeKeys() {}
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/8710

static final AttributeKey<String> AWS_BUCKET_NAME = AttributeKey.stringKey("aws.bucket.name");
static final AttributeKey<String> AWS_QUEUE_URL = AttributeKey.stringKey("aws.queue.url");
static final AttributeKey<String> AWS_QUEUE_NAME = AttributeKey.stringKey("aws.queue.name");
static final AttributeKey<String> AWS_STREAM_NAME = AttributeKey.stringKey("aws.stream.name");
static final AttributeKey<String> AWS_TABLE_NAME = AttributeKey.stringKey("aws.table.name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,7 @@
import static io.opentelemetry.semconv.SemanticAttributes.PEER_SERVICE;
import static io.opentelemetry.semconv.SemanticAttributes.RPC_METHOD;
import static io.opentelemetry.semconv.SemanticAttributes.RPC_SERVICE;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_SERVICE;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_TARGET;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.*;
atshaw43 marked this conversation as resolved.
Show resolved Hide resolved
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE;
Expand Down Expand Up @@ -144,13 +135,27 @@ private static void setRemoteTarget(SpanData span, AttributesBuilder builder) {
*/
private static Optional<String> getRemoteTarget(SpanData span) {
if (isKeyPresent(span, AWS_BUCKET_NAME)) {
return Optional.ofNullable(span.getAttributes().get(AWS_BUCKET_NAME));
} else if (isKeyPresent(span, AWS_QUEUE_NAME)) {
return Optional.ofNullable(span.getAttributes().get(AWS_QUEUE_NAME));
} else if (isKeyPresent(span, AWS_STREAM_NAME)) {
return Optional.ofNullable(span.getAttributes().get(AWS_STREAM_NAME));
} else if (isKeyPresent(span, AWS_TABLE_NAME)) {
return Optional.ofNullable(span.getAttributes().get(AWS_TABLE_NAME));
return Optional.ofNullable("::s3:::" + span.getAttributes().get(AWS_BUCKET_NAME));
}

if (isKeyPresent(span, AWS_QUEUE_URL)) {
String arn = SqsUrlParser.getSqsRemoteTarget(span.getAttributes().get(AWS_QUEUE_URL));

if (arn != null) {
return Optional.ofNullable(arn);
}
}

if (isKeyPresent(span, AWS_QUEUE_NAME)) {
return Optional.ofNullable("::sqs:::" + span.getAttributes().get(AWS_QUEUE_NAME));
}
atshaw43 marked this conversation as resolved.
Show resolved Hide resolved

if (isKeyPresent(span, AWS_STREAM_NAME)) {
return Optional.ofNullable("::kinesis:::stream/" + span.getAttributes().get(AWS_STREAM_NAME));
}

if (isKeyPresent(span, AWS_TABLE_NAME)) {
return Optional.ofNullable("::dynamodb:::table/" + span.getAttributes().get(AWS_TABLE_NAME));
}
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Copyright Amazon.com, Inc. or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

package software.amazon.opentelemetry.javaagent.providers;

public class SqsUrlParser {
private static final char ARN_DELIMETER = ':';
private static final String HTTP_SCHEMA = "http://";
private static final String HTTPS_SCHEMA = "https://";

public static String getSqsRemoteTarget(String sqsUrl) {
sqsUrl = stripSchemaFromUrl(sqsUrl);

if (!isSqsUrl(sqsUrl) && !isLegacySqsUrl(sqsUrl) && !isCustomUrl(sqsUrl)) {
return null;
}

String region = getRegion(sqsUrl);
String accountId = getAccountId(sqsUrl);
String partition = getPartition(sqsUrl);
String queueName = getQueueName(sqsUrl);

StringBuilder remoteTarget = new StringBuilder();

if (region == null && accountId == null && partition == null && queueName == null) {
return null;
}

if (region != null && accountId != null && partition != null && queueName != null) {
remoteTarget.append("arn");
}

remoteTarget
.append(ARN_DELIMETER)
.append(nullToEmpty(partition))
.append(ARN_DELIMETER)
.append("sqs")
.append(ARN_DELIMETER)
.append(nullToEmpty(region))
.append(ARN_DELIMETER)
.append(nullToEmpty(accountId))
.append(ARN_DELIMETER)
.append(queueName);

return remoteTarget.toString();
}

private static String stripSchemaFromUrl(String url) {
return url.replace(HTTP_SCHEMA, "").replace(HTTPS_SCHEMA, "");
}

private static String getRegion(String sqsUrl) {
if (sqsUrl == null) {
return null;
}

if (sqsUrl.startsWith("queue.amazonaws.com/")) {
return "us-east-1";
} else if (isSqsUrl(sqsUrl)) {
return getRegionFromSqsUrl(sqsUrl);
} else if (isLegacySqsUrl(sqsUrl)) {
return getRegionFromLegacySqsUrl(sqsUrl);
} else {
return null;
}
}

private static boolean isSqsUrl(String sqsUrl) {
String[] split = sqsUrl.split("/");

return split.length == 3
&& split[0].startsWith("sqs.")
&& split[0].endsWith(".amazonaws.com")
&& isAccountId(split[1])
&& isValidQueueName(split[2]);
}

private static boolean isLegacySqsUrl(String sqsUrl) {
String[] split = sqsUrl.split("/");

return split.length == 3
&& split[0].endsWith(".queue.amazonaws.com")
&& isAccountId(split[1])
&& isValidQueueName(split[2]);
}

private static boolean isCustomUrl(String sqsUrl) {
String[] split = sqsUrl.split("/");
return split.length == 3 && isAccountId(split[1]) && isValidQueueName(split[2]);
}

private static boolean isValidQueueName(String input) {
if (input.length() == 0 || input.length() > 80) {
return false;
}

for (Character c : input.toCharArray()) {
if (c != '_' && c != '-' && !Character.isAlphabetic(c) && !Character.isDigit(c)) {
return false;
}
}

return true;
}

private static boolean isAccountId(String input) {
if (input.length() != 12) {
return false;
}

try {
Long.valueOf(input);
} catch (Exception e) {
return false;
}

return true;
}

private static String getRegionFromSqsUrl(String sqsUrl) {
String[] split = sqsUrl.split("\\.");

if (split.length >= 2) {
return split[1];
}

return null;
}

private static String getRegionFromLegacySqsUrl(String sqsUrl) {
String[] split = sqsUrl.split("\\.");
return split[0];
}

private static String getAccountId(String sqsUrl) {
if (sqsUrl == null) {
return null;
}

String[] split = sqsUrl.split("/");
if (split.length >= 2) {
return split[1];
}

return null;
}

private static String getPartition(String sqsUrl) {
String region = getRegion(sqsUrl);

if (region == null) {
return null;
}

if (region.startsWith("us-gov-")) {
return "aws-us-gov";
} else if (region.startsWith("cn-")) {
return "aws-cn";
} else {
return "aws";
}
}

private static String getQueueName(String sqsUrl) {
String[] split = sqsUrl.split("/");

if (split.length >= 3) {
return split[2];
}

return null;
}

private static String nullToEmpty(String input) {
return input == null ? "" : input;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_SERVICE;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_TARGET;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME;
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.*;
import static software.amazon.opentelemetry.javaagent.providers.MetricAttributeGenerator.DEPENDENCY_METRIC;
import static software.amazon.opentelemetry.javaagent.providers.MetricAttributeGenerator.SERVICE_METRIC;

Expand Down Expand Up @@ -550,25 +541,125 @@ public void testPeerServiceDoesNotOverrideAwsRemoteService() {
public void testClientSpanWithRemoteTargetAttributes() {
// Validate behaviour of aws bucket name attribute, then remove it.
mockAttribute(AWS_BUCKET_NAME, "aws_s3_bucket_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_s3_bucket_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "::s3:::aws_s3_bucket_name");
mockAttribute(AWS_BUCKET_NAME, null);

// Validate behaviour of AWS_QUEUE_NAME attribute, then remove it.
mockAttribute(AWS_QUEUE_NAME, "aws_queue_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_queue_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "::sqs:::aws_queue_name");
mockAttribute(AWS_QUEUE_NAME, null);

// Validate behaviour of having both AWS_QUEUE_NAME and AWS_QUEUE_URL attribute, then remove
// them.
mockAttribute(AWS_QUEUE_URL, "https://sqs.us-east-2.amazonaws.com/123456789012/Queue");
mockAttribute(AWS_QUEUE_NAME, "aws_queue_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "arn:aws:sqs:us-east-2:123456789012:Queue");
mockAttribute(AWS_QUEUE_URL, null);
mockAttribute(AWS_QUEUE_NAME, null);

// Valid queue name with invalid queue URL, we should default to using the queue name.
mockAttribute(AWS_QUEUE_URL, "invalidUrl");
mockAttribute(AWS_QUEUE_NAME, "aws_queue_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "::sqs:::aws_queue_name");
mockAttribute(AWS_QUEUE_URL, null);
mockAttribute(AWS_QUEUE_NAME, null);

// Validate behaviour of AWS_STREAM_NAME attribute, then remove it.
mockAttribute(AWS_STREAM_NAME, "aws_stream_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_stream_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "::kinesis:::stream/aws_stream_name");
mockAttribute(AWS_STREAM_NAME, null);

// Validate behaviour of AWS_TABLE_NAME attribute, then remove it.
mockAttribute(AWS_TABLE_NAME, "aws_table_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_table_name");
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "::dynamodb:::table/aws_table_name");
mockAttribute(AWS_TABLE_NAME, null);
}

@Test
public void testClientSpanSqsBasicUrls() {
testSqsUrl(
"https://sqs.us-east-1.amazonaws.com/123412341234/Q_Name-5",
"arn:aws:sqs:us-east-1:123412341234:Q_Name-5");
testSqsUrl(
"https://sqs.af-south-1.amazonaws.com/112233445566/Queue",
"arn:aws:sqs:af-south-1:112233445566:Queue");
testSqsUrl(
"http://sqs.eu-west-3.amazonaws.com/112233445566/FirstQueue",
"arn:aws:sqs:eu-west-3:112233445566:FirstQueue");
testSqsUrl(
"sqs.sa-east-1.amazonaws.com/123456781234/SecondQueue",
"arn:aws:sqs:sa-east-1:123456781234:SecondQueue");
}

@Test
public void testClientSpanSqsUsGovUrls() {
testSqsUrl(
"https://sqs.us-gov-east-1.amazonaws.com/123456789012/MyQueue",
"arn:aws-us-gov:sqs:us-gov-east-1:123456789012:MyQueue");
testSqsUrl(
"sqs.us-gov-west-1.amazonaws.com/112233445566/Queue",
"arn:aws-us-gov:sqs:us-gov-west-1:112233445566:Queue");
}

@Test
public void testClientSpanSqsLegacyFormatUrls() {
testSqsUrl(
"https://ap-northeast-2.queue.amazonaws.com/123456789012/MyQueue",
"arn:aws:sqs:ap-northeast-2:123456789012:MyQueue");
testSqsUrl(
"http://cn-northwest-1.queue.amazonaws.com/123456789012/MyQueue",
"arn:aws-cn:sqs:cn-northwest-1:123456789012:MyQueue");
testSqsUrl(
"http://cn-north-1.queue.amazonaws.com/123456789012/MyQueue",
"arn:aws-cn:sqs:cn-north-1:123456789012:MyQueue");
testSqsUrl(
"ap-south-1.queue.amazonaws.com/123412341234/MyLongerQueueNameHere",
"arn:aws:sqs:ap-south-1:123412341234:MyLongerQueueNameHere");
testSqsUrl(
"https://us-gov-east-1.queue.amazonaws.com/123456789012/MyQueue",
"arn:aws-us-gov:sqs:us-gov-east-1:123456789012:MyQueue");
}

@Test
public void testClientSpanSqsNorthVirginiaUrl() {
testSqsUrl(
"https://queue.amazonaws.com/123456789012/MyQueue",
"arn:aws:sqs:us-east-1:123456789012:MyQueue");
}

@Test
public void testClientSpanSqsCustomUrls() {
testSqsUrl("http://127.0.0.1:1212/123456789012/MyQueue", "::sqs::123456789012:MyQueue");
testSqsUrl("https://127.0.0.1:1212/123412341234/RRR", "::sqs::123412341234:RRR");
testSqsUrl("127.0.0.1:1212/123412341234/QQ", "::sqs::123412341234:QQ");
testSqsUrl("https://amazon.com/123412341234/BB", "::sqs::123412341234:BB");
}

@Test
public void testClientSpanSqsLongUrls() {
String queueName = "a".repeat(80);
testSqsUrl(
"http://127.0.0.1:1212/123456789012/" + queueName, "::sqs::123456789012:" + queueName);

String queueNameTooLong = "a".repeat(81);
testSqsUrl("http://127.0.0.1:1212/123456789012/" + queueNameTooLong, null);
}

@Test
public void testClientSpanSqsInvalidOrEmptyUrls() {
testSqsUrl(null, null);
testSqsUrl("", null);
testSqsUrl("invalidUrl", null);
testSqsUrl("https://www.amazon.com", null);
testSqsUrl("https://sqs.us-east-1.amazonaws.com/123412341234/.", null);
}

private void testSqsUrl(String sqsUrl, String expectedRemoteTarget) {
mockAttribute(AWS_QUEUE_URL, sqsUrl);
validateRemoteTargetAttributes(AWS_REMOTE_TARGET, expectedRemoteTarget);
mockAttribute(AWS_QUEUE_URL, null);
}

@Test
public void testHttpStatusAttributeNotAwsSdk() {
validateHttpStatusWithThrowable(new ThrowableWithMethodGetStatusCode(500), null);
Expand Down
Loading