File size: 6,009 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""
Create spans from Django middleware invocations
"""

from functools import wraps

from django import VERSION as DJANGO_VERSION

import sentry_sdk
from sentry_sdk.consts import OP
from sentry_sdk.utils import (
    ContextVar,
    transaction_from_function,
    capture_internal_exceptions,
)

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Any
    from typing import Callable
    from typing import Optional
    from typing import TypeVar

    from sentry_sdk.tracing import Span

    F = TypeVar("F", bound=Callable[..., Any])

_import_string_should_wrap_middleware = ContextVar(
    "import_string_should_wrap_middleware"
)

DJANGO_SUPPORTS_ASYNC_MIDDLEWARE = DJANGO_VERSION >= (3, 1)

if not DJANGO_SUPPORTS_ASYNC_MIDDLEWARE:
    _asgi_middleware_mixin_factory = lambda _: object
else:
    from .asgi import _asgi_middleware_mixin_factory


def patch_django_middlewares():
    # type: () -> None
    from django.core.handlers import base

    old_import_string = base.import_string

    def sentry_patched_import_string(dotted_path):
        # type: (str) -> Any
        rv = old_import_string(dotted_path)

        if _import_string_should_wrap_middleware.get(None):
            rv = _wrap_middleware(rv, dotted_path)

        return rv

    base.import_string = sentry_patched_import_string

    old_load_middleware = base.BaseHandler.load_middleware

    def sentry_patched_load_middleware(*args, **kwargs):
        # type: (Any, Any) -> Any
        _import_string_should_wrap_middleware.set(True)
        try:
            return old_load_middleware(*args, **kwargs)
        finally:
            _import_string_should_wrap_middleware.set(False)

    base.BaseHandler.load_middleware = sentry_patched_load_middleware


def _wrap_middleware(middleware, middleware_name):
    # type: (Any, str) -> Any
    from sentry_sdk.integrations.django import DjangoIntegration

    def _check_middleware_span(old_method):
        # type: (Callable[..., Any]) -> Optional[Span]
        integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
        if integration is None or not integration.middleware_spans:
            return None

        function_name = transaction_from_function(old_method)

        description = middleware_name
        function_basename = getattr(old_method, "__name__", None)
        if function_basename:
            description = "{}.{}".format(description, function_basename)

        middleware_span = sentry_sdk.start_span(
            op=OP.MIDDLEWARE_DJANGO,
            name=description,
            origin=DjangoIntegration.origin,
        )
        middleware_span.set_tag("django.function_name", function_name)
        middleware_span.set_tag("django.middleware_name", middleware_name)

        return middleware_span

    def _get_wrapped_method(old_method):
        # type: (F) -> F
        with capture_internal_exceptions():

            def sentry_wrapped_method(*args, **kwargs):
                # type: (*Any, **Any) -> Any
                middleware_span = _check_middleware_span(old_method)

                if middleware_span is None:
                    return old_method(*args, **kwargs)

                with middleware_span:
                    return old_method(*args, **kwargs)

            try:
                # fails for __call__ of function on Python 2 (see py2.7-django-1.11)
                sentry_wrapped_method = wraps(old_method)(sentry_wrapped_method)

                # Necessary for Django 3.1
                sentry_wrapped_method.__self__ = old_method.__self__  # type: ignore
            except Exception:
                pass

            return sentry_wrapped_method  # type: ignore

        return old_method

    class SentryWrappingMiddleware(
        _asgi_middleware_mixin_factory(_check_middleware_span)  # type: ignore
    ):
        sync_capable = getattr(middleware, "sync_capable", True)
        async_capable = DJANGO_SUPPORTS_ASYNC_MIDDLEWARE and getattr(
            middleware, "async_capable", False
        )

        def __init__(self, get_response=None, *args, **kwargs):
            # type: (Optional[Callable[..., Any]], *Any, **Any) -> None
            if get_response:
                self._inner = middleware(get_response, *args, **kwargs)
            else:
                self._inner = middleware(*args, **kwargs)
            self.get_response = get_response
            self._call_method = None
            if self.async_capable:
                super().__init__(get_response)

        # We need correct behavior for `hasattr()`, which we can only determine
        # when we have an instance of the middleware we're wrapping.
        def __getattr__(self, method_name):
            # type: (str) -> Any
            if method_name not in (
                "process_request",
                "process_view",
                "process_template_response",
                "process_response",
                "process_exception",
            ):
                raise AttributeError()

            old_method = getattr(self._inner, method_name)
            rv = _get_wrapped_method(old_method)
            self.__dict__[method_name] = rv
            return rv

        def __call__(self, *args, **kwargs):
            # type: (*Any, **Any) -> Any
            if hasattr(self, "async_route_check") and self.async_route_check():
                return self.__acall__(*args, **kwargs)

            f = self._call_method
            if f is None:
                self._call_method = f = self._inner.__call__

            middleware_span = _check_middleware_span(old_method=f)

            if middleware_span is None:
                return f(*args, **kwargs)

            with middleware_span:
                return f(*args, **kwargs)

    for attr in (
        "__name__",
        "__module__",
        "__qualname__",
    ):
        if hasattr(middleware, attr):
            setattr(SentryWrappingMiddleware, attr, getattr(middleware, attr))

    return SentryWrappingMiddleware