def setup_once(): # type: () -> None try: SanicIntegration.version = tuple(map(int, SANIC_VERSION.split("."))) except (TypeError, ValueError): raise DidNotEnable("Unparsable Sanic version: {}".format(SANIC_VERSION)) if SanicIntegration.version < (0, 8): raise DidNotEnable("Sanic 0.8 or newer required.") if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. raise DidNotEnable( "The sanic integration for Sentry requires Python 3.7+ " " or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE ) if SANIC_VERSION.startswith("0.8."): # Sanic 0.8 and older creates a logger named "root" and puts a # stringified version of every exception in there (without exc_info), # which our error deduplication can't detect. # # We explicitly check the version here because it is a very # invasive step to ignore this logger and not necessary in newer # versions at all. # # https://github.com/huge-success/sanic/issues/1332 ignore_logger("root") if SanicIntegration.version < (21, 9): _setup_legacy_sanic() return _setup_sanic()
from sanic.response import stream from sanic.response import text from sanic.server import HttpProtocol from ddtrace import config from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE from ddtrace.propagation import http as http_propagation from tests.utils import override_config from tests.utils import override_http_config # Helpers for handling response objects across sanic versions sanic_version = tuple(map(int, sanic_version.split("."))) def _response_status(response): return getattr(response, "status_code", getattr(response, "status", None)) async def _response_json(response): resp_json = response.json if callable(resp_json): resp_json = response.json() if asyncio.iscoroutine(resp_json): resp_json = await resp_json return resp_json
import sys import random import asyncio import pytest from sentry_sdk import capture_message, configure_scope from sentry_sdk.integrations.sanic import SanicIntegration from sanic import Sanic, request, response, __version__ as SANIC_VERSION_RAW from sanic.response import HTTPResponse from sanic.exceptions import abort SANIC_VERSION = tuple(map(int, SANIC_VERSION_RAW.split("."))) @pytest.fixture def app(): if SANIC_VERSION >= (20, 12): # Build (20.12.0) adds a feature where the instance is stored in an internal class # registry for later retrieval, and so add register=False to disable that app = Sanic(__name__, register=False) else: app = Sanic(__name__) @app.route("/message") def hi(request): capture_message("hi") return response.text("ok")
def setup_once(): # type: () -> None try: version = tuple(map(int, SANIC_VERSION.split("."))) except (TypeError, ValueError): raise DidNotEnable( "Unparsable Sanic version: {}".format(SANIC_VERSION)) if version < (0, 8): raise DidNotEnable("Sanic 0.8 or newer required.") if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. raise DidNotEnable( "The sanic integration for Sentry requires Python 3.7+ " " or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE) if SANIC_VERSION.startswith("0.8."): # Sanic 0.8 and older creates a logger named "root" and puts a # stringified version of every exception in there (without exc_info), # which our error deduplication can't detect. # # We explicitly check the version here because it is a very # invasive step to ignore this logger and not necessary in newer # versions at all. # # https://github.com/huge-success/sanic/issues/1332 ignore_logger("root") old_handle_request = Sanic.handle_request async def sentry_handle_request(self, request, *args, **kwargs): # type: (Any, Request, *Any, **Any) -> Any hub = Hub.current if hub.get_integration(SanicIntegration) is None: return old_handle_request(self, request, *args, **kwargs) weak_request = weakref.ref(request) with Hub(hub) as hub: with hub.configure_scope() as scope: scope.clear_breadcrumbs() scope.add_event_processor( _make_request_processor(weak_request)) response = old_handle_request(self, request, *args, **kwargs) if isawaitable(response): response = await response return response Sanic.handle_request = sentry_handle_request old_router_get = Router.get def sentry_router_get(self, request): # type: (Any, Request) -> Any rv = old_router_get(self, request) hub = Hub.current if hub.get_integration(SanicIntegration) is not None: with capture_internal_exceptions(): with hub.configure_scope() as scope: scope.transaction = rv[0].__name__ return rv Router.get = sentry_router_get old_error_handler_lookup = ErrorHandler.lookup def sentry_error_handler_lookup(self, exception): # type: (Any, Exception) -> Optional[object] _capture_exception(exception) old_error_handler = old_error_handler_lookup(self, exception) if old_error_handler is None: return None if Hub.current.get_integration(SanicIntegration) is None: return old_error_handler async def sentry_wrapped_error_handler(request, exception): # type: (Request, Exception) -> Any try: response = old_error_handler(request, exception) if isawaitable(response): response = await response return response except Exception: # Report errors that occur in Sanic error handler. These # exceptions will not even show up in Sanic's # `sanic.exceptions` logger. exc_info = sys.exc_info() _capture_exception(exc_info) reraise(*exc_info) return sentry_wrapped_error_handler ErrorHandler.lookup = sentry_error_handler_lookup