zulip/zproject/sentry.py

95 lines
4.0 KiB
Python

from typing import TYPE_CHECKING, Optional
import sentry_sdk
from django.utils.translation import override as override_language
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import ignore_logger
from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sentry_sdk.utils import capture_internal_exceptions
from version import ZULIP_VERSION
from zerver.lib.request import get_request_notes
if TYPE_CHECKING:
from sentry_sdk._types import Event, Hint
def add_context(event: "Event", hint: "Hint") -> Optional["Event"]:
if "exc_info" in hint:
_, exc_value, _ = hint["exc_info"]
# Ignore GeneratorExit, KeyboardInterrupt, and SystemExit exceptions
if not isinstance(exc_value, Exception):
return None
from django.conf import settings
from zerver.lib.request import get_current_request
from zerver.models import get_user_profile_by_id
with capture_internal_exceptions():
# event.user is the user context, from Sentry, which is
# pre-populated with some keys via its Django integration:
# https://docs.sentry.io/platforms/python/guides/django/enriching-error-data/additional-data/identify-user/
event.setdefault("tags", {})
user_info = event.get("user", {})
if user_info.get("id"):
user_profile = get_user_profile_by_id(user_info["id"])
event["tags"]["realm"] = user_info["realm"] = user_profile.realm.string_id or "root"
with override_language(settings.LANGUAGE_CODE):
# str() to force the lazy-translation to apply now,
# since it won't serialize into json for Sentry otherwise
user_info["role"] = str(user_profile.get_role_name())
# These are PII, and should be scrubbed
if "username" in user_info:
del user_info["username"]
if "email" in user_info:
del user_info["email"]
request = get_current_request()
if request:
request_notes = get_request_notes(request)
if request_notes.client is not None:
event["tags"]["client"] = request_notes.client.name
if request_notes.realm is not None:
event["tags"].setdefault("realm", request_notes.realm.string_id)
return event
def setup_sentry(dsn: Optional[str], environment: str) -> None:
if not dsn:
return
sentry_sdk.init(
dsn=dsn,
environment=environment,
release=ZULIP_VERSION,
integrations=[
DjangoIntegration(),
RedisIntegration(),
SqlalchemyIntegration(),
],
before_send=add_context,
# Because we strip the email/username from the Sentry data
# above, the effect of this flag is that the requests/users
# involved in exceptions will be identified in Sentry only by
# their IP address, user ID, realm, and role. We consider
# this an appropriate balance between avoiding Sentry getting
# PII while having the identifiers needed to determine that an
# exception only affects a small subset of users or realms.
send_default_pii=True,
)
# Ignore all of the loggers from django.security that are for user
# errors; see https://docs.djangoproject.com/en/3.0/ref/exceptions/#suspiciousoperation
ignore_logger("django.security.SuspiciousOperation")
ignore_logger("django.security.DisallowedHost")
ignore_logger("django.security.DisallowedModelAdminLookup")
ignore_logger("django.security.DisallowedModelAdminToField")
ignore_logger("django.security.DisallowedRedirect")
ignore_logger("django.security.InvalidSessionKey")
ignore_logger("django.security.RequestDataTooBig")
ignore_logger("django.security.SuspiciousFileOperation")
ignore_logger("django.security.SuspiciousMultipartForm")
ignore_logger("django.security.SuspiciousSession")
ignore_logger("django.security.TooManyFieldsSent")