feat: ASGI middleware by untitaker · Pull Request #429 · getsentry/sentry-python · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions sentry_sdk/integrations/asgi.py
3 changes: 3 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ def _capture_internal_warnings():
if "SessionAuthenticationMiddleware" in str(warning.message):
continue

if "Something has already installed a non-asyncio" in str(warning.message):
continue

raise AssertionError(warning)


Expand Down
3 changes: 3 additions & 0 deletions tests/integrations/asgi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import pytest

pytest.importorskip("starlette")
120 changes: 120 additions & 0 deletions tests/integrations/asgi/test_asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import sys

import pytest
from sentry_sdk import capture_message
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.testclient import TestClient


@pytest.fixture
def app():
app = Starlette()

@app.route("/sync-message")
def hi(request):
capture_message("hi", level="error")
return PlainTextResponse("ok")

@app.route("/async-message")
async def hi2(request):
capture_message("hi", level="error")
return PlainTextResponse("ok")

app.add_middleware(SentryAsgiMiddleware)

return app


@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
def test_sync_request_data(sentry_init, app, capture_events):
sentry_init(send_default_pii=True)
events = capture_events()

client = TestClient(app)
response = client.get("/sync-message?foo=bar")

assert response.status_code == 200

event, = events
assert event["transaction"] == "tests.integrations.asgi.test_asgi.app.<locals>.hi"
assert event["request"]["env"] == {"REMOTE_ADDR": "testclient"}
assert set(event["request"]["headers"]) == {
"accept",
"accept-encoding",
"connection",
"host",
"user-agent",
}
assert event["request"]["query_string"] == "foo=bar"
assert event["request"]["url"].endswith("/sync-message")
assert event["request"]["method"] == "GET"

# Assert that state is not leaked
events.clear()
capture_message("foo")
event, = events

assert "request" not in event
assert "transaction" not in event


def test_async_request_data(sentry_init, app, capture_events):
sentry_init(send_default_pii=True)
events = capture_events()

client = TestClient(app)
response = client.get("/async-message?foo=bar")

assert response.status_code == 200

event, = events
assert event["transaction"] == "tests.integrations.asgi.test_asgi.app.<locals>.hi2"
assert event["request"]["env"] == {"REMOTE_ADDR": "testclient"}
assert set(event["request"]["headers"]) == {
"accept",
"accept-encoding",
"connection",
"host",
"user-agent",
}
assert event["request"]["query_string"] == "foo=bar"
assert event["request"]["url"].endswith("/async-message")
assert event["request"]["method"] == "GET"

# Assert that state is not leaked
events.clear()
capture_message("foo")
event, = events

assert "request" not in event
assert "transaction" not in event


def test_errors(sentry_init, app, capture_events):
sentry_init(send_default_pii=True)
events = capture_events()

@app.route("/error")
def myerror(request):
raise ValueError("oh no")

client = TestClient(app, raise_server_exceptions=False)
response = client.get("/error")

assert response.status_code == 500

event, = events
assert (
event["transaction"]
== "tests.integrations.asgi.test_asgi.test_errors.<locals>.myerror"
)
exception, = event["exception"]["values"]

assert exception["type"] == "ValueError"
assert exception["value"] == "oh no"
assert any(
frame["filename"].endswith("tests/integrations/asgi/test_asgi.py")
for frame in exception["stacktrace"]["frames"]
)
3 changes: 3 additions & 0 deletions tests/integrations/django/channels/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import pytest

pytest.importorskip("channels")
34 changes: 34 additions & 0 deletions tests/integrations/django/channels/test_channels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import pytest


from channels.testing import HttpCommunicator

from sentry_sdk.integrations.django import DjangoIntegration

from tests.integrations.django.myapp.asgi import application


@pytest.mark.asyncio
async def test_basic(sentry_init, capture_events):
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
events = capture_events()

comm = HttpCommunicator(application, "GET", "/view-exc?test=query")
response = await comm.get_response()
assert response["status"] == 500

event, = events

exception, = event["exception"]["values"]
assert exception["type"] == "ZeroDivisionError"

# Test that the ASGI middleware got set up correctly. Right now this needs
# to be installed manually (see myapp/asgi.py)
assert event["transaction"] == "/view-exc"
assert event["request"] == {
"cookies": {},
"headers": {},
"method": "GET",
"query_string": "test=query",
"url": "/view-exc",
}
4 changes: 2 additions & 2 deletions tests/integrations/django/myapp/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

django.setup()

from sentry_asgi import SentryMiddleware
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware

application = get_default_application()
application = SentryMiddleware(application)
application = SentryAsgiMiddleware(application)
8 changes: 8 additions & 0 deletions tox.ini