Changed code to support older Python versions
This commit is contained in:
parent
eb92d2d36f
commit
582458cdd0
5027 changed files with 794942 additions and 4 deletions
|
|
@ -0,0 +1,360 @@
|
|||
import sys
|
||||
import weakref
|
||||
from functools import wraps
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.api import continue_trace
|
||||
from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA
|
||||
from sentry_sdk.integrations import (
|
||||
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
|
||||
_check_minimum_version,
|
||||
Integration,
|
||||
DidNotEnable,
|
||||
)
|
||||
from sentry_sdk.integrations.logging import ignore_logger
|
||||
from sentry_sdk.sessions import track_session
|
||||
from sentry_sdk.integrations._wsgi_common import (
|
||||
_filter_headers,
|
||||
request_body_within_bounds,
|
||||
)
|
||||
from sentry_sdk.tracing import (
|
||||
BAGGAGE_HEADER_NAME,
|
||||
SOURCE_FOR_STYLE,
|
||||
TransactionSource,
|
||||
)
|
||||
from sentry_sdk.tracing_utils import should_propagate_trace, add_http_request_source
|
||||
from sentry_sdk.utils import (
|
||||
capture_internal_exceptions,
|
||||
ensure_integration_enabled,
|
||||
event_from_exception,
|
||||
logger,
|
||||
parse_url,
|
||||
parse_version,
|
||||
reraise,
|
||||
transaction_from_function,
|
||||
HAS_REAL_CONTEXTVARS,
|
||||
CONTEXTVARS_ERROR_MESSAGE,
|
||||
SENSITIVE_DATA_SUBSTITUTE,
|
||||
AnnotatedValue,
|
||||
)
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
from aiohttp import __version__ as AIOHTTP_VERSION
|
||||
from aiohttp import ClientSession, TraceConfig
|
||||
from aiohttp.web import Application, HTTPException, UrlDispatcher
|
||||
except ImportError:
|
||||
raise DidNotEnable("AIOHTTP not installed")
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp.web_request import Request
|
||||
from aiohttp.web_urldispatcher import UrlMappingMatchInfo
|
||||
from aiohttp import TraceRequestStartParams, TraceRequestEndParams
|
||||
|
||||
from collections.abc import Set
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from sentry_sdk.utils import ExcInfo
|
||||
from sentry_sdk._types import Event, EventProcessor
|
||||
|
||||
|
||||
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
|
||||
|
||||
|
||||
class AioHttpIntegration(Integration):
|
||||
identifier = "aiohttp"
|
||||
origin = f"auto.http.{identifier}"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transaction_style="handler_name", # type: str
|
||||
*,
|
||||
failed_request_status_codes=_DEFAULT_FAILED_REQUEST_STATUS_CODES, # type: Set[int]
|
||||
):
|
||||
# type: (...) -> None
|
||||
if transaction_style not in TRANSACTION_STYLE_VALUES:
|
||||
raise ValueError(
|
||||
"Invalid value for transaction_style: %s (must be in %s)"
|
||||
% (transaction_style, TRANSACTION_STYLE_VALUES)
|
||||
)
|
||||
self.transaction_style = transaction_style
|
||||
self._failed_request_status_codes = failed_request_status_codes
|
||||
|
||||
@staticmethod
|
||||
def setup_once():
|
||||
# type: () -> None
|
||||
|
||||
version = parse_version(AIOHTTP_VERSION)
|
||||
_check_minimum_version(AioHttpIntegration, version)
|
||||
|
||||
if not HAS_REAL_CONTEXTVARS:
|
||||
# We better have contextvars or we're going to leak state between
|
||||
# requests.
|
||||
raise DidNotEnable(
|
||||
"The aiohttp integration for Sentry requires Python 3.7+ "
|
||||
" or aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE
|
||||
)
|
||||
|
||||
ignore_logger("aiohttp.server")
|
||||
|
||||
old_handle = Application._handle
|
||||
|
||||
async def sentry_app_handle(self, request, *args, **kwargs):
|
||||
# type: (Any, Request, *Any, **Any) -> Any
|
||||
integration = sentry_sdk.get_client().get_integration(AioHttpIntegration)
|
||||
if integration is None:
|
||||
return await old_handle(self, request, *args, **kwargs)
|
||||
|
||||
weak_request = weakref.ref(request)
|
||||
|
||||
with sentry_sdk.isolation_scope() as scope:
|
||||
with track_session(scope, session_mode="request"):
|
||||
# Scope data will not leak between requests because aiohttp
|
||||
# create a task to wrap each request.
|
||||
scope.generate_propagation_context()
|
||||
scope.clear_breadcrumbs()
|
||||
scope.add_event_processor(_make_request_processor(weak_request))
|
||||
|
||||
headers = dict(request.headers)
|
||||
transaction = continue_trace(
|
||||
headers,
|
||||
op=OP.HTTP_SERVER,
|
||||
# If this transaction name makes it to the UI, AIOHTTP's
|
||||
# URL resolver did not find a route or died trying.
|
||||
name="generic AIOHTTP request",
|
||||
source=TransactionSource.ROUTE,
|
||||
origin=AioHttpIntegration.origin,
|
||||
)
|
||||
with sentry_sdk.start_transaction(
|
||||
transaction,
|
||||
custom_sampling_context={"aiohttp_request": request},
|
||||
):
|
||||
try:
|
||||
response = await old_handle(self, request)
|
||||
except HTTPException as e:
|
||||
transaction.set_http_status(e.status_code)
|
||||
|
||||
if (
|
||||
e.status_code
|
||||
in integration._failed_request_status_codes
|
||||
):
|
||||
_capture_exception()
|
||||
|
||||
raise
|
||||
except (asyncio.CancelledError, ConnectionResetError):
|
||||
transaction.set_status(SPANSTATUS.CANCELLED)
|
||||
raise
|
||||
except Exception:
|
||||
# This will probably map to a 500 but seems like we
|
||||
# have no way to tell. Do not set span status.
|
||||
reraise(*_capture_exception())
|
||||
|
||||
try:
|
||||
# A valid response handler will return a valid response with a status. But, if the handler
|
||||
# returns an invalid response (e.g. None), the line below will raise an AttributeError.
|
||||
# Even though this is likely invalid, we need to handle this case to ensure we don't break
|
||||
# the application.
|
||||
response_status = response.status
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
transaction.set_http_status(response_status)
|
||||
|
||||
return response
|
||||
|
||||
Application._handle = sentry_app_handle
|
||||
|
||||
old_urldispatcher_resolve = UrlDispatcher.resolve
|
||||
|
||||
@wraps(old_urldispatcher_resolve)
|
||||
async def sentry_urldispatcher_resolve(self, request):
|
||||
# type: (UrlDispatcher, Request) -> UrlMappingMatchInfo
|
||||
rv = await old_urldispatcher_resolve(self, request)
|
||||
|
||||
integration = sentry_sdk.get_client().get_integration(AioHttpIntegration)
|
||||
if integration is None:
|
||||
return rv
|
||||
|
||||
name = None
|
||||
|
||||
try:
|
||||
if integration.transaction_style == "handler_name":
|
||||
name = transaction_from_function(rv.handler)
|
||||
elif integration.transaction_style == "method_and_path_pattern":
|
||||
route_info = rv.get_info()
|
||||
pattern = route_info.get("path") or route_info.get("formatter")
|
||||
name = "{} {}".format(request.method, pattern)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if name is not None:
|
||||
sentry_sdk.get_current_scope().set_transaction_name(
|
||||
name,
|
||||
source=SOURCE_FOR_STYLE[integration.transaction_style],
|
||||
)
|
||||
|
||||
return rv
|
||||
|
||||
UrlDispatcher.resolve = sentry_urldispatcher_resolve
|
||||
|
||||
old_client_session_init = ClientSession.__init__
|
||||
|
||||
@ensure_integration_enabled(AioHttpIntegration, old_client_session_init)
|
||||
def init(*args, **kwargs):
|
||||
# type: (Any, Any) -> None
|
||||
client_trace_configs = list(kwargs.get("trace_configs") or ())
|
||||
trace_config = create_trace_config()
|
||||
client_trace_configs.append(trace_config)
|
||||
|
||||
kwargs["trace_configs"] = client_trace_configs
|
||||
return old_client_session_init(*args, **kwargs)
|
||||
|
||||
ClientSession.__init__ = init
|
||||
|
||||
|
||||
def create_trace_config():
|
||||
# type: () -> TraceConfig
|
||||
|
||||
async def on_request_start(session, trace_config_ctx, params):
|
||||
# type: (ClientSession, SimpleNamespace, TraceRequestStartParams) -> None
|
||||
if sentry_sdk.get_client().get_integration(AioHttpIntegration) is None:
|
||||
return
|
||||
|
||||
method = params.method.upper()
|
||||
|
||||
parsed_url = None
|
||||
with capture_internal_exceptions():
|
||||
parsed_url = parse_url(str(params.url), sanitize=False)
|
||||
|
||||
span = sentry_sdk.start_span(
|
||||
op=OP.HTTP_CLIENT,
|
||||
name="%s %s"
|
||||
% (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE),
|
||||
origin=AioHttpIntegration.origin,
|
||||
)
|
||||
span.set_data(SPANDATA.HTTP_METHOD, method)
|
||||
if parsed_url is not None:
|
||||
span.set_data("url", parsed_url.url)
|
||||
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
|
||||
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
|
||||
|
||||
client = sentry_sdk.get_client()
|
||||
|
||||
if should_propagate_trace(client, str(params.url)):
|
||||
for (
|
||||
key,
|
||||
value,
|
||||
) in sentry_sdk.get_current_scope().iter_trace_propagation_headers(
|
||||
span=span
|
||||
):
|
||||
logger.debug(
|
||||
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
|
||||
key=key, value=value, url=params.url
|
||||
)
|
||||
)
|
||||
if key == BAGGAGE_HEADER_NAME and params.headers.get(
|
||||
BAGGAGE_HEADER_NAME
|
||||
):
|
||||
# do not overwrite any existing baggage, just append to it
|
||||
params.headers[key] += "," + value
|
||||
else:
|
||||
params.headers[key] = value
|
||||
|
||||
trace_config_ctx.span = span
|
||||
|
||||
async def on_request_end(session, trace_config_ctx, params):
|
||||
# type: (ClientSession, SimpleNamespace, TraceRequestEndParams) -> None
|
||||
if trace_config_ctx.span is None:
|
||||
return
|
||||
|
||||
span = trace_config_ctx.span
|
||||
span.set_http_status(int(params.response.status))
|
||||
span.set_data("reason", params.response.reason)
|
||||
span.finish()
|
||||
|
||||
with capture_internal_exceptions():
|
||||
add_http_request_source(span)
|
||||
|
||||
trace_config = TraceConfig()
|
||||
|
||||
trace_config.on_request_start.append(on_request_start)
|
||||
trace_config.on_request_end.append(on_request_end)
|
||||
|
||||
return trace_config
|
||||
|
||||
|
||||
def _make_request_processor(weak_request):
|
||||
# type: (weakref.ReferenceType[Request]) -> EventProcessor
|
||||
def aiohttp_processor(
|
||||
event, # type: Event
|
||||
hint, # type: dict[str, Tuple[type, BaseException, Any]]
|
||||
):
|
||||
# type: (...) -> Event
|
||||
request = weak_request()
|
||||
if request is None:
|
||||
return event
|
||||
|
||||
with capture_internal_exceptions():
|
||||
request_info = event.setdefault("request", {})
|
||||
|
||||
request_info["url"] = "%s://%s%s" % (
|
||||
request.scheme,
|
||||
request.host,
|
||||
request.path,
|
||||
)
|
||||
|
||||
request_info["query_string"] = request.query_string
|
||||
request_info["method"] = request.method
|
||||
request_info["env"] = {"REMOTE_ADDR": request.remote}
|
||||
request_info["headers"] = _filter_headers(dict(request.headers))
|
||||
|
||||
# Just attach raw data here if it is within bounds, if available.
|
||||
# Unfortunately there's no way to get structured data from aiohttp
|
||||
# without awaiting on some coroutine.
|
||||
request_info["data"] = get_aiohttp_request_data(request)
|
||||
|
||||
return event
|
||||
|
||||
return aiohttp_processor
|
||||
|
||||
|
||||
def _capture_exception():
|
||||
# type: () -> ExcInfo
|
||||
exc_info = sys.exc_info()
|
||||
event, hint = event_from_exception(
|
||||
exc_info,
|
||||
client_options=sentry_sdk.get_client().options,
|
||||
mechanism={"type": "aiohttp", "handled": False},
|
||||
)
|
||||
sentry_sdk.capture_event(event, hint=hint)
|
||||
return exc_info
|
||||
|
||||
|
||||
BODY_NOT_READ_MESSAGE = "[Can't show request body due to implementation details.]"
|
||||
|
||||
|
||||
def get_aiohttp_request_data(request):
|
||||
# type: (Request) -> Union[Optional[str], AnnotatedValue]
|
||||
bytes_body = request._read_bytes
|
||||
|
||||
if bytes_body is not None:
|
||||
# we have body to show
|
||||
if not request_body_within_bounds(sentry_sdk.get_client(), len(bytes_body)):
|
||||
return AnnotatedValue.removed_because_over_size_limit()
|
||||
|
||||
encoding = request.charset or "utf-8"
|
||||
return bytes_body.decode(encoding, "replace")
|
||||
|
||||
if request.can_read_body:
|
||||
# body exists but we can't show it
|
||||
return BODY_NOT_READ_MESSAGE
|
||||
|
||||
# request has no body
|
||||
return None
|
||||
Loading…
Add table
Add a link
Reference in a new issue