Skip to content

Commit 063dd0b

Browse files
Data streams support in sqs without raw message delivery (#8071)
* Parse JSON * put config behind ff * Add test * Add test case when there are no message attributes * declare static object mapper * add test for non json message * remove log & widen exception
1 parent 4524f7b commit 063dd0b

File tree

4 files changed

+150
-1
lines changed

4 files changed

+150
-1
lines changed

dd-java-agent/instrumentation/aws-java-sqs-1.0/src/main/java/datadog/trace/instrumentation/aws/v1/sqs/MessageExtractAdapter.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@
22

33
import com.amazonaws.services.sqs.model.Message;
44
import com.amazonaws.services.sqs.model.MessageAttributeValue;
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import datadog.trace.api.Config;
58
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
69
import datadog.trace.bootstrap.instrumentation.messaging.DatadogAttributeParser;
10+
import java.io.IOException;
11+
import java.nio.ByteBuffer;
12+
import java.util.Base64;
713
import java.util.Map;
814
import org.slf4j.Logger;
915
import org.slf4j.LoggerFactory;
1016

1117
public final class MessageExtractAdapter implements AgentPropagation.ContextVisitor<Message> {
1218
private static final Logger log = LoggerFactory.getLogger(MessageExtractAdapter.class);
13-
1419
public static final MessageExtractAdapter GETTER = new MessageExtractAdapter();
20+
private static final ObjectMapper MAPPER = new ObjectMapper();
21+
public static final boolean SHOULD_EXTRACT_CONTEXT_FROM_BODY =
22+
Config.get().isSqsBodyPropagationEnabled();
1523

1624
@Override
1725
public void forEachKey(Message carrier, AgentPropagation.KeyClassifier classifier) {
@@ -28,6 +36,31 @@ public void forEachKey(Message carrier, AgentPropagation.KeyClassifier classifie
2836
} else if ("Binary".equals(datadog.getDataType())) {
2937
DatadogAttributeParser.forEachProperty(classifier, datadog.getBinaryValue());
3038
}
39+
} else if (SHOULD_EXTRACT_CONTEXT_FROM_BODY) {
40+
try {
41+
this.forEachKeyInBody(carrier.getBody(), classifier);
42+
} catch (Throwable e) {
43+
log.debug("Error extracting Datadog context from SQS message body", e);
44+
}
45+
}
46+
}
47+
48+
public void forEachKeyInBody(String body, AgentPropagation.KeyClassifier classifier)
49+
throws IOException {
50+
// Parse the JSON string into a JsonNode
51+
JsonNode rootNode = MAPPER.readTree(body);
52+
53+
// Navigate to MessageAttributes._datadog
54+
JsonNode messageAttributes = rootNode.path("MessageAttributes").path("_datadog");
55+
56+
// Extract Value and Type
57+
String value = messageAttributes.path("Value").asText();
58+
String type = messageAttributes.path("Type").asText();
59+
if ("String".equals(type)) {
60+
DatadogAttributeParser.forEachProperty(classifier, value);
61+
} else if ("Binary".equals(type)) {
62+
ByteBuffer decodedValue = ByteBuffer.wrap(Base64.getDecoder().decode(value));
63+
DatadogAttributeParser.forEachProperty(classifier, decodedValue);
3164
}
3265
}
3366

dd-java-agent/instrumentation/aws-java-sqs-1.0/src/test/groovy/SqsClientTest.groovy

+108
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ abstract class SqsClientTest extends VersionedNamingTestBase {
4444
// Set a service name that gets sorted early with SORT_BY_NAMES
4545
injectSysConfig(GeneralConfig.SERVICE_NAME, "A-service")
4646
injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, isDataStreamsEnabled().toString())
47+
injectSysConfig("trace.sqs.body.propagation.enabled", "true")
4748
}
4849

4950
@Shared
@@ -511,6 +512,22 @@ class SqsClientV0DataStreamsTest extends SqsClientTest {
511512
}
512513

