Repository: DataDog/dd-trace-py

Issue:
500 Error with pylibmc+django+gunicorn
Hi, I'm integrating the Django cache in my app, if I run gunicorn without `ddtrace-run` everything works ok, but running with `ddtrace-run gunicorn app.wsgi:application --name app --workers 2 --timeout 30 --log-level=DEBUG --log-file=- --bind=unix:/projects/app/run/gunicorn.sock` I'm getting this error:

```
[2019-02-16 11:30:37 +0000] [138] [DEBUG] GET /api/accounts/me/
Internal Server Error: /api/accounts/me/
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/usr/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/rest_framework/views.py", line 489, in dispatch
    response = self.handle_exception(exc)
  File "/usr/lib/python3.6/site-packages/ddtrace/contrib/django/restframework.py", line 26, in _traced_handle_exception
    return wrapped(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/rest_framework/views.py", line 449, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/lib/python3.6/site-packages/rest_framework/views.py", line 486, in dispatch
    response = handler(request, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/rest_framework/generics.py", line 201, in get
    return self.list(request, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/django/utils/decorators.py", line 67, in _wrapper
    return bound_func(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/django/utils/decorators.py", line 141, in _wrapped_view
    result = middleware.process_request(request)
  File "/usr/lib/python3.6/site-packages/django/middleware/cache.py", line 133, in process_request
    cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
  File "/usr/lib/python3.6/site-packages/django/utils/cache.py", line 362, in get_cache_key
    headerlist = cache.get(cache_key)
  File "/usr/lib/python3.6/site-packages/ddtrace/contrib/django/cache.py", line 67, in wrapped
    return method(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/django/core/cache/backends/memcached.py", line 79, in get
    val = self._cache.get(key)
  File "/usr/lib/python3.6/site-packages/ddtrace/contrib/pylibmc/client.py", line 72, in get
    return self._trace_cmd("get", *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/ddtrace/contrib/pylibmc/client.py", line 115, in _trace_cmd
    with self._span(method_name) as span:
AttributeError: __enter__
```

My lib versions:
```
ddtrace==0.20.4
Django==1.11.12
gunicorn==19.4.5
pylibmc==1.6.0
```

And my settings:
```
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': os.environ.get('MEMCACHED_HOST', 'memcached:11211'),
    }
}
```
Any idea?

Thanks!


Discussion:
I have the same problem, there is a flaw in TracedClient (see <=== comments)
https://github.com/DataDog/dd-trace-py/blob/master/ddtrace/contrib/pylibmc/client.py#L106
```
    def _trace_cmd(self, method_name, *args, **kwargs):
        """ trace the execution of the method with the given name and will
            patch the first arg.
        """
        method = getattr(self.__wrapped__, method_name)
        with self._span(method_name) as span: <=== when self._span() returns None, you get an error 

            if span and args:
                span.set_tag(memcached.QUERY, '%s %s' % (method_name, args[0]))

            return method(*args, **kwargs)
```
https://github.com/DataDog/dd-trace-py/blob/master/ddtrace/contrib/pylibmc/client.py#L127
```
    def _span(self, cmd_name):
        """ Return a span timing the given command. """
        pin = ddtrace.Pin.get_from(self)
        if pin and pin.enabled(): <=== and self._span() returns None if tracer is disabled
            span = pin.tracer.trace(
                'memcached.cmd',
                service=pin.service,
                resource=cmd_name,
                # TODO(Benjamin): set a better span type
                span_type='cache')

            try:
                self._tag_span(span)
            except Exception:
                log.debug('error tagging span', exc_info=True)
            return span
```
This issue is a blocker. Is anyone working on this?

Resolving patch:
diff --git a/ddtrace/contrib/pylibmc/client.py b/ddtrace/contrib/pylibmc/client.py
--- a/ddtrace/contrib/pylibmc/client.py
+++ b/ddtrace/contrib/pylibmc/client.py
@@ -1,3 +1,4 @@
+from contextlib import contextmanager
 import random
 
 # 3p
@@ -121,22 +122,28 @@ def _trace_multi_cmd(self, method_name, *args, **kwargs):
 
             return method(*args, **kwargs)
 
+    @contextmanager
+    def _no_span(self):
+        yield None
+
     def _span(self, cmd_name):
         """ Return a span timing the given command. """
         pin = ddtrace.Pin.get_from(self)
-        if pin and pin.enabled():
-            span = pin.tracer.trace(
-                'memcached.cmd',
-                service=pin.service,
-                resource=cmd_name,
-                # TODO(Benjamin): set a better span type
-                span_type='cache')
-
-            try:
-                self._tag_span(span)
-            except Exception:
-                log.debug('error tagging span', exc_info=True)
-            return span
+        if not pin or not pin.enabled():
+            return self._no_span()
+
+        span = pin.tracer.trace(
+            'memcached.cmd',
+            service=pin.service,
+            resource=cmd_name,
+            # TODO(Benjamin): set a better span type
+            span_type='cache')
+
+        try:
+            self._tag_span(span)
+        except Exception:
+            log.debug('error tagging span', exc_info=True)
+        return span
 
     def _tag_span(self, span):
         # FIXME[matt] the host selection is buried in c code. we can't tell what it's actually


Test patch:
diff --git a/tests/contrib/pylibmc/test.py b/tests/contrib/pylibmc/test.py
--- a/tests/contrib/pylibmc/test.py
+++ b/tests/contrib/pylibmc/test.py
@@ -218,6 +218,21 @@ def test_analytics_without_rate(self):
         self.assertEqual(len(spans), 1)
         self.assertEqual(spans[0].get_metric(ANALYTICS_SAMPLE_RATE_KEY), 1.0)
 
+    def test_disabled(self):
+        """
+        Ensure client works when the tracer is disabled
+        """
+        client, tracer = self.get_client()
+        try:
+            tracer.enabled = False
+
+            client.set('a', 'crow')
+
+            spans = self.get_spans()
+            assert len(spans) == 0
+        finally:
+            tracer.enabled = True
+
 
 class TestPylibmcLegacy(BaseTracerTestCase, PylibmcCore):
     """Test suite for the tracing of pylibmc with the legacy TracedClient interface"""