We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DroppedSpan causing AttributeError
**Describe the bug**: celery tasks are now failing with attribute errors caused by APM

**Environment (please complete the following information)**
- OS: Linux
- Python version: 3.8.5
- Framework and version [e.g. Django 2.1]: Django 3.2.3, Celery 5.0.5
- APM Server version: 7.13
- Agent version: 6.2.2

Could be related to elastic/apm-agent-python#468

Traceback:
```
Traceback (most recent call last):
......
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/task.py", line 421, in delay
return self.apply_async(args, kwargs)
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/task.py", line 561, in apply_async
return app.send_task(
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/base.py", line 749, in send_task
amqp.send_task_message(P, name, message, **options)
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/amqp.py", line 523, in send_task_message
ret = producer.publish(
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/messaging.py", line 175, in publish
return _publish(
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/connection.py", line 525, in _ensured
return fun(*args, **kwargs)
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/messaging.py", line 197, in _publish
return channel.basic_publish(
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/transport/virtual/base.py", line 605, in basic_publish
return self._put(routing_key, message, **kwargs)
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/transport/SQS.py", line 294, in _put
c.send_message(**kwargs)
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/botocore/client.py", line 357, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/aimful/shared/virtualenv/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 "/aimful/shared/virtualenv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/botocore.py", line 91, in call
span_modifiers[service](span, args, kwargs)
File "/aimful/shared/virtualenv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/botocore.py", line 174, in modify_span_sqs
trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)
AttributeError: 'DroppedSpan' object has no attribute 'transaction'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `elasticapm/instrumentation/packages/botocore.py`
Content:
```
1 #  BSD 3-Clause License
2 #
3 #  Copyright (c) 2019, Elasticsearch BV
4 #  All rights reserved.
5 #
6 #  Redistribution and use in source and binary forms, with or without
7 #  modification, are permitted provided that the following conditions are met:
8 #
9 #  * Redistributions of source code must retain the above copyright notice, this
10 #    list of conditions and the following disclaimer.
11 #
12 #  * Redistributions in binary form must reproduce the above copyright notice,
13 #    this list of conditions and the following disclaimer in the documentation
14 #    and/or other materials provided with the distribution.
15 #
16 #  * Neither the name of the copyright holder nor the names of its
17 #    contributors may be used to endorse or promote products derived from
18 #    this software without specific prior written permission.
19 #
20 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 #  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 from collections import namedtuple
32 
33 from elasticapm.conf import constants
34 from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule
35 from elasticapm.traces import capture_span
36 from elasticapm.utils.compat import urlparse
37 from elasticapm.utils.logging import get_logger
38 
39 logger = get_logger("elasticapm.instrument")
40 
41 
42 HandlerInfo = namedtuple("HandlerInfo", ("signature", "span_type", "span_subtype", "span_action", "context"))
43 
44 # Used for boto3 < 1.7
45 endpoint_to_service_id = {"SNS": "SNS", "S3": "S3", "DYNAMODB": "DynamoDB", "SQS": "SQS"}
46 
47 
48 class BotocoreInstrumentation(AbstractInstrumentedModule):
49     name = "botocore"
50 
51     instrument_list = [("botocore.client", "BaseClient._make_api_call")]
52 
53     def call(self, module, method, wrapped, instance, args, kwargs):
54         if "operation_name" in kwargs:
55             operation_name = kwargs["operation_name"]
56         else:
57             operation_name = args[0]
58 
59         service_model = instance.meta.service_model
60         if hasattr(service_model, "service_id"):  # added in boto3 1.7
61             service = service_model.service_id
62         else:
63             service = service_model.service_name.upper()
64             service = endpoint_to_service_id.get(service, service)
65 
66         parsed_url = urlparse.urlparse(instance.meta.endpoint_url)
67         context = {
68             "destination": {
69                 "address": parsed_url.hostname,
70                 "port": parsed_url.port,
71                 "cloud": {"region": instance.meta.region_name},
72             }
73         }
74 
75         handler_info = None
76         handler = handlers.get(service, False)
77         if handler:
78             handler_info = handler(operation_name, service, instance, args, kwargs, context)
79         if not handler_info:
80             handler_info = handle_default(operation_name, service, instance, args, kwargs, context)
81 
82         with capture_span(
83             handler_info.signature,
84             span_type=handler_info.span_type,
85             leaf=True,
86             span_subtype=handler_info.span_subtype,
87             span_action=handler_info.span_action,
88             extra=handler_info.context,
89         ) as span:
90             if service in span_modifiers:
91                 span_modifiers[service](span, args, kwargs)
92             return wrapped(*args, **kwargs)
93 
94 
95 def handle_s3(operation_name, service, instance, args, kwargs, context):
96     span_type = "storage"
97     span_subtype = "s3"
98     span_action = operation_name
99     if len(args) > 1 and "Bucket" in args[1]:
100         bucket = args[1]["Bucket"]
101     else:
102         # TODO handle Access Points
103         bucket = ""
104     signature = f"S3 {operation_name} {bucket}"
105 
106     context["destination"]["service"] = {"name": span_subtype, "resource": bucket, "type": span_type}
107 
108     return HandlerInfo(signature, span_type, span_subtype, span_action, context)
109 
110 
111 def handle_dynamodb(operation_name, service, instance, args, kwargs, context):
112     span_type = "db"
113     span_subtype = "dynamodb"
114     span_action = "query"
115     if len(args) > 1 and "TableName" in args[1]:
116         table = args[1]["TableName"]
117     else:
118         table = ""
119     signature = f"DynamoDB {operation_name} {table}".rstrip()
120 
121     context["db"] = {"type": "dynamodb", "instance": instance.meta.region_name}
122     if operation_name == "Query" and len(args) > 1 and "KeyConditionExpression" in args[1]:
123         context["db"]["statement"] = args[1]["KeyConditionExpression"]
124 
125     context["destination"]["service"] = {"name": span_subtype, "resource": table, "type": span_type}
126     return HandlerInfo(signature, span_type, span_subtype, span_action, context)
127 
128 
129 def handle_sns(operation_name, service, instance, args, kwargs, context):
130     if operation_name != "Publish":
131         # only "publish" is handled specifically, other endpoints get the default treatment
132         return False
133     span_type = "messaging"
134     span_subtype = "sns"
135     span_action = "send"
136     topic_name = ""
137     if len(args) > 1:
138         if "Name" in args[1]:
139             topic_name = args[1]["Name"]
140         if "TopicArn" in args[1]:
141             topic_name = args[1]["TopicArn"].rsplit(":", maxsplit=1)[-1]
142     signature = f"SNS {operation_name} {topic_name}".rstrip()
143     context["destination"]["service"] = {
144         "name": span_subtype,
145         "resource": f"{span_subtype}/{topic_name}" if topic_name else span_subtype,
146         "type": span_type,
147     }
148     return HandlerInfo(signature, span_type, span_subtype, span_action, context)
149 
150 
151 def handle_sqs(operation_name, service, instance, args, kwargs, context):
152     if operation_name not in ("SendMessage", "SendMessageBatch", "ReceiveMessage"):
153         # only "publish" is handled specifically, other endpoints get the default treatment
154         return False
155     span_type = "messaging"
156     span_subtype = "sqs"
157     span_action = "send" if operation_name in ("SendMessage", "SendMessageBatch") else "receive"
158     topic_name = ""
159     batch = "_BATCH" if operation_name == "SendMessageBatch" else ""
160     signature_type = "RECEIVE from" if span_action == "receive" else f"SEND{batch} to"
161 
162     if len(args) > 1:
163         topic_name = args[1]["QueueUrl"].rsplit("/", maxsplit=1)[-1]
164     signature = f"SQS {signature_type} {topic_name}".rstrip() if topic_name else f"SQS {signature_type}"
165     context["destination"]["service"] = {
166         "name": span_subtype,
167         "resource": f"{span_subtype}/{topic_name}" if topic_name else span_subtype,
168         "type": span_type,
169     }
170     return HandlerInfo(signature, span_type, span_subtype, span_action, context)
171 
172 
173 def modify_span_sqs(span, args, kwargs):
174     trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)
175     attributes = {constants.TRACEPARENT_HEADER_NAME: {"DataType": "String", "StringValue": trace_parent.to_string()}}
176     if trace_parent.tracestate:
177         attributes[constants.TRACESTATE_HEADER_NAME] = {"DataType": "String", "StringValue": trace_parent.tracestate}
178     if len(args) > 1:
179         attributes_count = len(attributes)
180         if "MessageAttributes" in args[1]:
181             messages = [args[1]]
182         # elif "Entries" in args[1]:
183         #     messages = args[1]["Entries"]
184         else:
185             messages = []
186         for message in messages:
187             if len(message["MessageAttributes"]) + attributes_count <= 10:
188                 message["MessageAttributes"].update(attributes)
189             else:
190                 logger.info("Not adding disttracing headers to message due to attribute limit reached")
191 
192 
193 def handle_default(operation_name, service, instance, args, kwargs, destination):
194     span_type = "aws"
195     span_subtype = service.lower()
196     span_action = operation_name
197 
198     destination["service"] = {"name": span_subtype, "resource": span_subtype, "type": span_type}
199 
200     signature = f"{service}:{operation_name}"
201     return HandlerInfo(signature, span_type, span_subtype, span_action, destination)
202 
203 
204 handlers = {
205     "S3": handle_s3,
206     "DynamoDB": handle_dynamodb,
207     "SNS": handle_sns,
208     "SQS": handle_sqs,
209     "default": handle_default,
210 }
211 
212 span_modifiers = {
213     "SQS": modify_span_sqs,
214 }
215
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -32,12 +32,14 @@
 
 from elasticapm.conf import constants
 from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule
