diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 51d9e1c4b7ed376fd4e17a6651e0460e1cf2d1b1..4eab2dad571201d406bc83f0c8b974bf17699c24 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Added ~~~~~ * Provide an ``ExtensiblePolymorphicModel`` to support the features of extensible models for polymorphic models and vice-versa. +* Implement optional Sentry integration for error and performance tracing. Changed ~~~~~~~ diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index 7a733c13983ba1ca7602f9661ef458b64414db8e..9a0a8b5f2e7de5ff23ddd738db1b8974d32430d1 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -486,6 +486,7 @@ YARN_INSTALLED_APPS = [ "paper-css", "jquery-sortablejs", "sortablejs", + "@sentry/tracing", ] merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True) @@ -516,6 +517,7 @@ ANY_JS = { "Roboto500": {"css_url": JS_URL + "/@fontsource/roboto/500.css"}, "Roboto700": {"css_url": JS_URL + "/@fontsource/roboto/700.css"}, "Roboto900": {"css_url": JS_URL + "/@fontsource/roboto/900.css"}, + "Sentry": {"js_url": JS_URL + "/@sentry/tracing/build/bundle.tracing.js"}, } merge_app_settings("ANY_JS", ANY_JS, True) @@ -859,5 +861,31 @@ else: SASS_PROCESSOR_STORAGE = DEFAULT_FILE_STORAGE +SENTRY_ENABLED = _settings.get("health.sentry.enabled", False) +if SENTRY_ENABLED: + import sentry_sdk + from sentry_sdk.integrations.celery import CeleryIntegration + from sentry_sdk.integrations.django import DjangoIntegration + from sentry_sdk.integrations.redis import RedisIntegration + + from aleksis.core import __version__ + + SENTRY_SETTINGS = { + "dsn": _settings.get("health.sentry.dsn"), + "environment": _settings.get("health.sentry.environment"), + "traces_sample_rate": _settings.get("health.sentry.traces_sample_rate", 1.0), + "send_default_pii": _settings.get("health.sentry.send_default_pii", False), + "release": f"aleksis-core@{__version__}", + "in_app_include": "aleksis", + } + sentry_sdk.init( + integrations=[ + DjangoIntegration(transaction_style="function_name"), + RedisIntegration(), + CeleryIntegration(), + ], + **SENTRY_SETTINGS, + ) + # Add django-cleanup after all apps to ensure that it gets all signals as last app INSTALLED_APPS.append("django_cleanup.apps.CleanupConfig") diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html index 80e6ee8d7cae0837b1c6b91ce4b258bd2608f22b..0dd75f2c6f237dea1ecac47b010abfac9453000f 100644 --- a/aleksis/core/templates/core/base.html +++ b/aleksis/core/templates/core/base.html @@ -35,6 +35,24 @@ <script src="{% url "calendarweek_i18n_js" %}?first_day=6&locale={{ LANGUAGE_CODE }}" type="text/javascript"></script> + {% if SENTRY_ENABLED %} + {% if SENTRY_TRACE_ID %} + <meta name="sentry-trace" content="{{ SENTRY_TRACE_ID }}" /> + {% endif %} + {% include_js "Sentry" %} + {{ SENTRY_SETTINGS|json_script:"sentry_settings" }} + <script type="text/javascript"> + const sentry_settings = JSON.parse(document.getElementById('sentry_settings').textContent); + + Sentry.init({ + dsn: sentry_settings.dsn, + environment: sentry_settings.environment, + tracesSampleRate: sentry_settings.traces_sample_rate, + integrations: [new Sentry.Integrations.BrowserTracing()] + }); + </script> + {% endif %} + {# Include jQuery early to provide $(document).ready #} {% include_js "jQuery" %} diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index 2c40caf51d41045685a74b67800cc9575d3cf2de..5871fd8dd7f568d463c8f672373fa60b708caddf 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -208,12 +208,24 @@ def custom_information_processor(request: HttpRequest) -> dict: regrouped_pwa_icons.setdefault(pwa_icon.rel, {}) regrouped_pwa_icons[pwa_icon.rel][pwa_icon.size] = pwa_icon - return { + context = { "FOOTER_MENU": CustomMenu.get_default("footer"), "ADMINS": settings.ADMINS, "PWA_ICONS": regrouped_pwa_icons, + "SENTRY_ENABLED": settings.SENTRY_ENABLED, } + if settings.SENTRY_ENABLED: + context["SENTRY_SETTINGS"] = settings.SENTRY_SETTINGS + + import sentry_sdk + + span = sentry_sdk.Hub.current.scope.span + if span is not None: + context["SENTRY_TRACE_ID"] = span.to_traceparent() + + return context + def now_tomorrow() -> datetime: """Return current time tomorrow.""" diff --git a/poetry.lock b/poetry.lock index c9d4b0e2db4557925a052ed17ac3f3958a389ee9..5b8035e59280dcc14adefa0cb88178c007ced081 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2269,6 +2269,40 @@ python-versions = "*" [package.dependencies] urllib3 = "*" +[[package]] +name = "sentry-sdk" +version = "1.4.3" +description = "Python client for Sentry (https://sentry.io)" +category = "main" +optional = true +python-versions = "*" + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.10.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +flask = ["flask (>=0.11)", "blinker (>=1.1)"] +httpx = ["httpx (>=0.16.0)"] +pure_eval = ["pure-eval", "executing", "asttokens"] +pyspark = ["pyspark (>=2.4.4)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +tornado = ["tornado (>=5)"] + +[package.source] +type = "legacy" +url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple" +reference = "gitlab" + [[package]] name = "six" version = "1.16.0" @@ -2647,11 +2681,12 @@ pycryptodome = "*" [extras] ldap = ["django-auth-ldap"] s3 = ["boto3", "django-storages"] +sentry = [] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "9117742426e175d6402dac268ed74f7e72e084fe020e5ebf22e97915ff7acd0d" +content-hash = "d15f5a57e0a3e887cfe411829b19459af4ebfc9aadc6e2a0468b11f4fc03e6b4" [metadata.files] alabaster = [ @@ -3770,6 +3805,10 @@ selenium = [ {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, ] +sentry-sdk = [ + {file = "sentry-sdk-1.4.3.tar.gz", hash = "sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828"}, + {file = "sentry_sdk-1.4.3-py2.py3-none-any.whl", hash = "sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, diff --git a/pyproject.toml b/pyproject.toml index 7de56924b6d76c659ab4da8762e25e6815b9f435..044f77bfbf8d6f1c1e45923525274aa81d2a0f0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,10 +113,12 @@ Whoosh = "^2.7.4" django-titofisto = "^0.1.0" haystack-redis = "^0.0.1" python-gnupg = "^0.4.7" +sentry-sdk = {version = "^1.4.3", optional = true} [tool.poetry.extras] ldap = ["django-auth-ldap"] s3 = ["boto3", "django-storages"] +sentry = ["sentry"] [tool.poetry.dev-dependencies] aleksis-builddeps = "*"