Skip to content

Commit 42d7b15

Browse files
committed
feat(asyncio): Add on-demand way to enable AsyncioIntegration
1 parent f988f84 commit 42d7b15

File tree

3 files changed

+172
-2
lines changed

3 files changed

+172
-2
lines changed

sentry_sdk/integrations/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from threading import Lock
33
from typing import TYPE_CHECKING
44

5+
import sentry_sdk
56
from sentry_sdk.utils import logger
67

78
if TYPE_CHECKING:
@@ -279,6 +280,24 @@ def setup_integrations(
279280
return integrations
280281

281282

283+
def _enable_integration(integration: "Integration") -> "Optional[Integration]":
284+
identifier = integration.identifier
285+
client = sentry_sdk.get_client()
286+
287+
with _installer_lock:
288+
logger.debug("Setting up integration %s", identifier)
289+
try:
290+
type(integration).setup_once()
291+
integration.setup_once_with_options(client.options)
292+
except DidNotEnable as e:
293+
logger.debug("Did not enable integration %s: %s", identifier, e)
294+
else:
295+
_installed_integrations.add(identifier)
296+
return integration
297+
298+
_processed_integrations.add(identifier)
299+
300+
282301
def _check_minimum_version(
283302
integration: "type[Integration]",
284303
version: "Optional[tuple[int, ...]]",

sentry_sdk/integrations/asyncio.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import sentry_sdk
55
from sentry_sdk.consts import OP
6-
from sentry_sdk.integrations import Integration, DidNotEnable
6+
from sentry_sdk.integrations import Integration, DidNotEnable, _enable_integration
77
from sentry_sdk.utils import event_from_exception, logger, reraise
88

99
try:
@@ -138,3 +138,40 @@ class AsyncioIntegration(Integration):
138138
@staticmethod
139139
def setup_once() -> None:
140140
patch_asyncio()
141+
142+
143+
def enable_asyncio_integration(*args: "Any", **kwargs: "Any") -> None:
144+
"""
145+
Enable AsyncioIntegration with the provided options.
146+
147+
The options need to correspond to the options currently accepted by the
148+
AsyncioIntegration() constructor.
149+
150+
This is useful in scenarios where Sentry needs to be initialized before
151+
an event loop is set up, but you still want to instrument asyncio once there
152+
is an event loop. In that case, you can sentry_sdk.init() early on without
153+
the AsyncioIntegration and then, once the event loop has been set up, execute
154+
155+
```python
156+
from sentry_sdk.integrations.asyncio import enable_asyncio_integration
157+
158+
async def async_entrypoint():
159+
enable_asyncio_integration()
160+
```
161+
162+
If AsyncioIntegration is already enabled (e.g. because it was provided in
163+
sentry_sdk.init(integrations=[...])), this function will re-enable it.
164+
165+
If AsyncioIntegration was provided in
166+
sentry_sdk.init(disabled_integrations=[...]), this function will ignore that
167+
and enable it.
168+
"""
169+
client = sentry_sdk.get_client()
170+
if not client.is_active():
171+
return
172+
173+
integration = _enable_integration(AsyncioIntegration(*args, **kwargs))
174+
if integration is None:
175+
return
176+
177+
client.integrations[integration.identifier] = integration

tests/integrations/asyncio/test_asyncio.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
import sentry_sdk
99
from sentry_sdk.consts import OP
10-
from sentry_sdk.integrations.asyncio import AsyncioIntegration, patch_asyncio
10+
from sentry_sdk.integrations.asyncio import (
11+
AsyncioIntegration,
12+
patch_asyncio,
13+
enable_asyncio_integration,
14+
)
1115

1216
try:
1317
from contextvars import Context, ContextVar
@@ -386,3 +390,113 @@ async def test_span_origin(
386390

387391
assert event["contexts"]["trace"]["origin"] == "manual"
388392
assert event["spans"][0]["origin"] == "auto.function.asyncio"
393+
394+
395+
@minimum_python_38
396+
@pytest.mark.asyncio
397+
async def test_delayed_enable_integration(sentry_init, capture_events):
398+
sentry_init(traces_sample_rate=1.0)
399+
400+
assert "asyncio" not in sentry_sdk.get_client().integrations
401+
402+
events = capture_events()
403+
404+
with sentry_sdk.start_transaction(name="test"):
405+
await asyncio.create_task(foo())
406+
407+
assert len(events) == 1
408+
(transaction,) = events
409+
assert not transaction["spans"]
410+
411+
enable_asyncio_integration()
412+
413+
events = capture_events()
414+
415+
assert "asyncio" in sentry_sdk.get_client().integrations
416+
417+
with sentry_sdk.start_transaction(name="test"):
418+
await asyncio.create_task(foo())
419+
420+
assert len(events) == 1
421+
(transaction,) = events
422+
assert transaction["spans"]
423+
assert transaction["spans"][0]["origin"] == "auto.function.asyncio"
424+
425+
426+
@minimum_python_38
427+
@pytest.mark.asyncio
428+
async def test_delayed_enable_integration_with_options(sentry_init, capture_events):
429+
sentry_init(traces_sample_rate=1.0)
430+
431+
assert "asyncio" not in sentry_sdk.get_client().integrations
432+
433+
events = capture_events()
434+
435+
with sentry_sdk.start_transaction(name="test"):
436+
await asyncio.create_task(foo())
437+
438+
assert len(events) == 1
439+
(transaction,) = events
440+
assert not transaction["spans"]
441+
442+
mock_init = MagicMock(return_value=None)
443+
mock_setup_once = MagicMock()
444+
with patch(
445+
"sentry_sdk.integrations.asyncio.AsyncioIntegration.__init__", mock_init
446+
):
447+
with patch(
448+
"sentry_sdk.integrations.asyncio.AsyncioIntegration.setup_once",
449+
mock_setup_once,
450+
):
451+
enable_asyncio_integration("arg", kwarg="kwarg")
452+
453+
assert "asyncio" in sentry_sdk.get_client().integrations
454+
mock_init.assert_called_once_with("arg", kwarg="kwarg")
455+
mock_setup_once.assert_called_once()
456+
457+
458+
@minimum_python_38
459+
@pytest.mark.asyncio
460+
async def test_delayed_enable_enabled_integration(sentry_init):
461+
sentry_init(integrations=[AsyncioIntegration()], traces_sample_rate=1.0)
462+
463+
assert "asyncio" in sentry_sdk.get_client().integrations
464+
465+
original_integration = sentry_sdk.get_client().integrations["asyncio"]
466+
enable_asyncio_integration()
467+
468+
assert "asyncio" in sentry_sdk.get_client().integrations
469+
470+
# The new asyncio integration should override the old one
471+
assert sentry_sdk.get_client().integrations["asyncio"] is not original_integration
472+
473+
474+
@minimum_python_38
475+
@pytest.mark.asyncio
476+
async def test_delayed_enable_integration_after_disabling(sentry_init, capture_events):
477+
sentry_init(disabled_integrations=[AsyncioIntegration()], traces_sample_rate=1.0)
478+
479+
assert "asyncio" not in sentry_sdk.get_client().integrations
480+
481+
events = capture_events()
482+
483+
with sentry_sdk.start_transaction(name="test"):
484+
await asyncio.create_task(foo())
485+
486+
assert len(events) == 1
487+
(transaction,) = events
488+
assert not transaction["spans"]
489+
490+
enable_asyncio_integration()
491+
492+
events = capture_events()
493+
494+
assert "asyncio" in sentry_sdk.get_client().integrations
495+
496+
with sentry_sdk.start_transaction(name="test"):
497+
await asyncio.create_task(foo())
498+
499+
assert len(events) == 1
500+
(transaction,) = events
501+
assert transaction["spans"]
502+
assert transaction["spans"][0]["origin"] == "auto.function.asyncio"

0 commit comments

Comments
 (0)