def _bulk_snuba_query(snuba_param_list, headers): with sentry_sdk.start_span( op="start_snuba_query", description=f"running {len(snuba_param_list)} snuba queries", ) as span: span.set_tag("referrer", headers.get("referer", "<unknown>")) if len(snuba_param_list) > 1: query_results = list( _query_thread_pool.map( _snuba_query, [ params + (Hub(Hub.current), headers) for params in snuba_param_list ], )) else: # No need to submit to the thread pool if we're just performing a # single query query_results = [ _snuba_query(snuba_param_list[0] + (Hub(Hub.current), headers)) ] results = [] for response, _, reverse in query_results: try: body = json.loads(response.data) if SNUBA_INFO: if "sql" in body: logger.info("{}.sql: {}".format( headers.get("referer", "<unknown>"), body["sql"])) if "error" in body: logger.info("{}.err: {}".format( headers.get("referer", "<unknown>"), body["error"])) except ValueError: if response.status != 200: logger.error("snuba.query.invalid-json") raise SnubaError("Failed to parse snuba error response") raise UnexpectedResponseError( f"Could not decode JSON response: {response.data}") if response.status != 200: if body.get("error"): error = body["error"] if response.status == 429: raise RateLimitExceeded(error["message"]) elif error["type"] == "schema": raise SchemaValidationError(error["message"]) elif error["type"] == "clickhouse": raise clickhouse_error_codes_map.get( error["code"], QueryExecutionError)(error["message"]) else: raise SnubaError(error["message"]) else: raise SnubaError(f"HTTP {response.status}") # Forward and reverse translation maps from model ids to snuba keys, per column body["data"] = [reverse(d) for d in body["data"]] results.append(body) return results
def _resolve_hub_marker_value(marker_value): if marker_value is None: marker_value = DEFAULT_HUB else: marker_value = marker_value.args[0] if callable(marker_value): marker_value = marker_value() if marker_value is None: # user explicitly disabled reporting return Hub() if isinstance(marker_value, str): return Hub(Client(marker_value)) if isinstance(marker_value, dict): return Hub(Client(**marker_value)) if isinstance(marker_value, Client): return Hub(marker_value) if isinstance(marker_value, Hub): return marker_value raise RuntimeError( "The `sentry_client` value must be a client, hub or string, not {}". format(repr(type(marker_value))))
def test_basic(self, request): requests = [] def queue_event(method, url, body, headers): requests.append((method, url, body, headers)) request.side_effect = queue_event hub = Hub(Client( 'http://%s:%s@localhost:8000/%s' % (self.pk.public_key, self.pk.secret_key, self.pk.project_id), default_integrations=False )) hub.capture_message('foo') hub.client.close() for _request in requests: self.send_event(*_request) assert request.call_count is 1 assert Group.objects.count() == 1 group = Group.objects.get() assert group.event_set.count() == 1 instance = group.event_set.get() assert instance.data['logentry']['formatted'] == 'foo'
def test_attach_stacktrace_disabled(): events = [] hub = Hub(Client(attach_stacktrace=False, transport=events.append)) hub.capture_message("HI") event, = events assert "threads" not in event
def test_basic(self, request): requests = [] def queue_event(method, url, body, headers): requests.append((method, url, body, headers)) request.side_effect = queue_event hub = Hub( Client( "http://%s:%s@localhost:8000/%s" % (self.pk.public_key, self.pk.secret_key, self.pk.project_id), default_integrations=False, )) hub.capture_message("foo") hub.client.close() for _request in requests: self.send_event(*_request) assert request.call_count == 1 assert Group.objects.count() == 1 group = Group.objects.get() assert group.data["title"] == "foo"
def test_with_locals_enabled(): events = [] hub = Hub(Client(with_locals=True, transport=events.append)) try: 1 / 0 except Exception: hub.capture_exception() (event, ) = events assert all( frame["vars"] for frame in event["exception"]["values"][0]["stacktrace"]["frames"])
def test_with_locals_disabled(): events = [] hub = Hub(Client(with_locals=False, transport=events.append)) try: 1 / 0 except Exception: hub.capture_exception() event, = events assert all( "vars" not in frame for frame in event["exception"]["values"][0]["stacktrace"]["frames"])
def test_traceparent_header_wsgi(self): # Assert that posting something to store will not create another # (transaction) event under any circumstances. # # We use Werkzeug's test client because Django's test client bypasses a # lot of request handling code that we want to test implicitly (such as # all our WSGI middlewares and the entire Django instrumentation by # sentry-sdk). # # XXX(markus): Ideally methods such as `_postWithHeader` would always # call the WSGI application => swap out Django's test client with e.g. # Werkzeug's. client = WerkzeugClient(application) calls = [] def new_disable_transaction_events(): with configure_scope() as scope: assert scope.span.sampled assert scope.span.transaction disable_transaction_events() assert not scope.span.sampled calls.append(1) events = [] auth = get_auth_header("_postWithWerkzeug/0.0.0", self.projectkey.public_key, self.projectkey.secret_key, "7") with mock.patch("sentry.web.api.disable_transaction_events", new_disable_transaction_events): with self.tasks(): with Hub( Client( transport=events.append, integrations=[ CeleryIntegration(), DjangoIntegration() ], )): app_iter, status, headers = client.post( reverse("sentry-api-store"), data=b'{"message": "hello"}', headers={ "x-sentry-auth": auth, "sentry-trace": "1", "content-type": "application/octet-stream", }, environ_base={"REMOTE_ADDR": "127.0.0.1"}, ) body = "".join(app_iter) assert status == "200 OK", body assert set( (e.get("type"), e.get("transaction")) for e in events) == {("transaction", "rule_processor_apply")} assert calls == [1]
def inner(*args, **kwargs): with Hub(Hub.current): try: return f(*args, **kwargs) except Exception: _capture_and_reraise() finally: if flush: _flush_client()
def test_exception_captured_by_sentry(self): events = [] with Hub(Client(transport=events.append)): # This endpoint should return 500 as it internally raises an exception response = self.app.get('/tests/error') assert response.status_code == 500 assert len(events) == 1 assert events[0]['exception']['values'][0]['type'] == 'ZeroDivisionError'
def test_ignore_errors(): class MyDivisionError(ZeroDivisionError): pass def raise_it(exc_info): reraise(*exc_info) hub = Hub(Client(ignore_errors=[ZeroDivisionError], transport=_TestTransport())) hub._capture_internal_exception = raise_it def e(exc): try: raise exc except Exception: hub.capture_exception() e(ZeroDivisionError()) e(MyDivisionError()) pytest.raises(EventCaptured, lambda: e(ValueError()))
def test_traceparent_header_wsgi(self): # Assert that posting something to store will not create another # (transaction) event under any circumstances. # # We use Werkzeug's test client because Django's test client bypasses a # lot of request handling code that we want to test implicitly (such as # all our WSGI middlewares and the entire Django instrumentation by # sentry-sdk). # # XXX(markus): Ideally methods such as `_postWithHeader` would always # call the WSGI application => swap out Django's test client with e.g. # Werkzeug's. client = WerkzeugClient(application) calls = [] def new_disable_transaction_events(): with configure_scope() as scope: assert scope.span.sampled assert scope.span.transaction disable_transaction_events() assert not scope.span.sampled calls.append(1) events = [] auth = get_auth_header('_postWithWerkzeug/0.0.0', self.projectkey.public_key, self.projectkey.secret_key, '7') with mock.patch('sentry.web.api.disable_transaction_events', new_disable_transaction_events): with self.tasks(): with Hub( Client(transport=events.append, integrations=[ CeleryIntegration(), DjangoIntegration() ])): app_iter, status, headers = client.post( reverse('sentry-api-store'), data=b'{"message": "hello"}', headers={ 'x-sentry-auth': auth, 'sentry-trace': '1', 'content-type': 'application/octet-stream', }, environ_base={'REMOTE_ADDR': '127.0.0.1'}) body = ''.join(app_iter) assert status == '200 OK', body assert not events assert calls == [1]
def test_transport_infinite_loop(capturing_server, request, make_client): client = make_client( debug=True, # Make sure we cannot create events from our own logging integrations=[LoggingIntegration(event_level=logging.DEBUG)], ) with Hub(client): capture_message("hi") client.flush() assert len(capturing_server.captured) == 1
def sentry_start(self, *a, **kw): hub = Hub.current integration = hub.get_integration(ThreadingIntegration) if integration is not None: if not integration.propagate_hub: hub_ = None else: hub_ = Hub(hub) self.run = _wrap_run(hub_, self.run) return old_start(self, *a, **kw) # type: ignore
def test_transport_infinite_loop(httpserver, request): httpserver.serve_content("ok", 200) client = Client( "http://foobar@{}/123".format(httpserver.url[len("http://") :]), debug=True, # Make sure we cannot create events from our own logging integrations=[LoggingIntegration(event_level=logging.DEBUG)], ) with Hub(client): capture_message("hi") client.flush() assert len(httpserver.requests) == 1
def test_attach_stacktrace_enabled(): events = [] hub = Hub(Client(attach_stacktrace=True, transport=events.append)) def foo(): bar() def bar(): hub.capture_message("HI") foo() event, = events thread, = event["threads"]["values"] functions = [x["function"] for x in thread["stacktrace"]["frames"]] assert functions[-2:] == ["foo", "bar"]
def sentry_start(self, *a, **kw): hub = Hub.current integration = hub.get_integration(ThreadingIntegration) if integration is not None: if not integration.propagate_hub: hub_ = None else: hub_ = Hub(hub) # 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. self.run = _wrap_run(hub_, self.run.__func__) return old_start(self, *a, **kw) # type: ignore
def test_attach_stacktrace_enabled_no_locals(): events = [] hub = Hub( Client(attach_stacktrace=True, with_locals=False, transport=events.append) ) def foo(): bar() def bar(): hub.capture_message("HI") foo() (event,) = events (thread,) = event["threads"]["values"] local_vars = [x.get("vars") for x in thread["stacktrace"]["frames"]] assert local_vars[-2:] == [None, None]
async def lottery_status_cron_job(self): with Hub(Hub.current): # ensure that only one instance of job is running, other instances will be discarded if not self.lock.locked(): await self.lock.acquire() try: should_reload_options = False async with in_transaction(): await self._handle_stopping_sales() await self._handle_selecting_winning_tickets() should_reload_options = await self._handle_payments_to_winners() if should_reload_options: await reload_options_hack(self.bot) except Exception as e: logging.debug(f":::lottery_cron: {e}") capture_exception(e) finally: self.lock.release()
def capture_exception(self, exception=None): with configure_scope() as scope: scope.set_level('info') scope.set_user({'email': env.get_gitlab_user_email()}) scope.set_tag('host_url', env.get_target_host_data().full_url) scope.set_tag('git_branch_in_ci', env.get_git_branch_name()) # Replace '/' to '.' needs for compatibility with Prometheus format scope.set_tag('test', env.test_name_with_path()) scope.set_tag('ci_job_id', env.get_ci_job_id()) if self.__overload_job_url: scope.set_extra('overload_job_url', self.__overload_job_url) # Yes, we use concurrency issues # https://docs.sentry.io/platforms/python/troubleshooting/#addressing-concurrency-issues with Hub(Hub.current): self.init_client() event_id = sentry_sdk.capture_exception(exception, scope) event_sentry_url = f"https://your_sentry_instance_url/?query={event_id}" logging.warning( f"--- Sentry captured exception URL is: '{event_sentry_url}' ---")
def test_envelope_types(): """ Tests for calling the right transport method (capture_event vs capture_envelope) from the SDK client for different data types. """ envelopes = [] events = [] class CustomTransport(Transport): def capture_envelope(self, envelope): envelopes.append(envelope) def capture_event(self, event): events.append(event) with Hub(Client(traces_sample_rate=1.0, transport=CustomTransport())): event_id = capture_message("hello") # Assert error events get passed in via capture_event assert not envelopes event = events.pop() assert event["event_id"] == event_id assert "type" not in event with start_transaction(name="foo"): pass # Assert transactions get passed in via capture_envelope assert not events envelope = envelopes.pop() (item, ) = envelope.items assert item.data_category == "transaction" assert item.headers.get("type") == "transaction" assert not envelopes assert not events
async def middleware(self, handler: ASGIApp, request: Request, receive: Receive, send: Send): # type: ignore """Capture exceptions to Sentry.""" hub = Hub(Hub.current) with hub.configure_scope() as scope: scope.clear_breadcrumbs() scope._name = "muffin" self.current_scope.set(scope) scope.add_event_processor( partial(self.processData, request=request)) with hub.start_transaction( Transaction.continue_from_headers( request.headers, op=f"{request.scope['type']}.muffin"), custom_sampling_context={'asgi_scope': scope}): try: return await handler(request, receive, send) except Exception as exc: if type(exc) not in self.cfg.ignore_errors: hub.capture_exception(exc) raise exc from None
def bulk_raw_query(snuba_param_list, referrer=None): headers = {} if referrer: headers["referer"] = referrer query_param_list = map(_prepare_query_params, snuba_param_list) def snuba_query(params): query_params, forward, reverse, thread_hub = params try: with timer("snuba_query"): referrer = headers.get("referer", "<unknown>") if SNUBA_INFO: logger.info("{}.body: {}".format(referrer, json.dumps(query_params))) query_params["debug"] = True body = json.dumps(query_params) with thread_hub.start_span( op="snuba", description=u"query {}".format(referrer) ) as span: span.set_tag("referrer", referrer) for param_key, param_data in six.iteritems(query_params): span.set_data(param_key, param_data) return ( _snuba_pool.urlopen("POST", "/query", body=body, headers=headers), forward, reverse, ) except urllib3.exceptions.HTTPError as err: raise SnubaError(err) with sentry_sdk.start_span( op="start_snuba_query", description=u"running {} snuba queries".format(len(snuba_param_list)), ) as span: span.set_tag("referrer", headers.get("referer", "<unknown>")) if len(snuba_param_list) > 1: query_results = list( _query_thread_pool.map( snuba_query, [params + (Hub(Hub.current),) for params in query_param_list] ) ) else: # No need to submit to the thread pool if we're just performing a # single query query_results = [snuba_query(query_param_list[0] + (Hub(Hub.current),))] results = [] for response, _, reverse in query_results: try: body = json.loads(response.data) if SNUBA_INFO: if "sql" in body: logger.info( "{}.sql: {}".format(headers.get("referer", "<unknown>"), body["sql"]) ) if "error" in body: logger.info( "{}.err: {}".format(headers.get("referer", "<unknown>"), body["error"]) ) except ValueError: if response.status != 200: logger.error("snuba.query.invalid-json") raise SnubaError("Failed to parse snuba error response") raise UnexpectedResponseError( u"Could not decode JSON response: {}".format(response.data) ) if response.status != 200: if body.get("error"): error = body["error"] if response.status == 429: raise RateLimitExceeded(error["message"]) elif error["type"] == "schema": raise SchemaValidationError(error["message"]) elif error["type"] == "clickhouse": raise clickhouse_error_codes_map.get(error["code"], QueryExecutionError)( error["message"] ) else: raise SnubaError(error["message"]) else: raise SnubaError(u"HTTP {}".format(response.status)) # Forward and reverse translation maps from model ids to snuba keys, per column body["data"] = [reverse(d) for d in body["data"]] results.append(body) return results
cur_exc_chain = getattr(item, "pytest_sentry_exc_chain", []) if call.excinfo is not None: item.pytest_sentry_exc_chain = cur_exc_chain = cur_exc_chain + [ call.excinfo ] integration = Hub.current.get_integration(PytestIntegration) if (cur_exc_chain and call.excinfo is None) or integration.always_report: for exc_info in cur_exc_chain: capture_exception((exc_info.type, exc_info.value, exc_info.tb)) DEFAULT_HUB = Hub(Client()) def _resolve_hub_marker_value(marker_value): if marker_value is None: marker_value = DEFAULT_HUB else: marker_value = marker_value.args[0] if callable(marker_value): marker_value = marker_value() if marker_value is None: # user explicitly disabled reporting return Hub()
def captureException(self, *args, **kwargs): """Capture exception.""" with Hub(Hub.current, self.current_scope.get()) as hub: return hub.capture_exception(*args, **kwargs)
def test_simple_transport(): events = [] with Hub(Client(transport=events.append)): capture_message("Hello World!") assert events[0]["message"] == "Hello World!"
def captureMessage(self, *args, **kwargs): """Capture message.""" with Hub(Hub.current, self.current_scope.get()) as hub: return hub.capture_message(*args, **kwargs)