Repository: elastic/apm-agent-python

Issue:
Tornado <6.0 still tries to instrument (and fails with cryptic warning)
Flower version is flower==0.9.3
elastic-apm==6.7.2

Flower service is not working for elastic-apm==6.7.2. 
Can you please suggest which version is compatible for  flower==0.9.3.


Discussion:
Hi @akshaytml. Can you provide some more information on what the problem is? Stack traces or expected vs. observed behaviour would be very helpful. Thanks!
Hi @beniwohli, When we try to run the Flower service it is giving below error -
**site-packages/tornado/web.py:2228: RuntimeWarning: coroutine 'TornadoRequestExecuteInstrumentation.call' was never awaited
**RuntimeWarning: Enable tracemalloc to get the object allocation traceback****

When we disabled elastic APM from Django settings.py  it started working.

Ah, that's helpful. Is the version of tornado in your environment lower than 6.0.0? (you can check it with .e.g. `pip freeze | grep tornado`)

It looks like our Tornado instrumentation is not compatible with versions earlier than 6.0. We should skip instrumenting it for lower versions. In the meantime, you could set an environment variable `SKIP_INSTRUMENT_TORNADO_REQUEST_EXECUTE` to any value to skip instrumentation of tornado.
Yes its tornado==5.1.1.
Which version i need to install.
Please advise.
You should be fine with version of tornado 6.0.0, but that also requires at least flower 0.9.4.

Resolving patch:
diff --git a/elasticapm/instrumentation/packages/tornado.py b/elasticapm/instrumentation/packages/tornado.py
--- a/elasticapm/instrumentation/packages/tornado.py
+++ b/elasticapm/instrumentation/packages/tornado.py
@@ -35,9 +35,25 @@
 from elasticapm.instrumentation.packages.asyncio.base import AbstractInstrumentedModule, AsyncAbstractInstrumentedModule
 from elasticapm.traces import capture_span
 from elasticapm.utils.disttracing import TraceParent
+from elasticapm.utils.logging import get_logger
 
+logger = get_logger("elasticapm.instrument")
 
-class TornadoRequestExecuteInstrumentation(AsyncAbstractInstrumentedModule):
+
+class TornadoBaseInstrumentedModule(AbstractInstrumentedModule):
+    def instrument(self):
+        try:
+            import tornado
+
+            if tornado.version_info[0] < 6:
+                logger.debug("Skipping instrumentation of %s. Tornado is only supported with version 6.0+", self.name)
+                return
+        except ImportError:
+            pass
+        super().instrument()
+
+
+class TornadoRequestExecuteInstrumentation(TornadoBaseInstrumentedModule, AsyncAbstractInstrumentedModule):
     name = "tornado_request_execute"
     creates_transactions = True
     instrument_list = [("tornado.web", "RequestHandler._execute")]
@@ -78,7 +94,7 @@ async def call(self, module, method, wrapped, instance, args, kwargs):
         return ret
 
 
-class TornadoHandleRequestExceptionInstrumentation(AbstractInstrumentedModule):
+class TornadoHandleRequestExceptionInstrumentation(TornadoBaseInstrumentedModule):
     name = "tornado_handle_request_exception"
 
     instrument_list = [("tornado.web", "RequestHandler._handle_request_exception")]
@@ -115,7 +131,7 @@ def call(self, module, method, wrapped, instance, args, kwargs):
         return wrapped(*args, **kwargs)
 
 
-class TornadoRenderInstrumentation(AbstractInstrumentedModule):
+class TornadoRenderInstrumentation(TornadoBaseInstrumentedModule):
     name = "tornado_render"
 
     instrument_list = [("tornado.web", "RequestHandler.render")]


Test patch:
diff --git a/tests/contrib/asyncio/tornado/tornado_tests.py b/tests/contrib/asyncio/tornado/tornado_tests.py
--- a/tests/contrib/asyncio/tornado/tornado_tests.py
+++ b/tests/contrib/asyncio/tornado/tornado_tests.py
@@ -41,6 +41,8 @@
 from elasticapm.conf import constants
 from elasticapm.contrib.tornado import ElasticAPM
 from elasticapm.utils.disttracing import TraceParent
+from elasticapm.utils.wrapt import BoundFunctionWrapper
+from tests.utils import assert_any_record_contains
 
 pytestmark = pytest.mark.tornado
 
@@ -254,3 +256,28 @@ async def test_tornado_transaction_ignore_urls(app, base_url, http_client):
 
     response = await http_client.fetch(base_url + "/render")
     assert len(elasticapm_client.events[constants.TRANSACTION]) == 1
+
+
+def test_old_tornado_not_instrumented(caplog):
+    with mock.patch("tornado.version_info", (5, 11, 0)):
+        try:
+            with caplog.at_level("DEBUG"):
+                elasticapm.instrument()
+            from tornado.web import RequestHandler
+
+            assert not isinstance(RequestHandler._execute, BoundFunctionWrapper)
+            assert not isinstance(RequestHandler._handle_request_exception, BoundFunctionWrapper)
+            assert not isinstance(RequestHandler.render, BoundFunctionWrapper)
+        finally:
+            elasticapm.uninstrument()
+    assert_any_record_contains(
+        caplog.records, "Skipping instrumentation of tornado_render. Tornado is only supported with version 6.0+"
+    )
+    assert_any_record_contains(
+        caplog.records,
+        "Skipping instrumentation of tornado_handle_request_exception. Tornado is only supported with version 6.0+",
+    )
+    assert_any_record_contains(
+        caplog.records,
+        "Skipping instrumentation of tornado_request_execute. Tornado is only supported with version 6.0+",
+    )