File size: 5,392 Bytes
9c6594c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
import sys
import warnings
from functools import wraps
from threading import Thread, current_thread
import sentry_sdk
from sentry_sdk.integrations import Integration
from sentry_sdk.scope import use_isolation_scope, use_scope
from sentry_sdk.utils import (
event_from_exception,
capture_internal_exceptions,
logger,
reraise,
)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any
from typing import TypeVar
from typing import Callable
from typing import Optional
from sentry_sdk._types import ExcInfo
F = TypeVar("F", bound=Callable[..., Any])
class ThreadingIntegration(Integration):
identifier = "threading"
def __init__(self, propagate_hub=None, propagate_scope=True):
# type: (Optional[bool], bool) -> None
if propagate_hub is not None:
logger.warning(
"Deprecated: propagate_hub is deprecated. This will be removed in the future."
)
# Note: propagate_hub did not have any effect on propagation of scope data
# scope data was always propagated no matter what the value of propagate_hub was
# This is why the default for propagate_scope is True
self.propagate_scope = propagate_scope
if propagate_hub is not None:
self.propagate_scope = propagate_hub
@staticmethod
def setup_once():
# type: () -> None
old_start = Thread.start
try:
from django import VERSION as django_version # noqa: N811
import channels # type: ignore[import-not-found]
channels_version = channels.__version__
except ImportError:
django_version = None
channels_version = None
@wraps(old_start)
def sentry_start(self, *a, **kw):
# type: (Thread, *Any, **Any) -> Any
integration = sentry_sdk.get_client().get_integration(ThreadingIntegration)
if integration is None:
return old_start(self, *a, **kw)
if integration.propagate_scope:
if (
sys.version_info < (3, 9)
and channels_version is not None
and channels_version < "4.0.0"
and django_version is not None
and django_version >= (3, 0)
and django_version < (4, 0)
):
warnings.warn(
"There is a known issue with Django channels 2.x and 3.x when using Python 3.8 or older. "
"(Async support is emulated using threads and some Sentry data may be leaked between those threads.) "
"Please either upgrade to Django channels 4.0+, use Django's async features "
"available in Django 3.1+ instead of Django channels, or upgrade to Python 3.9+.",
stacklevel=2,
)
isolation_scope = sentry_sdk.get_isolation_scope()
current_scope = sentry_sdk.get_current_scope()
else:
isolation_scope = sentry_sdk.get_isolation_scope().fork()
current_scope = sentry_sdk.get_current_scope().fork()
else:
isolation_scope = None
current_scope = None
# Patching instance methods in `start()` creates a reference cycle if
# done in a naive way. See
# https://github.com/getsentry/sentry-python/pull/434
#
# In threading module, using current_thread API will access current thread instance
# without holding it to avoid a reference cycle in an easier way.
with capture_internal_exceptions():
new_run = _wrap_run(
isolation_scope,
current_scope,
getattr(self.run, "__func__", self.run),
)
self.run = new_run # type: ignore
return old_start(self, *a, **kw)
Thread.start = sentry_start # type: ignore
def _wrap_run(isolation_scope_to_use, current_scope_to_use, old_run_func):
# type: (Optional[sentry_sdk.Scope], Optional[sentry_sdk.Scope], F) -> F
@wraps(old_run_func)
def run(*a, **kw):
# type: (*Any, **Any) -> Any
def _run_old_run_func():
# type: () -> Any
try:
self = current_thread()
return old_run_func(self, *a, **kw)
except Exception:
reraise(*_capture_exception())
if isolation_scope_to_use is not None and current_scope_to_use is not None:
with use_isolation_scope(isolation_scope_to_use):
with use_scope(current_scope_to_use):
return _run_old_run_func()
else:
return _run_old_run_func()
return run # type: ignore
def _capture_exception():
# type: () -> ExcInfo
exc_info = sys.exc_info()
client = sentry_sdk.get_client()
if client.get_integration(ThreadingIntegration) is not None:
event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "threading", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)
return exc_info
|