diff --git a/src/content/docs/apm/agents/java-agent/instrumentation/instrument-aws-sqs-message-queues.mdx b/src/content/docs/apm/agents/java-agent/instrumentation/instrument-aws-sqs-message-queues.mdx new file mode 100644 index 00000000000..6f472b84b38 --- /dev/null +++ b/src/content/docs/apm/agents/java-agent/instrumentation/instrument-aws-sqs-message-queues.mdx @@ -0,0 +1,145 @@ +--- +title: Use AWS SQS for message queues +tags: + - Agents + - Java agent + - Instrumentation +metaDescription: "How to improve instrumentation of the Amazon SQS SDK for Java applications using New Relic's APM" +redirects: + - /docs/agents/java-agent/instrumentation//instrument-aws-sqs-message-queues +freshnessValidatedDate: never +--- + +The Java Agent suports instrumentation for AWS SQS message queues. Our SQS instrumentation creates message broker traces but customers +may want extra information about the usage of the SQS SDK. This document provides examples on how to do it. + +To see all supported SQS libraries, check the [Java compatibility and requirements page](/docs/agents/java-agent/getting-started/compatibility-requirements-java-agent). + +## View SQS Distributed Tracing + +Out of the box, our AWS SQS SDK instrumentation adds distributed trace headers as message attributes to SQS messages. +However for message recieven operations, we don't offer an out of the box way to accept these headers. +Instead we recommend cxustomers to use custom instrumentation to read the distributed trace headers. + +Here is an example of how to read the distributed trace headers from an SQS message using the V2 SDK: + + +```java + public void v2Retrieve(SqsClient sqsClient, String queueUrl) { + List msgs = receiveMessages(queueUrl); + for (Message msg : msgs) { + handleMessage(msg); + } + } + + // Since calling the SDK to recieve messages is a batch call, it is not recommended to accept distributed trace headers + // in the same transaction as the SDK call. Distributed traces can only have one parent span. + @Trace(dispatcher = true, metricName = "v2/receiveMessages") + public List receiveMessages(SqsClient sqsClient, String queueUrl) { + ReceiveMessageRequest request = ReceiveMessageRequest.builder() + .queueUrl(queueUrl) + .maxNumberOfMessages(10) + .build(); + + List messages = sqsClient.receiveMessage(request).messages(); + if (messages.isEmpty()) { + logger.info("[v2 API] No messages received"); + } + return messages; + } + + // Here we accept distributed trace headers in a seperate transaction from the SDK call. + @Trace(dispatcher = true, metricName = "v2/handleMessage") + public void handleMessage(Message msg, SqsClient sqsClient, String queueUrl) { + // We use a wrapper class that helps extract the distributed trace headers + SQSReceivedMessageHeaders headers = new SQSReceivedMessageHeaders(msg); + // Here we accept the distributed trace headers + NewRelic.getAgent().getTransaction().acceptDistributedTraceHeaders(TransportType.Other, headers); + + + // Process your message here + + DeleteMessageRequest deleteMessageRequest = DeleteMessageRequest.builder() + .queueUrl(queueUrl) + .receiptHandle(msg.receiptHandle()) + .build(); + sqsClient.deleteMessage(deleteMessageRequest); + } +``` + +Below is the wrapper class `SQSReceivedMessageHeaders` that extracts the distributed trace headers from the SQS message: + +```java +import com.newrelic.api.agent.HeaderType; +import com.newrelic.api.agent.Headers; +import software.amazon.awssdk.services.sqs.model.Message; +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SQSReceivedMessageHeaders implements Headers { + + private Message message = null; + + public SQSReceivedMessageHeaders(Message msg) { + message = msg; + } + + @Override + public HeaderType getHeaderType() { + return HeaderType.MESSAGE; + } + + @Override + public String getHeader(String name) { + String value = null; + Map msgAttributes = message.messageAttributes(); + MessageAttributeValue msgAttributeValue = msgAttributes.get(name); + if(msgAttributeValue != null) { + if(msgAttributeValue.dataType().equalsIgnoreCase("string")) { + value = msgAttributeValue.stringValue(); + } + } + return value; + } + + @Override + public Collection getHeaders(String name) { + List list = new ArrayList(); + String value = getHeader(name); + if(value != null && !value.isEmpty()) { + list.add(value); + } + return list; + } + + @Override + public void setHeader(String name, String value) { + // Not supported + } + + @Override + public void addHeader(String name, String value) { + // Not supported + } + + @Override + public Collection getHeaderNames() { + if(message != null) { + Map attributes = message.messageAttributes(); + if(attributes != null) return attributes.keySet(); + } + return Collections.emptyList(); + } + + @Override + public boolean containsHeader(String name) { + return getHeaderNames().contains(name); + } + +} +``` \ No newline at end of file diff --git a/src/nav/apm.yml b/src/nav/apm.yml index b06a8c3dc18..39f9ae5072c 100644 --- a/src/nav/apm.yml +++ b/src/nav/apm.yml @@ -180,6 +180,8 @@ pages: path: /docs/new-relic-solutions/best-practices-guides/full-stack-observability/browser-monitoring-best-practices-java - title: Instrument Kafka message queues path: /docs/apm/agents/java-agent/instrumentation/java-agent-instrument-kafka-message-queues + - title: Instrument Amazon SQS + path: /docs/apm/agents/java-agent/instrumentation/instrument-aws-sqs-message-queues - title: Use RabbitMQ or JMS for message queues path: /docs/apm/agents/java-agent/instrumentation/use-rabbitmq-or-jms-message-queues - title: Instrumentation modules