Skip to content

Commit 27d1187

Browse files
committed
feat(aiohttp): add instrumentation of client requests
1 parent 2d24560 commit 27d1187

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

sentry_sdk/integrations/aiohttp.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sentry_sdk.utils import (
1616
capture_internal_exceptions,
1717
event_from_exception,
18+
logger,
1819
transaction_from_function,
1920
HAS_REAL_CONTEXTVARS,
2021
CONTEXTVARS_ERROR_MESSAGE,
@@ -25,6 +26,7 @@
2526
import asyncio
2627

2728
from aiohttp import __version__ as AIOHTTP_VERSION
29+
from aiohttp import ClientSession, TraceConfig
2830
from aiohttp.web import Application, HTTPException, UrlDispatcher
2931
except ImportError:
3032
raise DidNotEnable("AIOHTTP not installed")
@@ -34,6 +36,9 @@
3436
if MYPY:
3537
from aiohttp.web_request import Request
3638
from aiohttp.abc import AbstractMatchInfo
39+
from aiohttp import TraceRequestStartParams
40+
from aiohttp import TraceRequestEndParams
41+
from types import SimpleNamespace
3742
from typing import Any
3843
from typing import Dict
3944
from typing import Optional
@@ -48,6 +53,57 @@
4853
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
4954

5055

56+
def create_trace_config() -> TraceConfig:
57+
async def on_request_start(
58+
session: ClientSession,
59+
trace_config_ctx: "SimpleNamespace",
60+
params: "TraceRequestStartParams",
61+
):
62+
hub = Hub.current
63+
if hub.get_integration(AioHttpIntegration) is None:
64+
return
65+
66+
method = params.method.upper()
67+
request_url = str(params.url)
68+
69+
span = hub.start_span(
70+
op=OP.HTTP_CLIENT, description="%s %s" % (method, request_url)
71+
)
72+
span.set_data("method", method)
73+
span.set_data("url", request_url)
74+
75+
for key, value in hub.iter_trace_propagation_headers(span):
76+
logger.debug(
77+
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
78+
key=key, value=value, url=params.url
79+
)
80+
)
81+
params.headers[key] = value
82+
83+
trace_config_ctx.span = span
84+
85+
async def on_request_end(
86+
session: ClientSession,
87+
trace_config_ctx: "SimpleNamespace",
88+
params: "TraceRequestEndParams",
89+
):
90+
if trace_config_ctx.span is None:
91+
return
92+
93+
span = trace_config_ctx.span
94+
span.set_data("status_code", int(params.response.status))
95+
span.set_http_status(int(params.response.status))
96+
span.set_data("reason", params.response.reason)
97+
span.finish()
98+
99+
trace_config = TraceConfig()
100+
101+
trace_config.on_request_start.append(on_request_start)
102+
trace_config.on_request_end.append(on_request_end)
103+
104+
return trace_config
105+
106+
51107
class AioHttpIntegration(Integration):
52108
identifier = "aiohttp"
53109

@@ -162,6 +218,22 @@ async def sentry_urldispatcher_resolve(self, request):
162218

163219
UrlDispatcher.resolve = sentry_urldispatcher_resolve
164220

221+
old_client_session_init = ClientSession.__init__
222+
223+
def init(*args, **kwargs):
224+
hub = Hub.current
225+
if hub.get_integration(AioHttpIntegration) is None:
226+
return old_client_session_init(*args, **kwargs)
227+
228+
client_trace_configs = list(kwargs.get("trace_configs", ()))
229+
trace_config = create_trace_config()
230+
client_trace_configs.append(trace_config)
231+
232+
kwargs["trace_configs"] = client_trace_configs
233+
return old_client_session_init(*args, **kwargs)
234+
235+
ClientSession.__init__ = init
236+
165237

166238
def _make_request_processor(weak_request):
167239
# type: (Callable[[], Request]) -> EventProcessor

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from aiohttp.client import ServerDisconnectedError
88
from aiohttp.web_request import Request
99

10+
from sentry_sdk import capture_message, start_transaction
1011
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
1112

1213
try:
@@ -52,6 +53,7 @@ async def hello(request):
5253
"Accept-Encoding": "gzip, deflate",
5354
"Host": host,
5455
"User-Agent": request["headers"]["User-Agent"],
56+
"sentry-trace": mock.ANY,
5557
}
5658

5759

@@ -275,3 +277,71 @@ async def kangaroo_handler(request):
275277
}
276278
)
277279
)
280+
281+
282+
async def test_crumb_capture(
283+
sentry_init, aiohttp_raw_server, aiohttp_client, loop, capture_events
284+
):
285+
def before_breadcrumb(crumb, hint):
286+
crumb["data"]["extra"] = "foo"
287+
return crumb
288+
289+
sentry_init(
290+
integrations=[AioHttpIntegration()], before_breadcrumb=before_breadcrumb
291+
)
292+
293+
async def handler(request):
294+
return web.Response(text="OK")
295+
296+
raw_server = await aiohttp_raw_server(handler)
297+
298+
with start_transaction():
299+
events = capture_events()
300+
301+
client = await aiohttp_client(raw_server)
302+
resp = await client.get("/")
303+
assert resp.status == 200
304+
capture_message("Testing!")
305+
306+
(event,) = events
307+
308+
crumb = event["breadcrumbs"]["values"][0]
309+
assert crumb["type"] == "http"
310+
assert crumb["category"] == "httplib"
311+
assert crumb["data"] == {
312+
"url": "http://127.0.0.1:{}/".format(raw_server.port),
313+
"method": "GET",
314+
"status_code": 200,
315+
"reason": "OK",
316+
"extra": "foo",
317+
}
318+
319+
320+
async def test_outgoing_trace_headers(sentry_init, aiohttp_raw_server, aiohttp_client):
321+
sentry_init(
322+
integrations=[AioHttpIntegration()],
323+
traces_sample_rate=1.0,
324+
)
325+
326+
async def handler(request):
327+
return web.Response(text="OK")
328+
329+
raw_server = await aiohttp_raw_server(handler)
330+
331+
with start_transaction(
332+
name="/interactions/other-dogs/new-dog",
333+
op="greeting.sniff",
334+
# make trace_id difference between transactions
335+
trace_id="0123456789012345678901234567890",
336+
) as transaction:
337+
client = await aiohttp_client(raw_server)
338+
resp = await client.get("/")
339+
request_span = transaction._span_recorder.spans[-1]
340+
341+
assert resp.request_info.headers[
342+
"sentry-trace"
343+
] == "{trace_id}-{parent_span_id}-{sampled}".format(
344+
trace_id=transaction.trace_id,
345+
parent_span_id=request_span.span_id,
346+
sampled=1,
347+
)

0 commit comments

Comments
 (0)