mirror of https://github.com/zulip/zulip.git
middleware: Send got_request_exception signal for JSON 500 errors.
This is ordinarily emitted by Django at https://github.com/django/django/blob/4.2.6/django/core/handlers/exception.py#L139 and received by Sentry at https://github.com/getsentry/sentry-python/blob/1.31.0/sentry_sdk/integrations/django/__init__.py#L166 Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
f71e2c8247
commit
f99cce91bf
|
@ -7,6 +7,7 @@ from urllib.parse import urlencode, urljoin
|
|||
|
||||
from django.conf import settings
|
||||
from django.conf.urls.i18n import is_language_prefix_patterns_used
|
||||
from django.core import signals
|
||||
from django.db import connection
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||
from django.http.response import HttpResponseBase
|
||||
|
@ -383,35 +384,35 @@ class JsonErrorHandler(MiddlewareMixin):
|
|||
|
||||
if isinstance(exception, JsonableError):
|
||||
response = json_response_from_error(exception)
|
||||
if response.status_code >= 500:
|
||||
# Here we use Django's log_response the way Django uses
|
||||
# it normally to log error responses. However, we make the small
|
||||
# modification of including the traceback to make the log message
|
||||
# more helpful. log_response takes care of knowing not to duplicate
|
||||
# the logging, so Django won't generate a second log message.
|
||||
log_response(
|
||||
"%s: %s",
|
||||
response.reason_phrase,
|
||||
request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
exception=exception,
|
||||
)
|
||||
return response
|
||||
|
||||
if RequestNotes.get_notes(request).error_format == "JSON" and not settings.TEST_SUITE:
|
||||
if response.status_code < 500:
|
||||
return response
|
||||
elif RequestNotes.get_notes(request).error_format == "JSON" and not settings.TEST_SUITE:
|
||||
response = json_response(res_type="error", msg=_("Internal server error"), status=500)
|
||||
log_response(
|
||||
"%s: %s",
|
||||
response.reason_phrase,
|
||||
request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
exception=exception,
|
||||
)
|
||||
return response
|
||||
else:
|
||||
return None
|
||||
|
||||
return None
|
||||
# Send the same signal that Django sends for an unhandled exception.
|
||||
# This is received by Sentry to log exceptions, and also by the Django
|
||||
# test HTTP client to show better error messages.
|
||||
try:
|
||||
raise exception # Ensure correct sys.exc_info().
|
||||
except BaseException:
|
||||
signals.got_request_exception.send(sender=None, request=request)
|
||||
|
||||
# Here we use Django's log_response the way Django uses
|
||||
# it normally to log error responses. However, we make the small
|
||||
# modification of including the traceback to make the log message
|
||||
# more helpful. log_response takes care of knowing not to duplicate
|
||||
# the logging, so Django won't generate a second log message.
|
||||
log_response(
|
||||
"%s: %s",
|
||||
response.reason_phrase,
|
||||
request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
exception=exception,
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
class TagRequests(MiddlewareMixin):
|
||||
|
|
|
@ -373,9 +373,6 @@ def validate_against_openapi_schema(
|
|||
# This first set of checks are primarily training wheels that we
|
||||
# hope to eliminate over time as we improve our API documentation.
|
||||
|
||||
# No 500 responses have been documented, so skip them
|
||||
if status_code.startswith("5"):
|
||||
return False
|
||||
if path not in openapi_spec.openapi()["paths"]:
|
||||
endpoint = find_openapi_endpoint(path)
|
||||
# If it doesn't match it hasn't been documented yet.
|
||||
|
|
|
@ -16,9 +16,10 @@ class HealthTest(ZulipTestCase):
|
|||
with mock.patch(
|
||||
"zerver.views.health.check_database",
|
||||
side_effect=ServerNotReadyError("Cannot query postgresql"),
|
||||
), self.assertLogs(level="ERROR") as logs:
|
||||
result = self.client_get("/health")
|
||||
self.assert_json_error(result, "Cannot query postgresql", status_code=500)
|
||||
), self.assertLogs(level="ERROR") as logs, self.assertRaisesRegex(
|
||||
ServerNotReadyError, r"^Cannot query postgresql$"
|
||||
):
|
||||
self.client_get("/health")
|
||||
self.assertIn(
|
||||
"zerver.lib.exceptions.ServerNotReadyError: Cannot query postgresql", logs.output[0]
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import orjson
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from zerver.lib.test_classes import ZulipTestCase
|
||||
from zerver.models import Message, Stream, get_realm, get_user
|
||||
|
@ -21,16 +22,14 @@ class TestIntegrationsDevPanel(ZulipTestCase):
|
|||
"custom_headers": "{}",
|
||||
"is_json": "true",
|
||||
}
|
||||
with self.assertLogs(level="ERROR") as logs, self.settings(TEST_SUITE=False):
|
||||
response = self.client_post(target_url, data)
|
||||
|
||||
self.assertEqual(response.status_code, 500) # Since the response would be forwarded.
|
||||
expected_response = {"result": "error", "msg": "Internal server error"}
|
||||
self.assertEqual(orjson.loads(response.content), expected_response)
|
||||
with self.assertLogs(level="ERROR") as logs, self.settings(
|
||||
TEST_SUITE=False
|
||||
), self.assertRaises(ValidationError):
|
||||
self.client_post(target_url, data)
|
||||
|
||||
# Intention of this test looks like to trigger ValidationError
|
||||
# so just testing ValidationError is printed along with Traceback in logs
|
||||
self.assert_length(logs.output, 1)
|
||||
self.assert_length(logs.output, 2)
|
||||
self.assertTrue(
|
||||
logs.output[0].startswith(
|
||||
"ERROR:django.request:Internal Server Error: /api/v1/external/airbrake\n"
|
||||
|
@ -38,6 +37,13 @@ class TestIntegrationsDevPanel(ZulipTestCase):
|
|||
)
|
||||
)
|
||||
self.assertTrue("ValidationError" in logs.output[0])
|
||||
self.assertTrue(
|
||||
logs.output[1].startswith(
|
||||
"ERROR:django.request:Internal Server Error: /devtools/integrations/check_send_webhook_fixture_message\n"
|
||||
"Traceback (most recent call last):\n"
|
||||
)
|
||||
)
|
||||
self.assertTrue("ValidationError" in logs.output[1])
|
||||
|
||||
def test_check_send_webhook_fixture_message_for_success_without_headers(self) -> None:
|
||||
bot = get_user("webhook-bot@zulip.com", self.zulip_realm)
|
||||
|
|
|
@ -563,12 +563,11 @@ class PushBouncerNotificationTest(BouncerTestCase):
|
|||
URL = settings.PUSH_NOTIFICATION_BOUNCER_URL + "/api/v1/remotes/push/register"
|
||||
with responses.RequestsMock() as resp, self.assertLogs(level="ERROR") as error_log:
|
||||
resp.add(responses.POST, URL, body=ConnectionError(), status=502)
|
||||
result = self.client_post(endpoint, {"token": token}, subdomain="zulip")
|
||||
self.assert_json_error(
|
||||
result,
|
||||
"ConnectionError while trying to connect to push notification bouncer",
|
||||
502,
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
PushNotificationBouncerRetryLaterError,
|
||||
r"^ConnectionError while trying to connect to push notification bouncer$",
|
||||
):
|
||||
self.client_post(endpoint, {"token": token}, subdomain="zulip")
|
||||
self.assertIn(
|
||||
f"ERROR:django.request:Bad Gateway: {endpoint}\nTraceback",
|
||||
error_log.output[0],
|
||||
|
@ -576,8 +575,11 @@ class PushBouncerNotificationTest(BouncerTestCase):
|
|||
|
||||
with responses.RequestsMock() as resp, self.assertLogs(level="WARNING") as warn_log:
|
||||
resp.add(responses.POST, URL, body=orjson.dumps({"msg": "error"}), status=500)
|
||||
result = self.client_post(endpoint, {"token": token}, subdomain="zulip")
|
||||
self.assert_json_error(result, "Received 500 from push notification bouncer", 502)
|
||||
with self.assertRaisesRegex(
|
||||
PushNotificationBouncerRetryLaterError,
|
||||
r"Received 500 from push notification bouncer$",
|
||||
):
|
||||
self.client_post(endpoint, {"token": token}, subdomain="zulip")
|
||||
self.assertEqual(
|
||||
warn_log.output[0],
|
||||
"WARNING:root:Received 500 from push notification bouncer",
|
||||
|
|
|
@ -120,7 +120,7 @@ def check_send_webhook_fixture_message(
|
|||
if response.status_code == 200:
|
||||
responses = [{"status_code": response.status_code, "message": response.content.decode()}]
|
||||
return json_success(request, data={"responses": responses})
|
||||
else:
|
||||
else: # nocoverage
|
||||
return response
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue