feat(observability): add Sentry error and performance monitoring integration

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-08 21:09:17 +00:00
parent b4c6f75786
commit 58af9e5a29
10 changed files with 681 additions and 2 deletions
+9 -1
View File
@@ -1,7 +1,7 @@
# app/celery_app.py
from celery import Celery
from celery.signals import task_failure
from celery.signals import task_failure, worker_ready
from app.config import settings
@@ -22,6 +22,14 @@ celery.conf.task_routes = {
}
@worker_ready.connect
def init_sentry_on_worker_ready(**kwargs):
"""Initialise Sentry SDK in the Celery worker process."""
from app.utils.sentry import init_sentry
init_sentry(integrations_extra=["celery"])
@task_failure.connect
def task_failure_handler(
sender=None, task_id=None, exception=None, args=None, kwargs=None, traceback=None, einfo=None, **kw
+44
View File
@@ -941,6 +941,50 @@ class Settings(BaseSettings):
description="Support e-mail address displayed on the Help Center page.",
)
# ---------------------------------------------------------------------------
# Observability Sentry error & performance monitoring
# ---------------------------------------------------------------------------
sentry_dsn: Optional[str] = Field(
default=None,
description=(
"Sentry Data Source Name (DSN). When set, error reporting and "
"performance tracing are enabled automatically. Leave blank (or unset) "
"to disable Sentry entirely."
),
)
sentry_environment: str = Field(
default="production",
description=(
"Environment tag sent to Sentry (e.g. 'development', 'staging', 'production'). "
"Helps you filter events in the Sentry dashboard."
),
)
sentry_traces_sample_rate: float = Field(
default=0.1,
description=(
"Fraction of transactions to capture for performance monitoring (0.01.0). "
"Set to 0.0 to disable tracing, 1.0 to capture every transaction. "
"Values above 0 may increase Sentry quota usage."
),
)
sentry_profiles_sample_rate: float = Field(
default=0.0,
description=(
"Fraction of profiled transactions to send to Sentry (0.01.0). "
"Profiling is only active when traces_sample_rate > 0. "
"Defaults to 0.0 (disabled) to minimise overhead."
),
)
sentry_send_default_pii: bool = Field(
default=False,
description=(
"Whether to attach personally identifiable information (PII) such as "
"IP addresses and user agents to Sentry events. Disabled by default "
"for privacy compliance (GDPR / CCPA). Enable only if your Sentry "
"project is configured to handle PII."
),
)
@model_validator(mode="before")
@classmethod
def strip_outer_quotes(cls, data: Any) -> Any:
+4
View File
@@ -27,6 +27,7 @@ from app.middleware.request_size_limit import RequestSizeLimitMiddleware
from app.middleware.security_headers import SecurityHeadersMiddleware
from app.utils.config_validator import check_all_configs
from app.utils.notification import init_apprise, notify_shutdown, notify_startup
from app.utils.sentry import init_sentry
# Import the routers - now using views directly instead of frontend
from app.views import router as frontend_router
@@ -47,6 +48,9 @@ SESSION_SECRET = (
settings.session_secret or "INSECURE_DEFAULT_FOR_DEVELOPMENT_ONLY_DO_NOT_USE_IN_PRODUCTION_MINIMUM_32_CHARS"
)
# Initialise Sentry as early as possible so that any startup errors are captured
init_sentry()
@asynccontextmanager
async def lifespan(app: FastAPI):
+113
View File
@@ -0,0 +1,113 @@
"""
Sentry integration utilities for DocuElevate.
Call ``init_sentry()`` early in your application entry point (before any
request handling) to enable error tracking and performance monitoring. The
function is a no-op when ``SENTRY_DSN`` is not configured, so it is safe to
call unconditionally in all environments.
Example (FastAPI)::
from app.utils.sentry import init_sentry
init_sentry()
Example (Celery worker)::
from app.utils.sentry import init_sentry
init_sentry(integrations_extra=["celery"])
"""
from __future__ import annotations
import logging
from app.config import settings
logger = logging.getLogger(__name__)
def init_sentry(*, integrations_extra: list[str] | None = None) -> bool:
"""
Initialise the Sentry SDK if ``SENTRY_DSN`` is configured.
Args:
integrations_extra: Optional list of additional integration names to
activate. Currently recognised values: ``"celery"``. The
``FastApiIntegration``, ``SqlalchemyIntegration``, and
``LoggingIntegration`` are always included when the SDK is
initialised.
Returns:
``True`` when Sentry was successfully initialised, ``False`` otherwise
(e.g. DSN not configured or SDK not installed).
"""
dsn = settings.sentry_dsn
if not dsn:
logger.debug("Sentry DSN not configured error monitoring disabled")
return False
try:
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
except ImportError:
logger.warning(
"sentry-sdk is not installed. Install it with: pip install 'sentry-sdk[fastapi,celery,sqlalchemy]'"
)
return False
integrations = [
StarletteIntegration(transaction_style="url"),
FastApiIntegration(transaction_style="url"),
SqlalchemyIntegration(),
LoggingIntegration(
level=logging.INFO, # Breadcrumbs from INFO+
event_level=logging.ERROR, # Send Sentry events for ERROR+
),
]
if integrations_extra and "celery" in integrations_extra:
try:
from sentry_sdk.integrations.celery import CeleryIntegration
integrations.append(CeleryIntegration(monitor_beat_tasks=True))
except ImportError:
logger.warning("CeleryIntegration not available skipping")
# Clamp sample rates to [0.0, 1.0]
traces_rate = max(0.0, min(1.0, settings.sentry_traces_sample_rate))
profiles_rate = max(0.0, min(1.0, settings.sentry_profiles_sample_rate))
version = _get_app_version()
sentry_sdk.init(
dsn=dsn,
environment=settings.sentry_environment,
release=version,
integrations=integrations,
traces_sample_rate=traces_rate,
profiles_sample_rate=profiles_rate,
send_default_pii=settings.sentry_send_default_pii,
# Attach a request body snapshot to every event (helps debugging)
max_request_body_size="medium",
# Keep the SDK from attaching local variable values to stack frames
# by default; enable explicitly if needed for deeper debugging.
attach_stacktrace=True,
)
logger.info(
"Sentry initialised (environment=%s, traces_sample_rate=%s)",
settings.sentry_environment,
traces_rate,
)
return True
def _get_app_version() -> str | None:
"""Return the application version string for Sentry release tracking."""
try:
return settings.version or None
except AttributeError: # pragma: no cover
return None
+58
View File
@@ -2264,6 +2264,64 @@ SETTING_METADATA = {
"required": False,
"restart_required": False,
},
# Observability Sentry
"sentry_dsn": {
"category": "Observability",
"description": (
"Sentry DSN (Data Source Name) URL. When set, runtime errors and "
"performance traces are automatically sent to Sentry. "
"Leave blank to disable Sentry entirely."
),
"type": "string",
"sensitive": True,
"required": False,
"restart_required": True,
},
"sentry_environment": {
"category": "Observability",
"description": (
"Environment label attached to every Sentry event "
"(e.g. 'development', 'staging', 'production'). "
"Helps you filter events in the Sentry dashboard. Default: 'production'."
),
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"sentry_traces_sample_rate": {
"category": "Observability",
"description": (
"Fraction of transactions captured for Sentry performance monitoring (0.01.0). "
"0.0 disables tracing; 1.0 captures every request. Default: 0.1 (10%)."
),
"type": "float",
"sensitive": False,
"required": False,
"restart_required": True,
},
"sentry_profiles_sample_rate": {
"category": "Observability",
"description": (
"Fraction of profiled transactions sent to Sentry (0.01.0). "
"Only active when sentry_traces_sample_rate > 0. Default: 0.0 (disabled)."
),
"type": "float",
"sensitive": False,
"required": False,
"restart_required": True,
},
"sentry_send_default_pii": {
"category": "Observability",
"description": (
"Attach personally identifiable information (PII) such as IP addresses "
"to Sentry events. Disabled by default for GDPR/CCPA compliance."
),
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": True,
},
}