Skip to content

Commit 704c642

Browse files
authored
feat: aiohttp-server integration (#189)
* feat: aiohttp-server integration * fix: Ignore 403 * fix: Copypaste error * fix: aiohttp plugin * fix: Dont assert version numbers
1 parent 9a4fdba commit 704c642

File tree

4 files changed

+166
-6
lines changed

4 files changed

+166
-6
lines changed

sentry_sdk/integrations/aiohttp.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import sys
2+
import weakref
3+
4+
from sentry_sdk._compat import reraise
5+
from sentry_sdk.hub import Hub
6+
from sentry_sdk.integrations import Integration
7+
from sentry_sdk.integrations.logging import ignore_logger
8+
from sentry_sdk.integrations._wsgi_common import _filter_headers
9+
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
10+
11+
import asyncio
12+
from aiohttp.web import Application, HTTPError
13+
14+
15+
class AioHttpIntegration(Integration):
16+
identifier = "aiohttp"
17+
18+
@staticmethod
19+
def setup_once():
20+
if sys.version_info < (3, 7):
21+
# We better have contextvars or we're going to leak state between
22+
# requests.
23+
raise RuntimeError(
24+
"The aiohttp integration for Sentry requires Python 3.7+"
25+
)
26+
27+
ignore_logger("aiohttp.server")
28+
29+
old_handle = Application._handle
30+
31+
async def sentry_app_handle(self, request, *args, **kwargs):
32+
async def inner():
33+
hub = Hub.current
34+
if hub.get_integration(AioHttpIntegration) is None:
35+
return old_handle(self, request, *args, **kwargs)
36+
37+
weak_request = weakref.ref(request)
38+
39+
with Hub(Hub.current) as hub:
40+
with hub.configure_scope() as scope:
41+
scope.add_event_processor(_make_request_processor(weak_request))
42+
43+
try:
44+
response = await old_handle(self, request)
45+
except HTTPError:
46+
raise
47+
except Exception:
48+
reraise(*_capture_exception(hub))
49+
50+
return response
51+
52+
return await asyncio.create_task(inner())
53+
54+
Application._handle = sentry_app_handle
55+
56+
57+
def _make_request_processor(weak_request):
58+
def aiohttp_processor(event, hint):
59+
request = weak_request()
60+
if request is None:
61+
return event
62+
63+
with capture_internal_exceptions():
64+
# TODO: Figure out what to do with request body. Methods on request
65+
# are async, but event processors are not.
66+
67+
request_info = event.setdefault("request", {})
68+
69+
if "url" not in request_info:
70+
request_info["url"] = "%s://%s%s" % (
71+
request.scheme,
72+
request.host,
73+
request.path,
74+
)
75+
76+
if "query_string" not in request_info:
77+
request_info["query_string"] = request.query_string
78+
79+
if "method" not in request_info:
80+
request_info["method"] = request.method
81+
82+
if "env" not in request_info:
83+
request_info["env"] = {"REMOTE_ADDR": request.remote}
84+
85+
if "headers" not in request_info:
86+
request_info["headers"] = _filter_headers(dict(request.headers))
87+
88+
return event
89+
90+
return aiohttp_processor
91+
92+
93+
def _capture_exception(hub):
94+
exc_info = sys.exc_info()
95+
event, hint = event_from_exception(
96+
exc_info,
97+
client_options=hub.client.options,
98+
mechanism={"type": "aiohttp", "handled": False},
99+
)
100+
hub.capture_event(event, hint=hint)
101+
return exc_info
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import pytest
2+
3+
aiohttp = pytest.importorskip("aiohttp")
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from aiohttp import web
2+
3+
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
4+
5+
6+
async def test_basic(sentry_init, aiohttp_client, loop, capture_events):
7+
sentry_init(integrations=[AioHttpIntegration()])
8+
9+
async def hello(request):
10+
1 / 0
11+
12+
app = web.Application()
13+
app.router.add_get("/", hello)
14+
15+
events = capture_events()
16+
17+
client = await aiohttp_client(app)
18+
resp = await client.get("/")
19+
assert resp.status == 500
20+
21+
event, = events
22+
23+
exception, = event["exception"]["values"]
24+
assert exception["type"] == "ZeroDivisionError"
25+
request = event["request"]
26+
host = request["headers"]["Host"]
27+
28+
assert request["env"] == {"REMOTE_ADDR": "127.0.0.1"}
29+
assert request["method"] == "GET"
30+
assert request["query_string"] == ""
31+
assert request["url"] == f"http://{host}/"
32+
assert request["headers"] == {
33+
"Accept": "*/*",
34+
"Accept-Encoding": "gzip, deflate",
35+
"Host": host,
36+
"User-Agent": request["headers"]["User-Agent"],
37+
}
38+
39+
40+
async def test_403_not_captured(sentry_init, aiohttp_client, loop, capture_events):
41+
sentry_init(integrations=[AioHttpIntegration()])
42+
43+
async def hello(request):
44+
raise web.HTTPForbidden()
45+
46+
app = web.Application()
47+
app.router.add_get("/", hello)
48+
49+
events = capture_events()
50+
51+
client = await aiohttp_client(app)
52+
resp = await client.get("/")
53+
assert resp.status == 403
54+
55+
assert not events

tox.ini

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,11 @@ envlist =
3030

3131
{pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-pyramid-{1.3,1.4,1.5,1.6,1.7,1.8,1.9}
3232

33-
{pypy,py2.7,py3.5,py3.6}-rq-0.6
34-
{pypy,py2.7,py3.5,py3.6}-rq-0.7
35-
{pypy,py2.7,py3.5,py3.6}-rq-0.8
36-
{pypy,py2.7,py3.5,py3.6}-rq-0.9
37-
{pypy,py2.7,py3.5,py3.6}-rq-0.10
38-
{pypy,py2.7,py3.5,py3.6}-rq-0.11
33+
{pypy,py2.7,py3.5,py3.6}-rq-{0.6,0.7,0.8,0.9,0.10,0.11}
3934
{pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-rq-0.12
4035

36+
{py3.7,py3.8}-aiohttp
37+
4138
[testenv]
4239
deps =
4340
-r test-requirements.txt
@@ -89,6 +86,9 @@ deps =
8986
rq-0.11: rq>=0.11,<0.12
9087
rq-0.12: rq>=0.12,<0.13
9188

89+
aiohttp: aiohttp>=3.4.0,<3.5.0
90+
aiohttp: pytest-aiohttp
91+
9292
linters: black
9393
linters: flake8
9494
setenv =
@@ -102,6 +102,7 @@ setenv =
102102
sanic: TESTPATH=tests/integrations/sanic
103103
pyramid: TESTPATH=tests/integrations/pyramid
104104
rq: TESTPATH=tests/integrations/rq
105+
aiohttp: TESTPATH=tests/integrations/aiohttp
105106
passenv =
106107
AWS_ACCESS_KEY_ID
107108
AWS_SECRET_ACCESS_KEY

0 commit comments

Comments
 (0)