513514
class SqsClientV1DataStreamsForkedTest extends SqsClientTest {
515+
private static final String MESSAGE_WITH_ATTRIBUTES = "{\n" +
516+
" \"Type\" : \"Notification\",\n" +
517+
" \"MessageId\" : \"cb337e2a-1c06-5629-86f5-21fba14fb492\",\n" +
518+
" \"TopicArn\" : \"arn:aws:sns:us-east-1:223300679234:dsm-dev-sns-topic\",\n" +
519+
" \"Message\" : \"Some message\",\n" +
520+
" \"Timestamp\" : \"2024-12-10T03:52:41.662Z\",\n" +
521+
" \"SignatureVersion\" : \"1\",\n" +
522+
" \"Signature\" : \"ZsEewd5gNR8jLC08TenLDp5rhdBtGIdAzWk7j6fzDyUzb/t56R9SBPrNJtjsPO8Ep8v/iGs/wSFUrnm+Zh3N1duc3alR1bKTAbDlzbEBxaHsGcNwzMz14JF7bKLE+3nPIi0/kT8EuIiRevGqPtCG/NEe9oW2dOyvYZvt+L7GC0AS9L0yJp8Ag7NkgNvYbIqPeKcjj8S7WRiV95Useg0P46e5pn5FXmNKPlpIqYN28jnrTZHWUDTiO5RE7lfFcdH2tBaYSR9F/PwA1Mga5NrTxlZp/yDoOlOUFj5zXAtDDpjNTcR48jAu66Mpi1wom7Si7vc3ZsYzN2Z2ig/aUJLaNA==\",\n" +
523+
" \"SigningCertURL\" : \"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-some-pem.pem\",\n" +
524+
" \"UnsubscribeURL\" : \"https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:7270067952343:dsm-dev-sns-topic:0d82adcc-5b42-4035-81c4-22ccd126fc41\",\n" +
525+
" \"MessageAttributes\" : {\n" +
526+
" \"_datadog\" : {\"Type\":\"Binary\",\"Value\":\"eyJ4LWRhdGFkb2ctdHJhY2UtaWQiOiI1ODExMzQ0MDA5MDA2NDM1Njk0IiwieC1kYXRhZG9nLXBhcmVudC1pZCI6Ijc3MjQzODMxMjg4OTMyNDAxNDAiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIwIiwieC1kYXRhZG9nLXRhZ3MiOiJfZGQucC50aWQ9Njc1N2JiMDkwMDAwMDAwMCIsInRyYWNlcGFyZW50IjoiMDAtNjc1N2JiMDkwMDAwMDAwMDUwYTYwYTk2MWM2YzRkNmUtNmIzMjg1ODdiYWIzYjM0Yy0wMCIsInRyYWNlc3RhdGUiOiJkZD1zOjA7cDo2YjMyODU4N2JhYjNiMzRjO3QudGlkOjY3NTdiYjA5MDAwMDAwMDAiLCJkZC1wYXRod2F5LWN0eC1iYXNlNjQiOiJkdzdKcjU0VERkcjA5cFRyOVdUMDlwVHI5V1E9In0=\"}\n" +
527+
" }\n" +
528+
"}"
529+
530+
514531
@Override
515532
String expectedOperation(String awsService, String awsOperation) {
516533
if (awsService == "SQS") {
@@ -537,6 +554,97 @@ class SqsClientV1DataStreamsForkedTest extends SqsClientTest {
537554
int version() {
538555
1
539556
}
557+
558+
def "Data streams context extracted from message body"() {
559+
setup:
560+
def client = AmazonSQSClientBuilder.standard()
561+
.withEndpointConfiguration(endpoint)
562+
.withCredentials(credentialsProvider)
563+
.build()
564+
def queueUrl = client.createQueue('somequeue').queueUrl
565+
TEST_WRITER.clear()
566+
567+
when:
568+
injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, "false")
569+
client.sendMessage(queueUrl, MESSAGE_WITH_ATTRIBUTES)
570+
injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, "true")
571+
def messages = client.receiveMessage(queueUrl).messages
572+
messages.forEach {/* consume to create message spans */ }
573+
574+
TEST_DATA_STREAMS_WRITER.waitForGroups(1)
575+
576+
then:
577+
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == -2734507826469073289 }
578+
579+
verifyAll(first) {
580+
edgeTags == ["direction:in", "topic:somequeue", "type:sqs"]
581+
edgeTags.size() == 3
582+
}
583+
584+
cleanup:
585+
client.shutdown()
586+
}
587+
588+
def "Data streams context not extracted from message body when message attributes are not present"() {
589+
setup:
590+
def client = AmazonSQSClientBuilder.standard()
591+
.withEndpointConfiguration(endpoint)
592+
.withCredentials(credentialsProvider)
593+
.build()
594+
def queueUrl = client.createQueue('somequeue').queueUrl
595+
TEST_WRITER.clear()
596+
597+
when:
598+
injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, "false")
599+
client.sendMessage(queueUrl, '{"Message": "sometext"}')
600+
injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, "true")
601+
def messages = client.receiveMessage(queueUrl).messages
602+
messages.forEach {}
603+
604+
TEST_DATA_STREAMS_WRITER.waitForGroups(1)
605+
606+
then:
607+
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 }
608+
609+
verifyAll(first) {
610+
edgeTags == ["direction:in", "topic:somequeue", "type:sqs"]
611+
edgeTags.size() == 3
612+
}
613+
614+
cleanup:
615+
client.shutdown()
616+
}
617+
618+
619+
def "Data streams context not extracted from message body when message is not a Json"() {
620+
setup:
621+
def client = AmazonSQSClientBuilder.standard()
622+
.withEndpointConfiguration(endpoint)
623+
.withCredentials(credentialsProvider)
624+
.build()
625+
def queueUrl = client.createQueue('somequeue').queueUrl
626+
TEST_WRITER.clear()
627+
628+
when:
629+
injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, "false")
630+
client.sendMessage(queueUrl, '{"Message": "not a json"')
631+
injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, "true")
632+
def messages = client.receiveMessage(queueUrl).messages
633+
messages.forEach {}
634+
635+
TEST_DATA_STREAMS_WRITER.waitForGroups(1)
636+
637+
then:
638+
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 }
639+
640+
verifyAll(first) {
641+
edgeTags == ["direction:in", "topic:somequeue", "type:sqs"]
642+
edgeTags.size() == 3
643+
}
644+
645+
cleanup:
646+
client.shutdown()
647+
}
540648
}
541649

