Skip to content

Commit 6292e79

Browse files
committed
feat(aiohttp): add instrumentation of client requests
1 parent 0ebb2f9 commit 6292e79

File tree

2 files changed

+142
-1
lines changed

2 files changed

+142
-1
lines changed

sentry_sdk/integrations/aiohttp.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from sentry_sdk.utils import (
1717
capture_internal_exceptions,
1818
event_from_exception,
19+
logger,
1920
parse_version,
2021
transaction_from_function,
2122
HAS_REAL_CONTEXTVARS,
@@ -27,6 +28,7 @@
2728
import asyncio
2829

2930
from aiohttp import __version__ as AIOHTTP_VERSION
31+
from aiohttp import ClientSession, TraceConfig
3032
from aiohttp.web import Application, HTTPException, UrlDispatcher
3133
except ImportError:
3234
raise DidNotEnable("AIOHTTP not installed")
@@ -36,6 +38,9 @@
3638
if TYPE_CHECKING:
3739
from aiohttp.web_request import Request
3840
from aiohttp.abc import AbstractMatchInfo
41+
from aiohttp import TraceRequestStartParams
42+
from aiohttp import TraceRequestEndParams
43+
from types import SimpleNamespace
3944
from typing import Any
4045
from typing import Dict
4146
from typing import Optional
@@ -50,6 +55,57 @@
5055
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
5156

5257

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

@@ -164,6 +220,22 @@ async def sentry_urldispatcher_resolve(self, request):
164220

165221
UrlDispatcher.resolve = sentry_urldispatcher_resolve
166222

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

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

tests/integrations/aiohttp/test_aiohttp.py

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

10-
from sentry_sdk import capture_message
10+
from sentry_sdk import capture_message, start_transaction
1111
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
1212

1313
try:
@@ -54,6 +54,7 @@ async def hello(request):
5454
"Accept-Encoding": "gzip, deflate",
5555
"Host": host,
5656
"User-Agent": request["headers"]["User-Agent"],
57+
"sentry-trace": mock.ANY,
5758
}
5859

5960

@@ -427,3 +428,71 @@ async def hello(request):
427428

428429
assert msg_event["contexts"]["trace"]["trace_id"] == trace_id
429430
assert error_event["contexts"]["trace"]["trace_id"] == trace_id
431+
432+
433+
async def test_crumb_capture(
434+
sentry_init, aiohttp_raw_server, aiohttp_client, loop, capture_events
435+
):
436+
def before_breadcrumb(crumb, hint):
437+
crumb["data"]["extra"] = "foo"
438+
return crumb
439+
440+
sentry_init(
441+
integrations=[AioHttpIntegration()], before_breadcrumb=before_breadcrumb
442+
)
443+
444+
async def handler(request):
445+
return web.Response(text="OK")
446+
447+
raw_server = await aiohttp_raw_server(handler)
448+
449+
with start_transaction():
450+
events = capture_events()
451+
452+
client = await aiohttp_client(raw_server)
453+
resp = await client.get("/")
454+
assert resp.status == 200
455+
capture_message("Testing!")
456+
457+
(event,) = events
458+
459+
crumb = event["breadcrumbs"]["values"][0]
460+
assert crumb["type"] == "http"
461+
assert crumb["category"] == "httplib"
462+
assert crumb["data"] == {
463+
"url": "http://127.0.0.1:{}/".format(raw_server.port),
464+
"method": "GET",
465+
"status_code": 200,
466+
"reason": "OK",
467+
"extra": "foo",
468+
}
469+
470+
471+
async def test_outgoing_trace_headers(sentry_init, aiohttp_raw_server, aiohttp_client):
472+
sentry_init(
473+
integrations=[AioHttpIntegration()],
474+
traces_sample_rate=1.0,
475+
)
476+
477+
async def handler(request):
478+
return web.Response(text="OK")
479+
480+
raw_server = await aiohttp_raw_server(handler)
481+
482+
with start_transaction(
483+
name="/interactions/other-dogs/new-dog",
484+
op="greeting.sniff",
485+
# make trace_id difference between transactions
486+
trace_id="0123456789012345678901234567890",
487+
) as transaction:
488+
client = await aiohttp_client(raw_server)
489+
resp = await client.get("/")
490+
request_span = transaction._span_recorder.spans[-1]
491+
492+
assert resp.request_info.headers[
493+
"sentry-trace"
494+
] == "{trace_id}-{parent_span_id}-{sampled}".format(
495+
trace_id=transaction.trace_id,
496+
parent_span_id=request_span.span_id,
497+
sampled=1,
498+
)

0 commit comments

Comments
 (0)