We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
database trace show twice
maybe this is not a bug
kibana show every database trace will show twice
![image](https://user-images.githubusercontent.com/35414361/72774905-5a16a700-3c47-11ea-878c-3b05a671ec4f.png)

but in 4.2.2 version,  it show this 
![image](https://user-images.githubusercontent.com/35414361/72774972-964a0780-3c47-11ea-9d0d-19269d9259f2.png)

i think its related to span? in 4.2.2 agent won't upload span log,   please tell me how to resovle it 😢
(forgive my poor English)

**Environment (please complete the following information)**
- OS: [e.g. Linux] linux
- Python version: 3.7.
- Framework and version [e.g. Django 2.1]: Django 3.0
- APM Server version:  7.4.2
- Agent version: 5.3.3


--- 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/base.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 import functools
32 import os
33 
34 from elasticapm.traces import execution_context
35 from elasticapm.utils import wrapt
36 from elasticapm.utils.logging import get_logger
37 
38 logger = get_logger("elasticapm.instrument")
39 
40 
41 class AbstractInstrumentedModule(object):
42     """
43     This class is designed to reduce the amount of code required to
44     instrument library functions using wrapt.
45 
46     Instrumentation modules inherit from this class and override pieces as
47     needed. Only `name`, `instrumented_list`, and `call` are required in
48     the inheriting class.
49 
50     The `instrument_list` is a list of (module, method) pairs that will be
51     instrumented. The module/method need not be imported -- in fact, because
52     instrumentation modules are all processed during the instrumentation
53     process, lazy imports should be used in order to avoid ImportError
54     exceptions.
55 
56     The `instrument()` method will be called for each InstrumentedModule
57     listed in the instrument register (elasticapm.instrumentation.register),
58     and each method in the `instrument_list` will be wrapped (using wrapt)
59     with the `call_if_sampling()` function, which (by default) will either
60     call the wrapped function by itself, or pass it into `call()` to be
61     called if there is a transaction active.
62 
63     For simple span-wrapping of instrumented libraries, a very simple
64     InstrumentedModule might look like this::
65 
66         from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule
67         from elasticapm.traces import capture_span
68 
69         class Jinja2Instrumentation(AbstractInstrumentedModule):
70             name = "jinja2"
71             instrument_list = [("jinja2", "Template.render")]
72             def call(self, module, method, wrapped, instance, args, kwargs):
73                 signature = instance.name or instance.filename
74                 with capture_span(signature, span_type="template", span_subtype="jinja2", span_action="render"):
75                     return wrapped(*args, **kwargs)
76 
77     This class can also be used to instrument callables which are expected to
78     create their own transactions (rather than spans within a transaction).
79     In this case, set `creates_transaction = True` next to your `name` and
80     `instrument_list`. This tells the instrumenting code to always wrap the
81     method with `call()`, even if there is no transaction active. It is
82     expected in this case that a new transaction will be created as part of
83     your `call()` method.
84     """
85 
86     name = None
87     mutates_unsampled_arguments = False
88     creates_transactions = False
89 
90     instrument_list = [
91         # List of (module, method) pairs to instrument. E.g.:
92         # ("requests.sessions", "Session.send"),
93     ]
94 
95     def __init__(self):
96         self.originals = {}
97         self.instrumented = False
98 
99         assert self.name is not None
100 
101     def get_wrapped_name(self, wrapped, instance, fallback_method=None):
102         wrapped_name = []
103         if hasattr(instance, "__class__") and hasattr(instance.__class__, "__name__"):
104             wrapped_name.append(instance.__class__.__name__)
105 
106         if hasattr(wrapped, "__name__"):
107             wrapped_name.append(wrapped.__name__)
108         elif fallback_method:
109             attribute = fallback_method.split(".")
110             if len(attribute) == 2:
111                 wrapped_name.append(attribute[1])
112 
113         return ".".join(wrapped_name)
114 
115     def get_instrument_list(self):
116         return self.instrument_list
117 
118     def instrument(self):
119         if self.instrumented:
120             return
121 
122         skip_env_var = "SKIP_INSTRUMENT_" + str(self.name.upper())
123         if skip_env_var in os.environ:
124             logger.debug("Skipping instrumentation of %s. %s is set.", self.name, skip_env_var)
125             return
126         try:
127             instrument_list = self.get_instrument_list()
128             skipped_modules = set()
129             instrumented_methods = []
130 
131             for module, method in instrument_list:
132                 try:
133                     # Skip modules we already failed to load
134                     if module in skipped_modules:
135                         continue
136                     # We jump through hoop here to get the original
137                     # `module`/`method` in the call to `call_if_sampling`
138                     parent, attribute, original = wrapt.resolve_path(module, method)
139                     self.originals[(module, method)] = original
140                     wrapper = wrapt.FunctionWrapper(original, functools.partial(self.call_if_sampling, module, method))
141                     wrapt.apply_patch(parent, attribute, wrapper)
142                     instrumented_methods.append((module, method))
143                 except ImportError:
144                     # Could not import module
145                     logger.debug("Skipping instrumentation of %s. Module %s not found", self.name, module)
146 
147                     # Keep track of modules we couldn't load so we don't
148                     # try to instrument anything in that module again
149                     skipped_modules.add(module)
150                 except AttributeError as ex:
151                     # Could not find thing in module
152                     logger.debug("Skipping instrumentation of %s.%s: %s", module, method, ex)
153             if instrumented_methods:
154                 logger.debug("Instrumented %s, %s", self.name, ", ".join(".".join(m) for m in instrumented_methods))
155 
156         except ImportError as ex:
157             logger.debug("Skipping instrumentation of %s. %s", self.name, ex)
158         self.instrumented = True
159 
160     def uninstrument(self):
161         if not self.instrumented or not self.originals:
162             return
163         uninstrumented_methods = []
164         for module, method in self.get_instrument_list():
165             if (module, method) in self.originals:
166                 parent, attribute, wrapper = wrapt.resolve_path(module, method)
167                 wrapt.apply_patch(parent, attribute, self.originals[(module, method)])
168                 uninstrumented_methods.append((module, method))
169         if uninstrumented_methods:
170             logger.debug("Uninstrumented %s, %s", self.name, ", ".join(".".join(m) for m in uninstrumented_methods))
171         self.instrumented = False
172         self.originals = {}
173 
174     def call_if_sampling(self, module, method, wrapped, instance, args, kwargs):
175         """
176         This is the function which will wrap the instrumented method/function.
177 
178         By default, will call the instrumented method/function, via `call()`,
179         only if a transaction is active and sampled. This behavior can be
180         overridden by setting `creates_transactions = True` at the class
181         level.
182 
183         If `creates_transactions == False` and there's an active transaction
184         with `transaction.is_sampled == False`, then the
185         `mutate_unsampled_call_args()` method is called, and the resulting
186         args and kwargs are passed into the wrapped function directly, not
187         via `call()`. This can e.g. be used to add traceparent headers to the
188         underlying http call for HTTP instrumentations, even if we're not
189         sampling the transaction.
190         """
191         if self.creates_transactions:
192             return self.call(module, method, wrapped, instance, args, kwargs)
193         transaction = execution_context.get_transaction()
194         if not transaction:
195             return wrapped(*args, **kwargs)
196         elif not transaction.is_sampled:
197             args, kwargs = self.mutate_unsampled_call_args(module, method, wrapped, instance, args, kwargs, transaction)
198             return wrapped(*args, **kwargs)
199         else:
200             return self.call(module, method, wrapped, instance, args, kwargs)
201 
202     def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
203         """
204         Method called for unsampled wrapped calls. This can e.g. be used to
205         add traceparent headers to the underlying http call for HTTP
206         instrumentations.
207 
208         :param module:
209         :param method:
210         :param wrapped:
211         :param instance:
212         :param args:
213         :param kwargs:
214         :param transaction:
215         :return:
216         """
217         return args, kwargs
218 
219     def call(self, module, method, wrapped, instance, args, kwargs):
220         """
221         Wrapped call. This method should gather all necessary data, then call
222         `wrapped` in a `capture_span` context manager.
223 
224         Note that by default this wrapper will only be used if a transaction is
225         currently active. If you want the ability to create a transaction in
226         your `call()` method, set `create_transactions = True` at the class
227         level.
228 
229         :param module: Name of the wrapped module
230         :param method: Name of the wrapped method/function
231         :param wrapped: the wrapped method/function object
232         :param instance: the wrapped instance
233         :param args: arguments to the wrapped method/function
234         :param kwargs: keyword arguments to the wrapped method/function
235         :return: the result of calling the wrapped method/function
236         """
237         raise NotImplementedError
238
```
--- 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/base.py b/elasticapm/instrumentation/packages/base.py
--- a/elasticapm/instrumentation/packages/base.py
+++ b/elasticapm/instrumentation/packages/base.py
@@ -38,6 +38,11 @@
 logger = get_logger("elasticapm.instrument")
 
 
+class ElasticAPMFunctionWrapper(wrapt.FunctionWrapper):
+    # used to differentiate between our own function wrappers and 1st/3rd party wrappers
+    pass
+
+
 class AbstractInstrumentedModule(object):
     """
     This class is designed to reduce the amount of code required to
@@ -136,8 +141,13 @@
                     # We jump through hoop here to get the original
                     # `module`/`method` in the call to `call_if_sampling`
                     parent, attribute, original = wrapt.resolve_path(module, method)
+                    if isinstance(original, ElasticAPMFunctionWrapper):
+                        logger.debug("%s.%s already instrumented, skipping", module, method)
+                        continue
                     self.originals[(module, method)] = original
-                    wrapper = wrapt.FunctionWrapper(original, functools.partial(self.call_if_sampling, module, method))
+                    wrapper = ElasticAPMFunctionWrapper(
+                        original, functools.partial(self.call_if_sampling, module, method)
+                    )
                     wrapt.apply_patch(parent, attribute, wrapper)
                     instrumented_methods.append((module, method))
                 except ImportError:

{"golden_diff": "diff --git a/elasticapm/instrumentation/packages/base.py b/elasticapm/instrumentation/packages/base.py\n--- a/elasticapm/instrumentation/packages/base.py\n+++ b/elasticapm/instrumentation/packages/base.py\n@@ -38,6 +38,11 @@\n logger = get_logger(\"elasticapm.instrument\")\n \n \n+class ElasticAPMFunctionWrapper(wrapt.FunctionWrapper):\n+    # used to differentiate between our own function wrappers and 1st/3rd party wrappers\n+    pass\n+\n+\n class AbstractInstrumentedModule(object):\n     \"\"\"\n     This class is designed to reduce the amount of code required to\n@@ -136,8 +141,13 @@\n                     # We jump through hoop here to get the original\n                     # `module`/`method` in the call to `call_if_sampling`\n                     parent, attribute, original = wrapt.resolve_path(module, method)\n+                    if isinstance(original, ElasticAPMFunctionWrapper):\n+                        logger.debug(\"%s.%s already instrumented, skipping\", module, method)\n+                        continue\n                     self.originals[(module, method)] = original\n-                    wrapper = wrapt.FunctionWrapper(original, functools.partial(self.call_if_sampling, module, method))\n+                    wrapper = ElasticAPMFunctionWrapper(\n+                        original, functools.partial(self.call_if_sampling, module, method)\n+                    )\n                     wrapt.apply_patch(parent, attribute, wrapper)\n                     instrumented_methods.append((module, method))\n                 except ImportError:\n", "issue": "database trace show twice\nmaybe this is not a bug\r\nkibana show every database trace will show twice\r\n![image](https://user-images.githubusercontent.com/35414361/72774905-5a16a700-3c47-11ea-878c-3b05a671ec4f.png)\r\n\r\nbut in 4.2.2 version,  it show this \r\n![image](https://user-images.githubusercontent.com/35414361/72774972-964a0780-3c47-11ea-9d0d-19269d9259f2.png)\r\n\r\ni think its related to span? in 4.2.2 agent won't upload span log,   please tell me how to resovle it \ud83d\ude22\r\n(forgive my poor English)\r\n\r\n**Environment (please complete the following information)**\r\n- OS: [e.g. Linux] linux\r\n- Python version: 3.7.\r\n- Framework and version [e.g. Django 2.1]: Django 3.0\r\n- APM Server version:  7.4.2\r\n- Agent version: 5.3.3\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\nimport functools\nimport os\n\nfrom elasticapm.traces import execution_context\nfrom elasticapm.utils import wrapt\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.instrument\")\n\n\nclass AbstractInstrumentedModule(object):\n    \"\"\"\n    This class is designed to reduce the amount of code required to\n    instrument library functions using wrapt.\n\n    Instrumentation modules inherit from this class and override pieces as\n    needed. Only `name`, `instrumented_list`, and `call` are required in\n    the inheriting class.\n\n    The `instrument_list` is a list of (module, method) pairs that will be\n    instrumented. The module/method need not be imported -- in fact, because\n    instrumentation modules are all processed during the instrumentation\n    process, lazy imports should be used in order to avoid ImportError\n    exceptions.\n\n    The `instrument()` method will be called for each InstrumentedModule\n    listed in the instrument register (elasticapm.instrumentation.register),\n    and each method in the `instrument_list` will be wrapped (using wrapt)\n    with the `call_if_sampling()` function, which (by default) will either\n    call the wrapped function by itself, or pass it into `call()` to be\n    called if there is a transaction active.\n\n    For simple span-wrapping of instrumented libraries, a very simple\n    InstrumentedModule might look like this::\n\n        from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule\n        from elasticapm.traces import capture_span\n\n        class Jinja2Instrumentation(AbstractInstrumentedModule):\n            name = \"jinja2\"\n            instrument_list = [(\"jinja2\", \"Template.render\")]\n            def call(self, module, method, wrapped, instance, args, kwargs):\n                signature = instance.name or instance.filename\n                with capture_span(signature, span_type=\"template\", span_subtype=\"jinja2\", span_action=\"render\"):\n                    return wrapped(*args, **kwargs)\n\n    This class can also be used to instrument callables which are expected to\n    create their own transactions (rather than spans within a transaction).\n    In this case, set `creates_transaction = True` next to your `name` and\n    `instrument_list`. This tells the instrumenting code to always wrap the\n    method with `call()`, even if there is no transaction active. It is\n    expected in this case that a new transaction will be created as part of\n    your `call()` method.\n    \"\"\"\n\n    name = None\n    mutates_unsampled_arguments = False\n    creates_transactions = False\n\n    instrument_list = [\n        # List of (module, method) pairs to instrument. E.g.:\n        # (\"requests.sessions\", \"Session.send\"),\n    ]\n\n    def __init__(self):\n        self.originals = {}\n        self.instrumented = False\n\n        assert self.name is not None\n\n    def get_wrapped_name(self, wrapped, instance, fallback_method=None):\n        wrapped_name = []\n        if hasattr(instance, \"__class__\") and hasattr(instance.__class__, \"__name__\"):\n            wrapped_name.append(instance.__class__.__name__)\n\n        if hasattr(wrapped, \"__name__\"):\n            wrapped_name.append(wrapped.__name__)\n        elif fallback_method:\n            attribute = fallback_method.split(\".\")\n            if len(attribute) == 2:\n                wrapped_name.append(attribute[1])\n\n        return \".\".join(wrapped_name)\n\n    def get_instrument_list(self):\n        return self.instrument_list\n\n    def instrument(self):\n        if self.instrumented:\n            return\n\n        skip_env_var = \"SKIP_INSTRUMENT_\" + str(self.name.upper())\n        if skip_env_var in os.environ:\n            logger.debug(\"Skipping instrumentation of %s. %s is set.\", self.name, skip_env_var)\n            return\n        try:\n            instrument_list = self.get_instrument_list()\n            skipped_modules = set()\n            instrumented_methods = []\n\n            for module, method in instrument_list:\n                try:\n                    # Skip modules we already failed to load\n                    if module in skipped_modules:\n                        continue\n                    # We jump through hoop here to get the original\n                    # `module`/`method` in the call to `call_if_sampling`\n                    parent, attribute, original = wrapt.resolve_path(module, method)\n                    self.originals[(module, method)] = original\n                    wrapper = wrapt.FunctionWrapper(original, functools.partial(self.call_if_sampling, module, method))\n                    wrapt.apply_patch(parent, attribute, wrapper)\n                    instrumented_methods.append((module, method))\n                except ImportError:\n                    # Could not import module\n                    logger.debug(\"Skipping instrumentation of %s. Module %s not found\", self.name, module)\n\n                    # Keep track of modules we couldn't load so we don't\n                    # try to instrument anything in that module again\n                    skipped_modules.add(module)\n                except AttributeError as ex:\n                    # Could not find thing in module\n                    logger.debug(\"Skipping instrumentation of %s.%s: %s\", module, method, ex)\n            if instrumented_methods:\n                logger.debug(\"Instrumented %s, %s\", self.name, \", \".join(\".\".join(m) for m in instrumented_methods))\n\n        except ImportError as ex:\n            logger.debug(\"Skipping instrumentation of %s. %s\", self.name, ex)\n        self.instrumented = True\n\n    def uninstrument(self):\n        if not self.instrumented or not self.originals:\n            return\n        uninstrumented_methods = []\n        for module, method in self.get_instrument_list():\n            if (module, method) in self.originals:\n                parent, attribute, wrapper = wrapt.resolve_path(module, method)\n                wrapt.apply_patch(parent, attribute, self.originals[(module, method)])\n                uninstrumented_methods.append((module, method))\n        if uninstrumented_methods:\n            logger.debug(\"Uninstrumented %s, %s\", self.name, \", \".join(\".\".join(m) for m in uninstrumented_methods))\n        self.instrumented = False\n        self.originals = {}\n\n    def call_if_sampling(self, module, method, wrapped, instance, args, kwargs):\n        \"\"\"\n        This is the function which will wrap the instrumented method/function.\n\n        By default, will call the instrumented method/function, via `call()`,\n        only if a transaction is active and sampled. This behavior can be\n        overridden by setting `creates_transactions = True` at the class\n        level.\n\n        If `creates_transactions == False` and there's an active transaction\n        with `transaction.is_sampled == False`, then the\n        `mutate_unsampled_call_args()` method is called, and the resulting\n        args and kwargs are passed into the wrapped function directly, not\n        via `call()`. This can e.g. be used to add traceparent headers to the\n        underlying http call for HTTP instrumentations, even if we're not\n        sampling the transaction.\n        \"\"\"\n        if self.creates_transactions:\n            return self.call(module, method, wrapped, instance, args, kwargs)\n        transaction = execution_context.get_transaction()\n        if not transaction:\n            return wrapped(*args, **kwargs)\n        elif not transaction.is_sampled:\n            args, kwargs = self.mutate_unsampled_call_args(module, method, wrapped, instance, args, kwargs, transaction)\n            return wrapped(*args, **kwargs)\n        else:\n            return self.call(module, method, wrapped, instance, args, kwargs)\n\n    def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):\n        \"\"\"\n        Method called for unsampled wrapped calls. This can e.g. be used to\n        add traceparent headers to the underlying http call for HTTP\n        instrumentations.\n\n        :param module:\n        :param method:\n        :param wrapped:\n        :param instance:\n        :param args:\n        :param kwargs:\n        :param transaction:\n        :return:\n        \"\"\"\n        return args, kwargs\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        \"\"\"\n        Wrapped call. This method should gather all necessary data, then call\n        `wrapped` in a `capture_span` context manager.\n\n        Note that by default this wrapper will only be used if a transaction is\n        currently active. If you want the ability to create a transaction in\n        your `call()` method, set `create_transactions = True` at the class\n        level.\n\n        :param module: Name of the wrapped module\n        :param method: Name of the wrapped method/function\n        :param wrapped: the wrapped method/function object\n        :param instance: the wrapped instance\n        :param args: arguments to the wrapped method/function\n        :param kwargs: keyword arguments to the wrapped method/function\n        :return: the result of calling the wrapped method/function\n        \"\"\"\n        raise NotImplementedError\n", "path": "elasticapm/instrumentation/packages/base.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\nimport functools\nimport os\n\nfrom elasticapm.traces import execution_context\nfrom elasticapm.utils import wrapt\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.instrument\")\n\n\nclass ElasticAPMFunctionWrapper(wrapt.FunctionWrapper):\n    # used to differentiate between our own function wrappers and 1st/3rd party wrappers\n    pass\n\n\nclass AbstractInstrumentedModule(object):\n    \"\"\"\n    This class is designed to reduce the amount of code required to\n    instrument library functions using wrapt.\n\n    Instrumentation modules inherit from this class and override pieces as\n    needed. Only `name`, `instrumented_list`, and `call` are required in\n    the inheriting class.\n\n    The `instrument_list` is a list of (module, method) pairs that will be\n    instrumented. The module/method need not be imported -- in fact, because\n    instrumentation modules are all processed during the instrumentation\n    process, lazy imports should be used in order to avoid ImportError\n    exceptions.\n\n    The `instrument()` method will be called for each InstrumentedModule\n    listed in the instrument register (elasticapm.instrumentation.register),\n    and each method in the `instrument_list` will be wrapped (using wrapt)\n    with the `call_if_sampling()` function, which (by default) will either\n    call the wrapped function by itself, or pass it into `call()` to be\n    called if there is a transaction active.\n\n    For simple span-wrapping of instrumented libraries, a very simple\n    InstrumentedModule might look like this::\n\n        from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule\n        from elasticapm.traces import capture_span\n\n        class Jinja2Instrumentation(AbstractInstrumentedModule):\n            name = \"jinja2\"\n            instrument_list = [(\"jinja2\", \"Template.render\")]\n            def call(self, module, method, wrapped, instance, args, kwargs):\n                signature = instance.name or instance.filename\n                with capture_span(signature, span_type=\"template\", span_subtype=\"jinja2\", span_action=\"render\"):\n                    return wrapped(*args, **kwargs)\n\n    This class can also be used to instrument callables which are expected to\n    create their own transactions (rather than spans within a transaction).\n    In this case, set `creates_transaction = True` next to your `name` and\n    `instrument_list`. This tells the instrumenting code to always wrap the\n    method with `call()`, even if there is no transaction active. It is\n    expected in this case that a new transaction will be created as part of\n    your `call()` method.\n    \"\"\"\n\n    name = None\n    mutates_unsampled_arguments = False\n    creates_transactions = False\n\n    instrument_list = [\n        # List of (module, method) pairs to instrument. E.g.:\n        # (\"requests.sessions\", \"Session.send\"),\n    ]\n\n    def __init__(self):\n        self.originals = {}\n        self.instrumented = False\n\n        assert self.name is not None\n\n    def get_wrapped_name(self, wrapped, instance, fallback_method=None):\n        wrapped_name = []\n        if hasattr(instance, \"__class__\") and hasattr(instance.__class__, \"__name__\"):\n            wrapped_name.append(instance.__class__.__name__)\n\n        if hasattr(wrapped, \"__name__\"):\n            wrapped_name.append(wrapped.__name__)\n        elif fallback_method:\n            attribute = fallback_method.split(\".\")\n            if len(attribute) == 2:\n                wrapped_name.append(attribute[1])\n\n        return \".\".join(wrapped_name)\n\n    def get_instrument_list(self):\n        return self.instrument_list\n\n    def instrument(self):\n        if self.instrumented:\n            return\n\n        skip_env_var = \"SKIP_INSTRUMENT_\" + str(self.name.upper())\n        if skip_env_var in os.environ:\n            logger.debug(\"Skipping instrumentation of %s. %s is set.\", self.name, skip_env_var)\n            return\n        try:\n            instrument_list = self.get_instrument_list()\n            skipped_modules = set()\n            instrumented_methods = []\n\n            for module, method in instrument_list:\n                try:\n                    # Skip modules we already failed to load\n                    if module in skipped_modules:\n                        continue\n                    # We jump through hoop here to get the original\n                    # `module`/`method` in the call to `call_if_sampling`\n                    parent, attribute, original = wrapt.resolve_path(module, method)\n                    if isinstance(original, ElasticAPMFunctionWrapper):\n                        logger.debug(\"%s.%s already instrumented, skipping\", module, method)\n                        continue\n                    self.originals[(module, method)] = original\n                    wrapper = ElasticAPMFunctionWrapper(\n                        original, functools.partial(self.call_if_sampling, module, method)\n                    )\n                    wrapt.apply_patch(parent, attribute, wrapper)\n                    instrumented_methods.append((module, method))\n                except ImportError:\n                    # Could not import module\n                    logger.debug(\"Skipping instrumentation of %s. Module %s not found\", self.name, module)\n\n                    # Keep track of modules we couldn't load so we don't\n                    # try to instrument anything in that module again\n                    skipped_modules.add(module)\n                except AttributeError as ex:\n                    # Could not find thing in module\n                    logger.debug(\"Skipping instrumentation of %s.%s: %s\", module, method, ex)\n            if instrumented_methods:\n                logger.debug(\"Instrumented %s, %s\", self.name, \", \".join(\".\".join(m) for m in instrumented_methods))\n\n        except ImportError as ex:\n            logger.debug(\"Skipping instrumentation of %s. %s\", self.name, ex)\n        self.instrumented = True\n\n    def uninstrument(self):\n        if not self.instrumented or not self.originals:\n            return\n        uninstrumented_methods = []\n        for module, method in self.get_instrument_list():\n            if (module, method) in self.originals:\n                parent, attribute, wrapper = wrapt.resolve_path(module, method)\n                wrapt.apply_patch(parent, attribute, self.originals[(module, method)])\n                uninstrumented_methods.append((module, method))\n        if uninstrumented_methods:\n            logger.debug(\"Uninstrumented %s, %s\", self.name, \", \".join(\".\".join(m) for m in uninstrumented_methods))\n        self.instrumented = False\n        self.originals = {}\n\n    def call_if_sampling(self, module, method, wrapped, instance, args, kwargs):\n        \"\"\"\n        This is the function which will wrap the instrumented method/function.\n\n        By default, will call the instrumented method/function, via `call()`,\n        only if a transaction is active and sampled. This behavior can be\n        overridden by setting `creates_transactions = True` at the class\n        level.\n\n        If `creates_transactions == False` and there's an active transaction\n        with `transaction.is_sampled == False`, then the\n        `mutate_unsampled_call_args()` method is called, and the resulting\n        args and kwargs are passed into the wrapped function directly, not\n        via `call()`. This can e.g. be used to add traceparent headers to the\n        underlying http call for HTTP instrumentations, even if we're not\n        sampling the transaction.\n        \"\"\"\n        if self.creates_transactions:\n            return self.call(module, method, wrapped, instance, args, kwargs)\n        transaction = execution_context.get_transaction()\n        if not transaction:\n            return wrapped(*args, **kwargs)\n        elif not transaction.is_sampled:\n            args, kwargs = self.mutate_unsampled_call_args(module, method, wrapped, instance, args, kwargs, transaction)\n            return wrapped(*args, **kwargs)\n        else:\n            return self.call(module, method, wrapped, instance, args, kwargs)\n\n    def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):\n        \"\"\"\n        Method called for unsampled wrapped calls. This can e.g. be used to\n        add traceparent headers to the underlying http call for HTTP\n        instrumentations.\n\n        :param module:\n        :param method:\n        :param wrapped:\n        :param instance:\n        :param args:\n        :param kwargs:\n        :param transaction:\n        :return:\n        \"\"\"\n        return args, kwargs\n\n    def call(self, module, method, wrapped, instance, args, kwargs):\n        \"\"\"\n        Wrapped call. This method should gather all necessary data, then call\n        `wrapped` in a `capture_span` context manager.\n\n        Note that by default this wrapper will only be used if a transaction is\n        currently active. If you want the ability to create a transaction in\n        your `call()` method, set `create_transactions = True` at the class\n        level.\n\n        :param module: Name of the wrapped module\n        :param method: Name of the wrapped method/function\n        :param wrapped: the wrapped method/function object\n        :param instance: the wrapped instance\n        :param args: arguments to the wrapped method/function\n        :param kwargs: keyword arguments to the wrapped method/function\n        :return: the result of calling the wrapped method/function\n        \"\"\"\n        raise NotImplementedError\n", "path": "elasticapm/instrumentation/packages/base.py"}]}