542650

dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java

+2
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,7 @@ public final class TraceInstrumentationConfig {
159159
/** If set, the instrumentation will set its resource name on the local root too. */
160160
public static final String AXIS_PROMOTE_RESOURCE_NAME = "trace.axis.promote.resource-name";
161161

162+
public static final String SQS_BODY_PROPAGATION_ENABLED = "trace.sqs.body.propagation.enabled";
163+
162164
private TraceInstrumentationConfig() {}
163165
}

internal-api/src/main/java/datadog/trace/api/Config.java

+6
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ public static String getHostName() {
413413

414414
private final boolean awsPropagationEnabled;
415415
private final boolean sqsPropagationEnabled;
416+
private final boolean sqsBodyPropagationEnabled;
416417

417418
private final boolean kafkaClientPropagationEnabled;
418419
private final Set<String> kafkaClientPropagationDisabledTopics;
@@ -1571,6 +1572,7 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment())
15711572

15721573
awsPropagationEnabled = isPropagationEnabled(true, "aws", "aws-sdk");
15731574
sqsPropagationEnabled = isPropagationEnabled(true, "sqs");
1575+
sqsBodyPropagationEnabled = configProvider.getBoolean(SQS_BODY_PROPAGATION_ENABLED, false);
15741576

15751577
kafkaClientPropagationEnabled = isPropagationEnabled(true, "kafka", "kafka.client");
15761578
kafkaClientPropagationDisabledTopics =
@@ -3048,6 +3050,10 @@ public boolean isSqsPropagationEnabled() {
30483050
return sqsPropagationEnabled;
30493051
}
30503052

3053+
public boolean isSqsBodyPropagationEnabled() {
3054+
return sqsBodyPropagationEnabled;
3055+
}
3056+
30513057
public boolean isKafkaClientPropagationEnabled() {
30523058
return kafkaClientPropagationEnabled;
30533059
}

0 commit comments

Comments
 (0)