Repository: DataDog/dd-trace-py

Issue:
Measuring Django management commands
When I use `patch_all(django=True)` inside my management command it doesn't behave the same as the API.

For context, I have a management command that does this:
1. Calls `patch_all`
1. Gets a message off the queue from SQS
1. calls `tracer.context_provider.activate(context)` on the context container in the message
1. creates a span with some tags

What I'm finding, is that the ORM/cache instrumentation doesn't seem to be working.
However, if I try to access `connections._datadog_original_connections_all` it exists which leads me to believe it is patched.

On a side note: we aren't getting an `apdex` score for our django applications even though they are correctly detected as `web` and have traces as such.


Discussion:

Resolving patch:
diff --git a/ddtrace/contrib/django/apps.py b/ddtrace/contrib/django/apps.py
--- a/ddtrace/contrib/django/apps.py
+++ b/ddtrace/contrib/django/apps.py
@@ -2,15 +2,7 @@
 from django.apps import AppConfig, apps
 
 # project
-from .db import patch_db
-from .conf import settings
-from .cache import patch_cache
-from .templates import patch_template
-from .middleware import insert_exception_middleware, insert_trace_middleware
-
-from ...internal.logger import get_logger
-
-log = get_logger(__name__)
+from .patch import apply_django_patches
 
 
 class TracerConfig(AppConfig):
@@ -23,46 +15,5 @@ def ready(self):
         Tracing capabilities must be enabled in this function so that
         all Django internals are properly configured.
         """
-        tracer = settings.TRACER
-
-        if settings.TAGS:
-            tracer.set_tags(settings.TAGS)
-
-        # configure the tracer instance
-        # TODO[manu]: we may use configure() but because it creates a new
-        # AgentWriter, it breaks all tests. The configure() behavior must
-        # be changed to use it in this integration
-        tracer.enabled = settings.ENABLED
-        tracer.writer.api.hostname = settings.AGENT_HOSTNAME
-        tracer.writer.api.port = settings.AGENT_PORT
-
-        if settings.AUTO_INSTRUMENT:
-            # trace Django internals
-            insert_trace_middleware()
-            insert_exception_middleware()
-
-            if settings.INSTRUMENT_TEMPLATE:
-                try:
-                    patch_template(tracer)
-                except Exception:
-                    log.exception('error patching Django template rendering')
-
-            if settings.INSTRUMENT_DATABASE:
-                try:
-                    patch_db(tracer)
-                except Exception:
-                    log.exception('error patching Django database connections')
-
-            if settings.INSTRUMENT_CACHE:
-                try:
-                    patch_cache(tracer)
-                except Exception:
-                    log.exception('error patching Django cache')
-
-            # Instrument rest_framework app to trace custom exception handling.
-            if apps.is_installed('rest_framework'):
-                try:
-                    from .restframework import patch_restframework
-                    patch_restframework(tracer)
-                except Exception:
-                    log.exception('error patching rest_framework app')
+        rest_framework_is_installed = apps.is_installed('rest_framework')
+        apply_django_patches(patch_rest_framework=rest_framework_is_installed)
diff --git a/ddtrace/contrib/django/patch.py b/ddtrace/contrib/django/patch.py
--- a/ddtrace/contrib/django/patch.py
+++ b/ddtrace/contrib/django/patch.py
@@ -1,6 +1,18 @@
+# 3rd party
 from ddtrace.vendor import wrapt
-
 import django
+from django.db import connections
+
+# project
+from .db import patch_db
+from .conf import settings
+from .cache import patch_cache
+from .templates import patch_template
+from .middleware import insert_exception_middleware, insert_trace_middleware
+
+from ...internal.logger import get_logger
+
+log = get_logger(__name__)
 
 
 def patch():
@@ -25,3 +37,58 @@ def traced_setup(wrapped, instance, args, kwargs):
             settings.INSTALLED_APPS.append('ddtrace.contrib.django')
 
     wrapped(*args, **kwargs)
+
+
+def apply_django_patches(patch_rest_framework):
+    """
+    Ready is called as soon as the registry is fully populated.
+    In order for all Django internals are properly configured, this
+    must be called after the app is finished starting
+    """
+    tracer = settings.TRACER
+
+    if settings.TAGS:
+        tracer.set_tags(settings.TAGS)
+
+    # configure the tracer instance
+    # TODO[manu]: we may use configure() but because it creates a new
+    # AgentWriter, it breaks all tests. The configure() behavior must
+    # be changed to use it in this integration
+    tracer.enabled = settings.ENABLED
+    tracer.writer.api.hostname = settings.AGENT_HOSTNAME
+    tracer.writer.api.port = settings.AGENT_PORT
+
+    if settings.AUTO_INSTRUMENT:
+        # trace Django internals
+        insert_trace_middleware()
+        insert_exception_middleware()
+
+        if settings.INSTRUMENT_TEMPLATE:
+            try:
+                patch_template(tracer)
+            except Exception:
+                log.exception('error patching Django template rendering')
+
+        if settings.INSTRUMENT_DATABASE:
+            try:
+                patch_db(tracer)
+                # This is the trigger to patch individual connections.
+                # By patching these here, all processes including
+                # management commands are also traced.
+                connections.all()
+            except Exception:
+                log.exception('error patching Django database connections')
+
+        if settings.INSTRUMENT_CACHE:
+            try:
+                patch_cache(tracer)
+            except Exception:
+                log.exception('error patching Django cache')
+
+        # Instrument rest_framework app to trace custom exception handling.
+        if patch_rest_framework:
+            try:
+                from .restframework import patch_restframework
+                patch_restframework(tracer)
+            except Exception:
+                log.exception('error patching rest_framework app')


Test patch:
diff --git a/tests/contrib/django/test_connection.py b/tests/contrib/django/test_connection.py
--- a/tests/contrib/django/test_connection.py
+++ b/tests/contrib/django/test_connection.py
@@ -1,9 +1,11 @@
+import mock
 import time
 
 # 3rd party
 from django.contrib.auth.models import User
 
 from ddtrace.contrib.django.conf import settings
+from ddtrace.contrib.django.patch import apply_django_patches, connections
 
 # testing
 from .utils import DjangoTraceTestCase, override_ddtrace_settings
@@ -61,3 +63,10 @@ def test_should_append_database_prefix(self):
         assert len(traces[0]) == 1
         span = traces[0][0]
         assert span.service == 'my_prefix_db-defaultdb'
+
+    def test_apply_django_patches_calls_connections_all(self):
+        with mock.patch.object(connections, 'all') as mock_connections:
+            apply_django_patches(patch_rest_framework=False)
+
+        assert mock_connections.call_count == 1
+        assert mock_connections.mock_calls == [mock.call()]