Repository: elastic/apm-agent-python

Issue:
Error in instrumentation on boto3 sqs client function send_message_batch
**Bug**
Error in instrumentation on boto3 sqs client function send_message_batch when do not define non required parameter MessageAttributes in entries.

[SQS.Client.send_message_batch](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.send_message_batch)


**To Reproduce**
instrumet code
start a transaction (without a started transaction code works fine)
send_bactch_message without Entries.MessageAttributes parameter

```
import uuid
import boto3
import elasticapm
from elasticapm import Client


if __name__ == '__main__':
    elasticapm.instrument()
    apm_client = Client(service_name=apm_info['SERVICE_NAME'],
                        server_url=apm_info['SERVER_URL'],
                        secret_token=apm_info['SECRET_TOKEN'],
                        environment=apm_info['Environment_name'])

    elasticapm.instrument()
    apm_client.begin_transaction(transaction_type="script")
    try:
        sqs_client = boto3.client('sqs')
        sqs_url = MY_SQS_URL
        entries = [dict(Id=uuid.uuid4().hex, MessageBody=str(i)) for i in range(10)]
        sqs_client.send_message_batch(QueueUrl=sqs_url, Entries=entries)
    except:
        apm_client.apm_client.capture_exception()
    apm_client.end_transaction(name="producer", result='success')
```
**workaround**
Definining Parameter Entries.MessageAttributes={} instrumentation works fine.

```
import uuid
import boto3
import elasticapm
from elasticapm import Client


if __name__ == '__main__':
    elasticapm.instrument()
    apm_client = Client(service_name=apm_info['SERVICE_NAME'],
                        server_url=apm_info['SERVER_URL'],
                        secret_token=apm_info['SECRET_TOKEN'],
                        environment=apm_info['Environment_name'])

    elasticapm.instrument()
    apm_client.begin_transaction(transaction_type="script")
    try:
        sqs_client = boto3.client('sqs')
        sqs_url = MY_SQS_URL
        entries = [dict(Id=uuid.uuid4().hex, MessageBody=str(i), MessageAttributes={}) for i in range(10)]
        sqs_client.send_message_batch(QueueUrl=sqs_url, Entries=entries)
    except:
        apm_client.apm_client.capture_exception()
    apm_client.end_transaction(name="producer", result='success')
```


`
**Environment (please complete the following information)**
- OS: Release Linux Mint 20.3 Una 64-bit, Kernel Linux 5.13.0-41-generic x86_64, MATE 1.26.0
- Python version: 3.8.10 
- APM Server version: 8.1.2
- Agent version: elastic-apm==6.9.1


**Additional context**

Add any other context about the problem here.

- Agent config options <!-- be careful not to post sensitive information -->
  <details>
    <summary>Click to expand</summary>

```
Traceback (most recent call last):
  File "/home/user/test/python/eapm_bug//tests/apm_bug.py", line 25, in <module>
    sqs_client.send_message_batch(QueueUrl=sqs_url, Entries=entries)
  File "/home/user/test/python/eapm_bug//venv/lib/python3.8/site-packages/botocore/client.py", line 386, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/home/user/test/python/eapm_bug//venv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/base.py", line 210, in call_if_sampling
    return self.call(module, method, wrapped, instance, args, kwargs)
  File "/home/user/test/python/eapm_bug//venv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/botocore.py", line 93, in call
    span_modifiers[service](span, args, kwargs)
  File "/home/user/test/python/eapm_bug//venv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/botocore.py", line 194, in modify_span_sqs
    if len(message["MessageAttributes"]) + attributes_count <= SQS_MAX_ATTRIBUTES:
KeyError: 'MessageAttributes'

Process finished with exit code 1

```
  </details>
- `requirements.txt`:
  <details>
    <summary>Click to expand</summary>

    ```
    elastic-apm[flask]>=6.8.1,<7
    ```
  </details>



Discussion:

Resolving patch:
diff --git a/elasticapm/instrumentation/packages/botocore.py b/elasticapm/instrumentation/packages/botocore.py
--- a/elasticapm/instrumentation/packages/botocore.py
+++ b/elasticapm/instrumentation/packages/botocore.py
@@ -214,6 +214,7 @@ def modify_span_sqs(span, args, kwargs):
         else:
             messages = []
         for message in messages:
+            message["MessageAttributes"] = message.get("MessageAttributes") or {}
             if len(message["MessageAttributes"]) + attributes_count <= SQS_MAX_ATTRIBUTES:
                 message["MessageAttributes"].update(attributes)
             else:


Test patch:
diff --git a/tests/instrumentation/botocore_tests.py b/tests/instrumentation/botocore_tests.py
--- a/tests/instrumentation/botocore_tests.py
+++ b/tests/instrumentation/botocore_tests.py
@@ -274,6 +274,34 @@ def test_sqs_send_batch(instrument, elasticapm_client, sqs_client_and_queue):
     assert span["id"] in traceparent
 
 
+def test_sqs_no_message_attributes(instrument, elasticapm_client, sqs_client_and_queue):
+    sqs, queue_url = sqs_client_and_queue
+    elasticapm_client.begin_transaction("test")
+    response = sqs.send_message_batch(
+        QueueUrl=queue_url,
+        Entries=[
+            {
+                "Id": "foo",
+                "MessageBody": "foo",
+            },
+        ],
+    )
+    transaction = elasticapm_client.end_transaction("test", "test")
+    span = elasticapm_client.events[constants.SPAN][0]
+    messages = sqs.receive_message(
+        QueueUrl=queue_url,
+        AttributeNames=["All"],
+        MessageAttributeNames=[
+            "All",
+        ],
+    )
+    message = messages["Messages"][0]
+    assert "traceparent" in message["MessageAttributes"]
+    traceparent = message["MessageAttributes"]["traceparent"]["StringValue"]
+    assert transaction.trace_parent.trace_id in traceparent
+    assert span["id"] in traceparent
+
+
 def test_sqs_send_too_many_attributes_for_disttracing(instrument, elasticapm_client, sqs_client_and_queue, caplog):
     sqs, queue_url = sqs_client_and_queue
     attributes = {str(i): {"DataType": "String", "StringValue": str(i)} for i in range(SQS_MAX_ATTRIBUTES)}