Repository: getsentry/sentry-python

Issue:
No request data with django
I recently decided to switch from Raven to `sentry-sdk` version 0.7.3.

When an event was reported using Raven I use to have many useful tags (the URLs originating the error, the user browser/device, OS information and much more). All this comes out of the box without the need to include any scripts in the frontend.

Despite sentry-sdk has simplified the configuration process I find myself with a weaker error tracking system (fewer information on each event).

I am working on a Django project and followed the [docs](https://docs.sentry.io/platforms/python/django/) 

Am I missing something? 



Discussion:
The tags you see are generated serverside and are inferred from HTTP headers and the current URL. All of this should be there when you turn on the Django integration. We're using this for Sentry itself and it generally is supposed to work.

From what you've told me it seems that you generally have no request data attached to your events. Could you paste the exact code you're using, and more importantly, where do you call `init` of the SDK?
@bluesurfer any update on this? This seems like a bug in the SDK, even if just a UX bug.
@untitaker oops, sorry for delay.. I just put the docs code (with init sentry) at the end of my settings file and I haven't defined any `LOGGING` configuration. 

> From what you've told me it seems that you generally have no request data attached to your events.

Yes, that's the issue. My previously working logging configuration with Raven was:
```
# Raven
# ------------------------------------------------------------------------------
INSTALLED_APPS += ['raven.contrib.django.raven_compat']  # noqa F405
MIDDLEWARE = ['raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware'] + MIDDLEWARE

# Sentry
# ------------------------------------------------------------------------------
SENTRY_DSN = env('DJANGO_SENTRY_DSN')
SENTRY_CLIENT = env('DJANGO_SENTRY_CLIENT', default='raven.contrib.django.raven_compat.DjangoClient')
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'root': {
        'level': 'WARNING',
        'handlers': ['sentry'],
    },
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s '
                      '%(process)d %(thread)d %(message)s'
        },
    },
    'handlers': {
        'sentry': {
            'level': 'ERROR',
            'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose'
        }
    },
    'loggers': {
        'django.db.backends': {
            'level': 'ERROR',
            'handlers': ['console'],
            'propagate': False,
        },
        'raven': {
            'level': 'DEBUG',
            'handlers': ['console'],
            'propagate': False,
        },
        'sentry.errors': {
            'level': 'DEBUG',
            'handlers': ['console'],
            'propagate': False,
        },
        'django.security.DisallowedHost': {
            'level': 'WARNING',
            'handlers': ['console', 'sentry'],
            'propagate': False,
        },
    },
}

SENTRY_CELERY_LOGLEVEL = env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO)
RAVEN_CONFIG = {
    'dsn': SENTRY_DSN
}
```
Than using sentry-sdk I simply replaced it with:

```
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn=SENTRY_DSN,
    integrations=[DjangoIntegration()]
)
```
Which version of Django and Python are you using?
@untitaker Django 2.1.7 with python 3.6.7. 

Also I am using Channels
I am also experiencing the same thing. Very similar setup to the one described by @bluesurfer. The Raven Python setup included a 'Cookies' section as well as better 'User' information (these both seem to be Django `request` object information). 

I am using Django 1.11.20, Python 3.6.7 and was trying Sentry 0.8.0.
@bcollazo @untitaker I have some updates.

I am following [cookiecutter-django](https://github.com/pydanny/cookiecutter-django) repository to lay down my project. It seems that with recent updates (also from sentry-sdk) the following configuration to me is working:

```
# LOGGING
# ------------------------------------------------------------------------------
LOGGING = {
    "version": 1,
    "disable_existing_loggers": True,
    "formatters": {
        "verbose": {
            "format": "%(levelname)s %(asctime)s %(module)s "
                      "%(process)d %(thread)d %(message)s"
        }
    },
    "handlers": {
        "console": {
            "level": "DEBUG",
            "class": "logging.StreamHandler",
            "formatter": "verbose",
        }
    },
    "loggers": {
        "django.db.backends": {
            "level": "ERROR",
            "handlers": ["console"],
            "propagate": False,
        },
        # Errors logged by the SDK itself
        "sentry_sdk": {"level": "ERROR", "handlers": ["console"], "propagate": False},
        "django.security.DisallowedHost": {
            "level": "ERROR",
            "handlers": ["console"],
            "propagate": False,
        },
    },
}

# Sentry
# ------------------------------------------------------------------------------
SENTRY_DSN = env("SENTRY_DSN")
SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO)

sentry_logging = LoggingIntegration(
    level=SENTRY_LOG_LEVEL,  # Capture info and above as breadcrumbs
    event_level=logging.ERROR,  # Send no events from log messages
)

sentry_sdk.init(
    dsn=SENTRY_DSN,
    integrations=[sentry_logging, DjangoIntegration(), CeleryIntegration()],
)
```

Therefore to me this issue could be closed.
Ah that's neat, I wasn't aware of https://github.com/pydanny/cookiecutter-django/pull/1820!

Tbh I think it's highly unlikely that @bluesurfer has the same actual problem as you, @bcollazo. Could you open a new issue with more information?
We are also facing the same issue

We are not getting the Django request data info in sentry console with updated sentry SDK

With old raven Django client, there is no issue about getting request data in sentry

We are getting request data in sentry console, only if there is some exception raised before our program did access the request.body

We are getting `can't read requst data once its already read` error at `sentry_sdk.integrations.django.DjangoRequestExtractor#raw_data`



@anush0247 could you link an event that exhibits this behavior?
@untitaker please find below link

https://sentry.io/organizations/backend-developers/issues/1054285117/?project=240166&query=is%3Aunresolved

@untitaker any update ?
Still working on it, I think I know what the issue is. Thanks for the
information so far!

On Wed, Jun 19, 2019 at 5:59 AM Aneesh Kumar <notifications@github.com>
wrote:

> @untitaker <https://github.com/untitaker> any update ?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/getsentry/sentry-python/issues/267?email_source=notifications&email_token=AAGMPRJG6AOIRCY7X67MWO3P3GVKHA5CNFSM4GYLHTG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYATSHA#issuecomment-503396636>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAGMPRPMLESYODSOIVP2YW3P3GVKHANCNFSM4GYLHTGQ>
> .
>


Resolving patch:
diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py
--- a/sentry_sdk/integrations/django/__init__.py
+++ b/sentry_sdk/integrations/django/__init__.py
@@ -124,6 +124,25 @@ def sentry_patched_get_response(self, request):
 
         BaseHandler.get_response = sentry_patched_get_response
 
+        try:
+            from rest_framework.views import APIView  # type: ignore
+        except ImportError:
+            pass
+        else:
+            # DRF's request type (which wraps the Django request and proxies
+            # all attrs) has some attributes such as `data` which buffer
+            # request data.  We want to use those in the RequestExtractor to
+            # get body data more reliably.
+            old_drf_initial = APIView.initial
+
+            def sentry_patched_drf_initial(self, request, *args, **kwargs):
+                with capture_internal_exceptions():
+                    request._request._sentry_drf_request_backref = weakref.ref(request)
+                    pass
+                return old_drf_initial(self, request, *args, **kwargs)
+
+            APIView.initial = sentry_patched_drf_initial
+
         signals.got_request_exception.connect(_got_request_exception)
 
         @add_global_event_processor
@@ -197,6 +216,13 @@ def event_processor(event, hint):
         if request is None:
             return event
 
+        try:
+            drf_request = request._sentry_drf_request_backref()
+            if drf_request is not None:
+                request = drf_request
+        except AttributeError:
+            pass
+
         try:
             if integration.transaction_style == "function_name":
                 event["transaction"] = transaction_from_function(


Test patch:
diff --git a/tests/integrations/django/myapp/urls.py b/tests/integrations/django/myapp/urls.py
--- a/tests/integrations/django/myapp/urls.py
+++ b/tests/integrations/django/myapp/urls.py
@@ -42,6 +42,13 @@
     urlpatterns.append(
         path("rest-framework-exc", views.rest_framework_exc, name="rest_framework_exc")
     )
+    urlpatterns.append(
+        path(
+            "rest-framework-read-body-and-exc",
+            views.rest_framework_read_body_and_exc,
+            name="rest_framework_read_body_and_exc",
+        )
+    )
 except AttributeError:
     pass
 
diff --git a/tests/integrations/django/myapp/views.py b/tests/integrations/django/myapp/views.py
--- a/tests/integrations/django/myapp/views.py
+++ b/tests/integrations/django/myapp/views.py
@@ -11,6 +11,11 @@
     def rest_framework_exc(request):
         1 / 0
 
+    @api_view(["POST"])
+    def rest_framework_read_body_and_exc(request):
+        request.data
+        1 / 0
+
 
 except ImportError:
     pass
diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py
--- a/tests/integrations/django/test_basic.py
+++ b/tests/integrations/django/test_basic.py
@@ -374,7 +374,7 @@ def test_read_request(sentry_init, client, capture_events):
 
     event, = events
 
-    assert event["request"]["data"] == {"hey": 42}
+    assert "data" not in event["request"]
 
 
 def test_template_exception(sentry_init, client, capture_events):
@@ -413,12 +413,15 @@ def test_template_exception(sentry_init, client, capture_events):
     ]
 
 
+@pytest.mark.parametrize(
+    "route", ["rest_framework_exc", "rest_framework_read_body_and_exc"]
+)
 @pytest.mark.parametrize(
     "type,event_request",
     [
         [
             "json",
-            {
+            lambda route: {
                 "cookies": {},
                 "data": {"foo": "bar"},
                 "env": {"SERVER_NAME": "localhost", "SERVER_PORT": "80"},
@@ -429,12 +432,12 @@ def test_template_exception(sentry_init, client, capture_events):
                 },
                 "method": "POST",
                 "query_string": "",
-                "url": "http://localhost/rest-framework-exc",
+                "url": "http://localhost/{}".format(route.replace("_", "-")),
             },
         ],
         [
             "formdata",
-            {
+            lambda route: {
                 "cookies": {},
                 "data": {"foo": "bar"},
                 "env": {"SERVER_NAME": "localhost", "SERVER_PORT": "80"},
@@ -445,13 +448,13 @@ def test_template_exception(sentry_init, client, capture_events):
                 },
                 "method": "POST",
                 "query_string": "",
-                "url": "http://localhost/rest-framework-exc",
+                "url": "http://localhost/{}".format(route.replace("_", "-")),
             },
         ],
     ],
 )
 def test_rest_framework_basic(
-    sentry_init, client, capture_events, capture_exceptions, type, event_request
+    sentry_init, client, capture_events, capture_exceptions, type, event_request, route
 ):
     pytest.importorskip("rest_framework")
     sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
@@ -460,12 +463,12 @@ def test_rest_framework_basic(
 
     if type == "json":
         client.post(
-            reverse("rest_framework_exc"),
+            reverse(route),
             data=json.dumps({"foo": "bar"}),
             content_type="application/json",
         )
     elif type == "formdata":
-        client.post(reverse("rest_framework_exc"), data={"foo": "bar"})
+        client.post(reverse(route), data={"foo": "bar"})
     else:
         assert False
 
@@ -475,4 +478,4 @@ def test_rest_framework_basic(
     event, = events
     assert event["exception"]["values"][0]["mechanism"]["type"] == "django"
 
-    assert event["request"] == event_request
+    assert event["request"] == event_request(route)