diff --git a/base_requirements.txt b/base_requirements.txt index 095906914..6bb537a6a 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -102,6 +102,10 @@ psycopg2-binary # https://github.com/yaml/pyyaml PyYAML +# Sentry SDK +# https://github.com/getsentry/sentry-python +sentry-sdk + # Social authentication framework # https://github.com/python-social-auth/social-core social-auth-core diff --git a/docs/administration/error-reporting.md b/docs/administration/error-reporting.md new file mode 100644 index 000000000..8ec83bdf0 --- /dev/null +++ b/docs/administration/error-reporting.md @@ -0,0 +1,29 @@ +# Error Reporting + +## Sentry + +NetBox v3.2.3 and later support native integration with [Sentry](https://sentry.io/) for automatic error reporting. To enable this feature, begin by creating a new project in Sentry to represent your NetBox deployment and obtain its corresponding data source name (DSN). This looks like a URL similar to the example below: + +``` +https://examplePublicKey@o0.ingest.sentry.io/0 +``` + +Once you have obtained a DSN, configure Sentry in NetBox's `configuration.py` file with the following parameters: + +```python +SENTRY_ENABLED = True +SENTRY_DSN = "https://YourDSNgoesHere@o0.ingest.sentry.io/0" +``` + +You can optionally attach one or more arbitrary tags to the outgoing error reports if desired by setting the `SENTRY_TAGS` parameter: + +```python +SENTRY_TAGS = { + "custom.foo": "123", + "custom.bar": "abc", +} +``` + +Once the configuration has been saved, restart the NetBox service. + +To test Sentry operation, try generating a 404 (page not found) error by navigating to an invalid URL, such as `https://netbox/404-error-testing`. After receiving a 404 response from the NetBox server, you should see the issue appear shortly in Sentry. diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index 76fd0a12c..4a2d62e91 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -404,6 +404,39 @@ The file path to the location where [custom scripts](../customization/custom-scr --- +## SENTRY_DSN + +Default: None + +Defines a Sentry data source name (DSN) for automated error reporting. `SENTRY_ENABLED` must be True for this parameter to take effect. For example: + +``` +SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0" +``` + +--- + +## SENTRY_ENABLED + +Default: False + +Set to True to enable automatic error reporting via [Sentry](https://sentry.io/). Requires `SENTRY_DSN` to be defined. + +--- + +## SENTRY_TAGS + +An optional dictionary of tags to apply to Sentry error reports. `SENTRY_ENABLED` must be True for this parameter to take effect. For example: + +``` +SENTRY_TAGS = { + "custom.foo": "123", + "custom.bar": "abc", +} +``` + +--- + ## SESSION_COOKIE_NAME Default: `sessionid` diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 6eadb3d9e..bfd432741 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -11,6 +11,7 @@ * [#9278](https://github.com/netbox-community/netbox/issues/9278) - Linkify device types count under manufacturers list * [#9280](https://github.com/netbox-community/netbox/issues/9280) - Allow adopting existing components when installing a module * [#9314](https://github.com/netbox-community/netbox/issues/9314) - Add device and VM filters for FHRP group assignments +* [#9340](https://github.com/netbox-community/netbox/issues/9340) - Introduce support for error reporting via Sentry ### Bug Fixes diff --git a/mkdocs.yml b/mkdocs.yml index 225c6d4bf..0b7108cd0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -123,6 +123,7 @@ nav: - Microsoft Azure AD: 'administration/authentication/microsoft-azure-ad.md' - Okta: 'administration/authentication/okta.md' - Permissions: 'administration/permissions.md' + - Error Reporting: 'administration/error-reporting.md' - Housekeeping: 'administration/housekeeping.md' - Replicating NetBox: 'administration/replicating-netbox.md' - NetBox Shell: 'administration/netbox-shell.md' diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 6dee1081a..e539ecbbe 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -8,9 +8,11 @@ import sys import warnings from urllib.parse import urlsplit +import sentry_sdk from django.contrib.messages import constants as messages from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.validators import URLValidator +from sentry_sdk.integrations.django import DjangoIntegration from netbox.config import PARAMS @@ -113,6 +115,9 @@ REMOTE_AUTH_GROUP_SEPARATOR = getattr(configuration, 'REMOTE_AUTH_GROUP_SEPARATO REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/') RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300) SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/') +SENTRY_DSN = getattr(configuration, 'SENTRY_DSN', None) +SENTRY_ENABLED = getattr(configuration, 'SENTRY_ENABLED', False) +SENTRY_TAGS = getattr(configuration, 'SENTRY_TAGS', {}) SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None) SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid') SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d') @@ -428,6 +433,26 @@ EXEMPT_PATHS = ( ) +# +# Sentry +# + +if SENTRY_ENABLED: + if not SENTRY_DSN: + raise ImproperlyConfigured("SENTRY_ENABLED is True but SENTRY_DSN has not been defined.") + sentry_sdk.init( + dsn=SENTRY_DSN, + release=VERSION, + integrations=[DjangoIntegration()], + traces_sample_rate=1.0, + send_default_pii=True, + http_proxy=HTTP_PROXIES.get('http') if HTTP_PROXIES else None, + https_proxy=HTTP_PROXIES.get('https') if HTTP_PROXIES else None + ) + for k, v in SENTRY_TAGS.items(): + sentry_sdk.set_tag(k, v) + + # # Django social auth # diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index e76efe0fe..e8ee4b7b6 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -100,4 +100,5 @@ urlpatterns = [ path('{}'.format(settings.BASE_PATH), include(_patterns)) ] +handler404 = 'netbox.views.handler_404' handler500 = 'netbox.views.server_error' diff --git a/netbox/netbox/views/__init__.py b/netbox/netbox/views/__init__.py index fad347c36..f159ee637 100644 --- a/netbox/netbox/views/__init__.py +++ b/netbox/netbox/views/__init__.py @@ -2,7 +2,6 @@ import platform import sys from django.conf import settings -from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.db.models import F from django.http import HttpResponseServerError @@ -11,9 +10,10 @@ from django.template import loader from django.template.exceptions import TemplateDoesNotExist from django.urls import reverse from django.views.decorators.csrf import requires_csrf_token -from django.views.defaults import ERROR_500_TEMPLATE_NAME +from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found from django.views.generic import View from packaging import version +from sentry_sdk import capture_message from circuits.models import Circuit, Provider from dcim.models import ( @@ -190,13 +190,21 @@ class StaticMediaFailureView(View): """ Display a user-friendly error message with troubleshooting tips when a static media file fails to load. """ - def get(self, request): return render(request, 'media_failure.html', { 'filename': request.GET.get('filename') }) +def handler_404(request, exception): + """ + Wrap Django's default 404 handler to enable Sentry reporting. + """ + capture_message("Page not found", level="error") + + return page_not_found(request, exception) + + @requires_csrf_token def server_error(request, template_name=ERROR_500_TEMPLATE_NAME): """ diff --git a/requirements.txt b/requirements.txt index 32c13d455..1e40f0b3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,6 +24,7 @@ netaddr==0.8.0 Pillow==9.1.0 psycopg2-binary==2.9.3 PyYAML==6.0 +sentry-sdk==1.5.12 social-auth-app-django==5.0.0 social-auth-core==4.2.0 svgwrite==1.4.2