diff --git a/sentry-python/5274/.gitignore b/sentry-python/5274/.gitignore new file mode 100644 index 0000000..ab41eb8 --- /dev/null +++ b/sentry-python/5274/.gitignore @@ -0,0 +1,5 @@ +.venv/ +__pycache__/ +*.pyc +db.sqlite3 +*.lock diff --git a/sentry-python/5274/README.md b/sentry-python/5274/README.md new file mode 100644 index 0000000..5bae5a7 --- /dev/null +++ b/sentry-python/5274/README.md @@ -0,0 +1,69 @@ +# Reproduction for sentry-python#5274 + +**Issue:** https://github.com/getsentry/sentry-python/issues/5274 + +## Description + +When running Django under ASGI with `send_default_pii=True`, Sentry's Django integration calls `request.user.is_authenticated` synchronously in the ASGI event processor. On Django 5.x this triggers lazy session-backed user resolution in an async context and raises `SynchronousOnlyOperation`. + +## Steps to Reproduce + +1. Install dependencies: + ```bash + uv sync + ``` + +2. Set up the database and create a superuser: + ```bash + uv run python manage.py migrate + uv run python manage.py createsuperuser --username admin --email admin@example.com + ``` + +3. (Optional) Set your Sentry DSN to see events: + ```bash + export SENTRY_DSN="your-dsn-here" + ``` + +4. Run the server with uvicorn (ASGI): + ```bash + uv run uvicorn asgi:application --host 127.0.0.1 --port 8000 + ``` + +5. In a browser: + - Go to http://127.0.0.1:8000/admin/ and log in + - Then visit http://127.0.0.1:8000/ + +6. Check the console output for the error. + +## Expected Behavior + +The Sentry SDK should capture errors without raising `SynchronousOnlyOperation`. In async contexts, the Django integration should use async-safe APIs (e.g., `request.auser()`) or skip user capture. + +## Actual Behavior + +Sentry logs an internal SDK error: + +``` +SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async. +``` + +The error occurs in the stack: +``` +.../sentry_sdk/integrations/django/__init__.py in is_authenticated + return request_user.is_authenticated +.../sentry_sdk/integrations/django/__init__.py in _set_user_info + if user is None or not is_authenticated(user): +.../sentry_sdk/integrations/django/asgi.py in asgi_request_event_processor + _set_user_info(request, event) +``` + +## Workaround + +Set `send_default_pii=False` and manually call `sentry_sdk.set_user()` after resolving the user asynchronously. + +## Environment + +- Python: 3.11+ +- Django: 5.1+ +- sentry-sdk: 2.48.0+ +- ASGI server: uvicorn diff --git a/sentry-python/5274/asgi.py b/sentry-python/5274/asgi.py new file mode 100644 index 0000000..6136794 --- /dev/null +++ b/sentry-python/5274/asgi.py @@ -0,0 +1,5 @@ +import os +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") +application = get_asgi_application() diff --git a/sentry-python/5274/manage.py b/sentry-python/5274/manage.py new file mode 100644 index 0000000..87ab2e2 --- /dev/null +++ b/sentry-python/5274/manage.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + from django.core.management import execute_from_command_line + execute_from_command_line(sys.argv) diff --git a/sentry-python/5274/pyproject.toml b/sentry-python/5274/pyproject.toml new file mode 100644 index 0000000..41ed15f --- /dev/null +++ b/sentry-python/5274/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "django-asgi-send-default-pii-repro" +version = "0.1.0" +description = "Reproduction for sentry-python#5274" +requires-python = ">=3.11" +dependencies = [ + "django>=5.1", + "sentry-sdk>=2.48.0", + "uvicorn", +] + +[tool.uv] +dev-dependencies = [] diff --git a/sentry-python/5274/settings.py b/sentry-python/5274/settings.py new file mode 100644 index 0000000..a589b84 --- /dev/null +++ b/sentry-python/5274/settings.py @@ -0,0 +1,62 @@ +import os +import sentry_sdk + +# Initialize Sentry with send_default_pii=True +# This triggers the bug when accessing request.user in async context +sentry_sdk.init( + dsn=os.environ.get("SENTRY_DSN", ""), + send_default_pii=True, # <-- This causes the issue in ASGI + traces_sample_rate=1.0, +) + +SECRET_KEY = "insecure-secret-key-for-repro" +DEBUG = True +ALLOWED_HOSTS = ["*"] + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", +] + +ROOT_URLCONF = "urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +ASGI_APPLICATION = "asgi.application" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "db.sqlite3", + } +} + +STATIC_URL = "/static/" +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/sentry-python/5274/urls.py b/sentry-python/5274/urls.py new file mode 100644 index 0000000..ac462a3 --- /dev/null +++ b/sentry-python/5274/urls.py @@ -0,0 +1,48 @@ +import logging +from django.contrib import admin +from django.urls import path +from django.http import HttpResponse + +# Enable debug logging for sentry_sdk to see internal errors +logging.basicConfig(level=logging.DEBUG) +logging.getLogger("sentry_sdk").setLevel(logging.DEBUG) + + +async def async_view(request): + """ + An async view that triggers Sentry error capture. + + When send_default_pii=True, Sentry's Django integration will try to + access request.user.is_authenticated synchronously in the ASGI event + processor, which triggers SynchronousOnlyOperation on Django 5.x. + + The error happens in sentry_sdk/integrations/django/asgi.py when + _set_user_info() is called, which accesses request.user.is_authenticated. + """ + print(f"\n{'='*60}") + print(f"Request user type: {type(request.user)}") + print(f"Is async context: True (this is an async view)") + print(f"{'='*60}\n") + + # Trigger an error to be captured by Sentry + # The bug manifests when Sentry tries to capture user info + try: + raise ValueError("Test error to trigger Sentry capture") + except ValueError: + import sentry_sdk + # This capture_exception call triggers the ASGI event processor + # which will try to access request.user.is_authenticated synchronously + sentry_sdk.capture_exception() + print("Exception captured by Sentry - check for SynchronousOnlyOperation in logs") + + return HttpResponse( + "Check the server console for SynchronousOnlyOperation error from Sentry SDK.\n" + "The error occurs when Sentry tries to access request.user.is_authenticated " + "in the ASGI event processor." + ) + + +urlpatterns = [ + path("admin/", admin.site.urls), + path("", async_view), +]