This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/authentik/lib/sentry.py

172 lines
5.8 KiB
Python
Raw Normal View History

2020-12-05 21:08:42 +00:00
"""authentik sentry integration"""
from asyncio.exceptions import CancelledError
from typing import Any, Optional
from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError
2020-09-15 09:29:43 +00:00
from celery.exceptions import CeleryError
from channels.middleware import BaseMiddleware
2020-09-30 13:55:59 +00:00
from channels_redis.core import ChannelFull
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation, ValidationError
from django.db import DatabaseError, InternalError, OperationalError, ProgrammingError
from django.http.response import Http404
2020-02-23 18:48:14 +00:00
from django_redis.exceptions import ConnectionInterrupted
from docker.errors import DockerException
from h11 import LocalProtocolError
from ldap3.core.exceptions import LDAPException
from redis.exceptions import ConnectionError as RedisConnectionError
2020-10-28 18:00:11 +00:00
from redis.exceptions import RedisError, ResponseError
2020-02-23 18:48:14 +00:00
from rest_framework.exceptions import APIException
from sentry_sdk import HttpTransport, Hub
from sentry_sdk import init as sentry_sdk_init
from sentry_sdk.api import set_tag
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.threading import ThreadingIntegration
from sentry_sdk.tracing import Transaction
from structlog.stdlib import get_logger
2020-09-30 13:55:59 +00:00
from websockets.exceptions import WebSocketException
from authentik import __version__, get_build_hash
from authentik.lib.config import CONFIG
from authentik.lib.utils.http import authentik_user_agent
from authentik.lib.utils.reflection import class_to_path, get_env
LOGGER = get_logger()
class SentryWSMiddleware(BaseMiddleware):
"""Sentry Websocket middleweare to set the transaction name based on
consumer class path"""
async def __call__(self, scope, receive, send):
transaction: Optional[Transaction] = Hub.current.scope.transaction
class_path = class_to_path(self.inner.consumer_class)
if transaction:
transaction.name = class_path
return await self.inner(scope, receive, send)
class SentryIgnoredException(Exception):
"""Base Class for all errors that are suppressed, and not sent to sentry."""
class SentryTransport(HttpTransport):
"""Custom sentry transport with custom user-agent"""
def __init__(self, options: dict[str, Any]) -> None:
super().__init__(options)
self._auth = self.parsed_dsn.to_auth(authentik_user_agent())
def sentry_init(**sentry_init_kwargs):
"""Configure sentry SDK"""
sentry_env = CONFIG.y("error_reporting.environment", "customer")
kwargs = {
"environment": sentry_env,
"send_default_pii": CONFIG.y_bool("error_reporting.send_pii", False),
"_experiments": {
"profiles_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.1)),
},
}
kwargs.update(**sentry_init_kwargs)
# pylint: disable=abstract-class-instantiated
sentry_sdk_init(
dsn=CONFIG.y("error_reporting.sentry_dsn"),
integrations=[
DjangoIntegration(transaction_style="function_name"),
CeleryIntegration(),
RedisIntegration(),
ThreadingIntegration(propagate_hub=True),
],
before_send=before_send,
traces_sampler=traces_sampler,
release=f"authentik@{__version__}",
transport=SentryTransport,
**kwargs,
)
set_tag("authentik.build_hash", get_build_hash("tagged"))
set_tag("authentik.env", get_env())
set_tag("authentik.component", "backend")
def traces_sampler(sampling_context: dict) -> float:
"""Custom sampler to ignore certain routes"""
path = sampling_context.get("asgi_scope", {}).get("path", "")
# Ignore all healthcheck routes
if path.startswith("/-/health") or path.startswith("/-/metrics"):
return 0
return float(CONFIG.y("error_reporting.sample_rate", 0.1))
def before_send(event: dict, hint: dict) -> Optional[dict]:
"""Check if error is database error, and ignore if so"""
# pylint: disable=no-name-in-module
from psycopg2.errors import Error
ignored_classes = (
2020-10-28 18:00:11 +00:00
# Inbuilt types
KeyboardInterrupt,
ConnectionResetError,
OSError,
PermissionError,
# Django Errors
Error,
ImproperlyConfigured,
DatabaseError,
OperationalError,
2020-02-23 18:48:14 +00:00
InternalError,
ProgrammingError,
SuspiciousOperation,
2020-10-28 18:00:11 +00:00
ValidationError,
# Redis errors
RedisConnectionError,
ConnectionInterrupted,
2020-10-28 18:00:11 +00:00
RedisError,
ResponseError,
# websocket errors
ChannelFull,
WebSocketException,
LocalProtocolError,
2020-10-28 18:00:11 +00:00
# rest_framework error
APIException,
2020-10-28 18:00:11 +00:00
# celery errors
WorkerLostError,
2020-10-28 18:00:11 +00:00
CeleryError,
SoftTimeLimitExceeded,
2020-10-28 18:00:11 +00:00
# custom baseclass
SentryIgnoredException,
2020-10-28 18:00:11 +00:00
# ldap errors
LDAPException,
# Docker errors
DockerException,
# End-user errors
Http404,
# AsyncIO
CancelledError,
)
exc_value = None
2019-12-31 11:51:16 +00:00
if "exc_info" in hint:
2020-07-07 12:02:20 +00:00
_, exc_value, _ = hint["exc_info"]
if isinstance(exc_value, ignored_classes):
LOGGER.debug("dropping exception", exc=exc_value)
return None
if "logger" in event:
if event["logger"] in [
"kombu",
"asyncio",
"multiprocessing",
"django_redis",
"django.security.DisallowedHost",
"django_redis.cache",
"celery.backends.redis",
"celery.worker",
"paramiko.transport",
]:
return None
LOGGER.debug("sending event to sentry", exc=exc_value, source_logger=event.get("logger", None))
if settings.DEBUG:
return None
return event