-from elasticapm.traces import capture_span
+from elasticapm.traces import capture_span, execution_context
 from elasticapm.utils.compat import urlparse
 from elasticapm.utils.logging import get_logger
 
 logger = get_logger("elasticapm.instrument")
 
+SQS_MAX_ATTRIBUTES = 10
+
 
 HandlerInfo = namedtuple("HandlerInfo", ("signature", "span_type", "span_subtype", "span_action", "context"))
 
@@ -171,7 +173,12 @@
 
 
 def modify_span_sqs(span, args, kwargs):
-    trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)
+    if span.id:
+        trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)
+    else:
+        # this is a dropped span, use transaction id instead
+        transaction = execution_context.get_transaction()
+        trace_parent = transaction.trace_parent.copy_from(span_id=transaction.id)
     attributes = {constants.TRACEPARENT_HEADER_NAME: {"DataType": "String", "StringValue": trace_parent.to_string()}}
     if trace_parent.tracestate:
         attributes[constants.TRACESTATE_HEADER_NAME] = {"DataType": "String", "StringValue": trace_parent.tracestate}
@@ -179,12 +186,12 @@
         attributes_count = len(attributes)
         if "MessageAttributes" in args[1]:
             messages = [args[1]]
-        # elif "Entries" in args[1]:
-        #     messages = args[1]["Entries"]
+        elif "Entries" in args[1]:
+            messages = args[1]["Entries"]
         else:
             messages = []
         for message in messages:
-            if len(message["MessageAttributes"]) + attributes_count <= 10:
+            if len(message["MessageAttributes"]) + attributes_count <= SQS_MAX_ATTRIBUTES:
                 message["MessageAttributes"].update(attributes)
             else:
                 logger.info("Not adding disttracing headers to message due to attribute limit reached")

{"golden_diff": "diff --git a/elasticapm/instrumentation/packages/botocore.py b/elasticapm/instrumentation/packages/botocore.py\n--- a/elasticapm/instrumentation/packages/botocore.py\n+++ b/elasticapm/instrumentation/packages/botocore.py\n@@ -32,12 +32,14 @@\n \n from elasticapm.conf import constants\n from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule\n-from elasticapm.traces import capture_span\n+from elasticapm.traces import capture_span, execution_context\n from elasticapm.utils.compat import urlparse\n from elasticapm.utils.logging import get_logger\n \n logger = get_logger(\"elasticapm.instrument\")\n \n+SQS_MAX_ATTRIBUTES = 10\n+\n \n HandlerInfo = namedtuple(\"HandlerInfo\", (\"signature\", \"span_type\", \"span_subtype\", \"span_action\", \"context\"))\n \n@@ -171,7 +173,12 @@\n \n \n def modify_span_sqs(span, args, kwargs):\n-    trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)\n+    if span.id:\n+        trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)\n+    else:\n+        # this is a dropped span, use transaction id instead\n+        transaction = execution_context.get_transaction()\n+        trace_parent = transaction.trace_parent.copy_from(span_id=transaction.id)\n     attributes = {constants.TRACEPARENT_HEADER_NAME: {\"DataType\": \"String\", \"StringValue\": trace_parent.to_string()}}\n     if trace_parent.tracestate:\n         attributes[constants.TRACESTATE_HEADER_NAME] = {\"DataType\": \"String\", \"StringValue\": trace_parent.tracestate}\n@@ -179,12 +186,12 @@\n         attributes_count = len(attributes)\n         if \"MessageAttributes\" in args[1]:\n             messages = [args[1]]\n-        # elif \"Entries\" in args[1]:\n-        #     messages = args[1][\"Entries\"]\n+        elif \"Entries\" in args[1]:\n+            messages = args[1][\"Entries\"]\n         else:\n             messages = []\n         for message in messages:\n-            if len(message[\"MessageAttributes\"]) + attributes_count <= 10:\n+            if len(message[\"MessageAttributes\"]) + attributes_count <= SQS_MAX_ATTRIBUTES:\n                 message[\"MessageAttributes\"].update(attributes)\n             else:\n                 logger.info(\"Not adding disttracing headers to message due to attribute limit reached\")\n", "issue": "DroppedSpan causing AttributeError\n**Describe the bug**: celery tasks are now failing with attribute errors caused by APM\r\n\r\n**Environment (please complete the following information)**\r\n- OS: Linux\r\n- Python version: 3.8.5\r\n- Framework and version [e.g. Django 2.1]: Django 3.2.3, Celery 5.0.5\r\n- APM Server version: 7.13\r\n- Agent version: 6.2.2\r\n\r\nCould be related to elastic/apm-agent-python#468\r\n\r\nTraceback:\r\n```\r\nTraceback (most recent call last):\r\n......\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/task.py\", line 421, in delay\r\nreturn self.apply_async(args, kwargs)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/task.py\", line 561, in apply_async\r\nreturn app.send_task(\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/base.py\", line 749, in send_task\r\namqp.send_task_message(P, name, message, **options)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/celery/app/amqp.py\", line 523, in send_task_message\r\nret = producer.publish(\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/messaging.py\", line 175, in publish\r\nreturn _publish(\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/connection.py\", line 525, in _ensured\r\nreturn fun(*args, **kwargs)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/messaging.py\", line 197, in _publish\r\nreturn channel.basic_publish(\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/transport/virtual/base.py\", line 605, in basic_publish\r\nreturn self._put(routing_key, message, **kwargs)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/kombu/transport/SQS.py\", line 294, in _put\r\nc.send_message(**kwargs)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/botocore/client.py\", line 357, in _api_call\r\nreturn self._make_api_call(operation_name, kwargs)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/base.py\", line 210, in call_if_sampling\r\nreturn self.call(module, method, wrapped, instance, args, kwargs)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/botocore.py\", line 91, in call\r\nspan_modifiers[service](span, args, kwargs)\r\nFile \"/aimful/shared/virtualenv/lib/python3.8/site-packages/elasticapm/instrumentation/packages/botocore.py\", line 174, in modify_span_sqs\r\ntrace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)\r\nAttributeError: 'DroppedSpan' object has no attribute 'transaction'\r\n```\n", "before_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nfrom collections import namedtuple\n\nfrom elasticapm.conf import constants\nfrom elasticapm.instrumentation.packages.base import AbstractInstrumentedModule\nfrom elasticapm.traces import capture_span\nfrom elasticapm.utils.compat import urlparse\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.instrument\")\n\n\nHandlerInfo = namedtuple(\"HandlerInfo\", (\"signature\", \"span_type\", \"span_subtype\", \"span_action\", \"context\"))\n\n# Used for boto3 < 1.7\nendpoint_to_service_id = {\"SNS\": \"SNS\", \"S3\": \"S3\", \"DYNAMODB\": \"DynamoDB\", \"SQS\": \"SQS\"}\n\n\nclass BotocoreInstrumentation(AbstractInstrumentedModule):\n    name = \"botocore\"\n\n    instrument_list = [(\"botocore.client\", \"BaseClient._make_api_call\")]\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        if \"operation_name\" in kwargs:\n            operation_name = kwargs[\"operation_name\"]\n        else:\n            operation_name = args[0]\n\n        service_model = instance.meta.service_model\n        if hasattr(service_model, \"service_id\"):  # added in boto3 1.7\n            service = service_model.service_id\n        else:\n            service = service_model.service_name.upper()\n            service = endpoint_to_service_id.get(service, service)\n\n        parsed_url = urlparse.urlparse(instance.meta.endpoint_url)\n        context = {\n            \"destination\": {\n                \"address\": parsed_url.hostname,\n                \"port\": parsed_url.port,\n                \"cloud\": {\"region\": instance.meta.region_name},\n            }\n        }\n\n        handler_info = None\n        handler = handlers.get(service, False)\n        if handler:\n            handler_info = handler(operation_name, service, instance, args, kwargs, context)\n        if not handler_info:\n            handler_info = handle_default(operation_name, service, instance, args, kwargs, context)\n\n        with capture_span(\n            handler_info.signature,\n            span_type=handler_info.span_type,\n            leaf=True,\n            span_subtype=handler_info.span_subtype,\n            span_action=handler_info.span_action,\n            extra=handler_info.context,\n        ) as span:\n            if service in span_modifiers:\n                span_modifiers[service](span, args, kwargs)\n            return wrapped(*args, **kwargs)\n\n\ndef handle_s3(operation_name, service, instance, args, kwargs, context):\n    span_type = \"storage\"\n    span_subtype = \"s3\"\n    span_action = operation_name\n    if len(args) > 1 and \"Bucket\" in args[1]:\n        bucket = args[1][\"Bucket\"]\n    else:\n        # TODO handle Access Points\n        bucket = \"\"\n    signature = f\"S3 {operation_name} {bucket}\"\n\n    context[\"destination\"][\"service\"] = {\"name\": span_subtype, \"resource\": bucket, \"type\": span_type}\n\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef handle_dynamodb(operation_name, service, instance, args, kwargs, context):\n    span_type = \"db\"\n    span_subtype = \"dynamodb\"\n    span_action = \"query\"\n    if len(args) > 1 and \"TableName\" in args[1]:\n        table = args[1][\"TableName\"]\n    else:\n        table = \"\"\n    signature = f\"DynamoDB {operation_name} {table}\".rstrip()\n\n    context[\"db\"] = {\"type\": \"dynamodb\", \"instance\": instance.meta.region_name}\n    if operation_name == \"Query\" and len(args) > 1 and \"KeyConditionExpression\" in args[1]:\n        context[\"db\"][\"statement\"] = args[1][\"KeyConditionExpression\"]\n\n    context[\"destination\"][\"service\"] = {\"name\": span_subtype, \"resource\": table, \"type\": span_type}\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef handle_sns(operation_name, service, instance, args, kwargs, context):\n    if operation_name != \"Publish\":\n        # only \"publish\" is handled specifically, other endpoints get the default treatment\n        return False\n    span_type = \"messaging\"\n    span_subtype = \"sns\"\n    span_action = \"send\"\n    topic_name = \"\"\n    if len(args) > 1:\n        if \"Name\" in args[1]:\n            topic_name = args[1][\"Name\"]\n        if \"TopicArn\" in args[1]:\n            topic_name = args[1][\"TopicArn\"].rsplit(\":\", maxsplit=1)[-1]\n    signature = f\"SNS {operation_name} {topic_name}\".rstrip()\n    context[\"destination\"][\"service\"] = {\n        \"name\": span_subtype,\n        \"resource\": f\"{span_subtype}/{topic_name}\" if topic_name else span_subtype,\n        \"type\": span_type,\n    }\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef handle_sqs(operation_name, service, instance, args, kwargs, context):\n    if operation_name not in (\"SendMessage\", \"SendMessageBatch\", \"ReceiveMessage\"):\n        # only \"publish\" is handled specifically, other endpoints get the default treatment\n        return False\n    span_type = \"messaging\"\n    span_subtype = \"sqs\"\n    span_action = \"send\" if operation_name in (\"SendMessage\", \"SendMessageBatch\") else \"receive\"\n    topic_name = \"\"\n    batch = \"_BATCH\" if operation_name == \"SendMessageBatch\" else \"\"\n    signature_type = \"RECEIVE from\" if span_action == \"receive\" else f\"SEND{batch} to\"\n\n    if len(args) > 1:\n        topic_name = args[1][\"QueueUrl\"].rsplit(\"/\", maxsplit=1)[-1]\n    signature = f\"SQS {signature_type} {topic_name}\".rstrip() if topic_name else f\"SQS {signature_type}\"\n    context[\"destination\"][\"service\"] = {\n        \"name\": span_subtype,\n        \"resource\": f\"{span_subtype}/{topic_name}\" if topic_name else span_subtype,\n        \"type\": span_type,\n    }\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef modify_span_sqs(span, args, kwargs):\n    trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)\n    attributes = {constants.TRACEPARENT_HEADER_NAME: {\"DataType\": \"String\", \"StringValue\": trace_parent.to_string()}}\n    if trace_parent.tracestate:\n        attributes[constants.TRACESTATE_HEADER_NAME] = {\"DataType\": \"String\", \"StringValue\": trace_parent.tracestate}\n    if len(args) > 1:\n        attributes_count = len(attributes)\n        if \"MessageAttributes\" in args[1]:\n            messages = [args[1]]\n        # elif \"Entries\" in args[1]:\n        #     messages = args[1][\"Entries\"]\n        else:\n            messages = []\n        for message in messages:\n            if len(message[\"MessageAttributes\"]) + attributes_count <= 10:\n                message[\"MessageAttributes\"].update(attributes)\n            else:\n                logger.info(\"Not adding disttracing headers to message due to attribute limit reached\")\n\n\ndef handle_default(operation_name, service, instance, args, kwargs, destination):\n    span_type = \"aws\"\n    span_subtype = service.lower()\n    span_action = operation_name\n\n    destination[\"service\"] = {\"name\": span_subtype, \"resource\": span_subtype, \"type\": span_type}\n\n    signature = f\"{service}:{operation_name}\"\n    return HandlerInfo(signature, span_type, span_subtype, span_action, destination)\n\n\nhandlers = {\n    \"S3\": handle_s3,\n    \"DynamoDB\": handle_dynamodb,\n    \"SNS\": handle_sns,\n    \"SQS\": handle_sqs,\n    \"default\": handle_default,\n}\n\nspan_modifiers = {\n    \"SQS\": modify_span_sqs,\n}\n", "path": "elasticapm/instrumentation/packages/botocore.py"}], "after_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nfrom collections import namedtuple\n\nfrom elasticapm.conf import constants\nfrom elasticapm.instrumentation.packages.base import AbstractInstrumentedModule\nfrom elasticapm.traces import capture_span, execution_context\nfrom elasticapm.utils.compat import urlparse\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.instrument\")\n\nSQS_MAX_ATTRIBUTES = 10\n\n\nHandlerInfo = namedtuple(\"HandlerInfo\", (\"signature\", \"span_type\", \"span_subtype\", \"span_action\", \"context\"))\n\n# Used for boto3 < 1.7\nendpoint_to_service_id = {\"SNS\": \"SNS\", \"S3\": \"S3\", \"DYNAMODB\": \"DynamoDB\", \"SQS\": \"SQS\"}\n\n\nclass BotocoreInstrumentation(AbstractInstrumentedModule):\n    name = \"botocore\"\n\n    instrument_list = [(\"botocore.client\", \"BaseClient._make_api_call\")]\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        if \"operation_name\" in kwargs:\n            operation_name = kwargs[\"operation_name\"]\n        else:\n            operation_name = args[0]\n\n        service_model = instance.meta.service_model\n        if hasattr(service_model, \"service_id\"):  # added in boto3 1.7\n            service = service_model.service_id\n        else:\n            service = service_model.service_name.upper()\n            service = endpoint_to_service_id.get(service, service)\n\n        parsed_url = urlparse.urlparse(instance.meta.endpoint_url)\n        context = {\n            \"destination\": {\n                \"address\": parsed_url.hostname,\n                \"port\": parsed_url.port,\n                \"cloud\": {\"region\": instance.meta.region_name},\n            }\n        }\n\n        handler_info = None\n        handler = handlers.get(service, False)\n        if handler:\n            handler_info = handler(operation_name, service, instance, args, kwargs, context)\n        if not handler_info:\n            handler_info = handle_default(operation_name, service, instance, args, kwargs, context)\n\n        with capture_span(\n            handler_info.signature,\n            span_type=handler_info.span_type,\n            leaf=True,\n            span_subtype=handler_info.span_subtype,\n            span_action=handler_info.span_action,\n            extra=handler_info.context,\n        ) as span:\n            if service in span_modifiers:\n                span_modifiers[service](span, args, kwargs)\n            return wrapped(*args, **kwargs)\n\n\ndef handle_s3(operation_name, service, instance, args, kwargs, context):\n    span_type = \"storage\"\n    span_subtype = \"s3\"\n    span_action = operation_name\n    if len(args) > 1 and \"Bucket\" in args[1]:\n        bucket = args[1][\"Bucket\"]\n    else:\n        # TODO handle Access Points\n        bucket = \"\"\n    signature = f\"S3 {operation_name} {bucket}\"\n\n    context[\"destination\"][\"service\"] = {\"name\": span_subtype, \"resource\": bucket, \"type\": span_type}\n\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef handle_dynamodb(operation_name, service, instance, args, kwargs, context):\n    span_type = \"db\"\n    span_subtype = \"dynamodb\"\n    span_action = \"query\"\n    if len(args) > 1 and \"TableName\" in args[1]:\n        table = args[1][\"TableName\"]\n    else:\n        table = \"\"\n    signature = f\"DynamoDB {operation_name} {table}\".rstrip()\n\n    context[\"db\"] = {\"type\": \"dynamodb\", \"instance\": instance.meta.region_name}\n    if operation_name == \"Query\" and len(args) > 1 and \"KeyConditionExpression\" in args[1]:\n        context[\"db\"][\"statement\"] = args[1][\"KeyConditionExpression\"]\n\n    context[\"destination\"][\"service\"] = {\"name\": span_subtype, \"resource\": table, \"type\": span_type}\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef handle_sns(operation_name, service, instance, args, kwargs, context):\n    if operation_name != \"Publish\":\n        # only \"publish\" is handled specifically, other endpoints get the default treatment\n        return False\n    span_type = \"messaging\"\n    span_subtype = \"sns\"\n    span_action = \"send\"\n    topic_name = \"\"\n    if len(args) > 1:\n        if \"Name\" in args[1]:\n            topic_name = args[1][\"Name\"]\n        if \"TopicArn\" in args[1]:\n            topic_name = args[1][\"TopicArn\"].rsplit(\":\", maxsplit=1)[-1]\n    signature = f\"SNS {operation_name} {topic_name}\".rstrip()\n    context[\"destination\"][\"service\"] = {\n        \"name\": span_subtype,\n        \"resource\": f\"{span_subtype}/{topic_name}\" if topic_name else span_subtype,\n        \"type\": span_type,\n    }\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef handle_sqs(operation_name, service, instance, args, kwargs, context):\n    if operation_name not in (\"SendMessage\", \"SendMessageBatch\", \"ReceiveMessage\"):\n        # only \"publish\" is handled specifically, other endpoints get the default treatment\n        return False\n    span_type = \"messaging\"\n    span_subtype = \"sqs\"\n    span_action = \"send\" if operation_name in (\"SendMessage\", \"SendMessageBatch\") else \"receive\"\n    topic_name = \"\"\n    batch = \"_BATCH\" if operation_name == \"SendMessageBatch\" else \"\"\n    signature_type = \"RECEIVE from\" if span_action == \"receive\" else f\"SEND{batch} to\"\n\n    if len(args) > 1:\n        topic_name = args[1][\"QueueUrl\"].rsplit(\"/\", maxsplit=1)[-1]\n    signature = f\"SQS {signature_type} {topic_name}\".rstrip() if topic_name else f\"SQS {signature_type}\"\n    context[\"destination\"][\"service\"] = {\n        \"name\": span_subtype,\n        \"resource\": f\"{span_subtype}/{topic_name}\" if topic_name else span_subtype,\n        \"type\": span_type,\n    }\n    return HandlerInfo(signature, span_type, span_subtype, span_action, context)\n\n\ndef modify_span_sqs(span, args, kwargs):\n    if span.id:\n        trace_parent = span.transaction.trace_parent.copy_from(span_id=span.id)\n    else:\n        # this is a dropped span, use transaction id instead\n        transaction = execution_context.get_transaction()\n        trace_parent = transaction.trace_parent.copy_from(span_id=transaction.id)\n    attributes = {constants.TRACEPARENT_HEADER_NAME: {\"DataType\": \"String\", \"StringValue\": trace_parent.to_string()}}\n    if trace_parent.tracestate:\n        attributes[constants.TRACESTATE_HEADER_NAME] = {\"DataType\": \"String\", \"StringValue\": trace_parent.tracestate}\n    if len(args) > 1:\n        attributes_count = len(attributes)\n        if \"MessageAttributes\" in args[1]:\n            messages = [args[1]]\n        elif \"Entries\" in args[1]:\n            messages = args[1][\"Entries\"]\n        else:\n            messages = []\n        for message in messages:\n            if len(message[\"MessageAttributes\"]) + attributes_count <= SQS_MAX_ATTRIBUTES:\n                message[\"MessageAttributes\"].update(attributes)\n            else:\n                logger.info(\"Not adding disttracing headers to message due to attribute limit reached\")\n\n\ndef handle_default(operation_name, service, instance, args, kwargs, destination):\n    span_type = \"aws\"\n    span_subtype = service.lower()\n    span_action = operation_name\n\n    destination[\"service\"] = {\"name\": span_subtype, \"resource\": span_subtype, \"type\": span_type}\n\n    signature = f\"{service}:{operation_name}\"\n    return HandlerInfo(signature, span_type, span_subtype, span_action, destination)\n\n\nhandlers = {\n    \"S3\": handle_s3,\n    \"DynamoDB\": handle_dynamodb,\n    \"SNS\": handle_sns,\n    \"SQS\": handle_sqs,\n    \"default\": handle_default,\n}\n\nspan_modifiers = {\n    \"SQS\": modify_span_sqs,\n}\n", "path": "elasticapm/instrumentation/packages/botocore.py"}]}