From 16c8d0163a39ee4225c8779a5d38830abccc401d Mon Sep 17 00:00:00 2001 From: Rajat Ahuja Date: Wed, 15 Oct 2025 11:17:09 -0400 Subject: [PATCH 1/6] feat: filter validation error from sentry. centralize config --- src/deriver/queue_manager.py | 11 ++----- src/main.py | 34 ++++----------------- src/sentry.py | 58 ++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 38 deletions(-) create mode 100644 src/sentry.py diff --git a/src/deriver/queue_manager.py b/src/deriver/queue_manager.py index fbe463a0..356ae3ba 100644 --- a/src/deriver/queue_manager.py +++ b/src/deriver/queue_manager.py @@ -28,6 +28,7 @@ from src.dreamer.dream_scheduler import ( set_dream_scheduler, ) from src.models import QueueItem +from src.sentry import initialize_sentry from src.utils.work_unit import parse_work_unit_key from src.webhooks.events import ( QueueEmptyEvent, @@ -68,15 +69,7 @@ class QueueManager: # Initialize Sentry if enabled, using settings if settings.SENTRY.ENABLED: - sentry_sdk.init( - dsn=settings.SENTRY.DSN, - enable_tracing=True, - release=settings.SENTRY.RELEASE, - environment=settings.SENTRY.ENVIRONMENT, - traces_sample_rate=settings.SENTRY.TRACES_SAMPLE_RATE, - profiles_sample_rate=settings.SENTRY.PROFILES_SAMPLE_RATE, - integrations=[AsyncioIntegration()], - ) + initialize_sentry(integrations=[AsyncioIntegration()]) def add_task(self, task: asyncio.Task[None]) -> None: """Track a new task""" diff --git a/src/main.py b/src/main.py index c1cb1976..4d317b38 100644 --- a/src/main.py +++ b/src/main.py @@ -3,22 +3,16 @@ import re import uuid from collections.abc import Awaitable, Callable from contextlib import asynccontextmanager -from typing import TYPE_CHECKING import sentry_sdk from fastapi import FastAPI, Request, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi_pagination import add_pagination - -from src import prometheus -from src.utils.logging import get_route_template - -if TYPE_CHECKING: - from sentry_sdk._types import Event, Hint from sentry_sdk.integrations.fastapi import FastApiIntegration from sentry_sdk.integrations.starlette import StarletteIntegration +from src import prometheus from src.config import settings from src.db import engine, request_context from src.exceptions import HonchoException @@ -31,6 +25,8 @@ from src.routers import ( workspaces, ) from src.security import create_admin_jwt +from src.sentry import initialize_sentry +from src.utils.logging import get_route_template def get_log_level() -> int: @@ -71,27 +67,7 @@ async def setup_admin_jwt(): # Sentry Setup SENTRY_ENABLED = settings.SENTRY.ENABLED if SENTRY_ENABLED: - - def before_send(event: "Event", hint: "Hint") -> "Event | None": - if "exc_info" in hint: - _, exc_value, _ = hint["exc_info"] - # Filter out HonchoExceptions from being sent to Sentry - if isinstance(exc_value, HonchoException): - return None - - return event - - # Sentry SDK's default behavior: - # - Captures INFO+ level logs as breadcrumbs - # - Captures ERROR+ level logs as Sentry events - # - # For custom log levels, use the LoggingIntegration class: - # sentry_sdk.init(..., integrations=[LoggingIntegration(level=logging.INFO, event_level=logging.ERROR)]) - sentry_sdk.init( - dsn=settings.SENTRY.DSN, - traces_sample_rate=settings.SENTRY.TRACES_SAMPLE_RATE, - profiles_sample_rate=settings.SENTRY.PROFILES_SAMPLE_RATE, - before_send=before_send, + initialize_sentry( integrations=[ StarletteIntegration( transaction_style="endpoint", @@ -99,7 +75,7 @@ if SENTRY_ENABLED: FastApiIntegration( transaction_style="endpoint", ), - ], + ] ) diff --git a/src/sentry.py b/src/sentry.py new file mode 100644 index 00000000..1980f0e8 --- /dev/null +++ b/src/sentry.py @@ -0,0 +1,58 @@ +"""Sentry initialization and configuration.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +import sentry_sdk +from pydantic import ValidationError + +from src.config import settings +from src.exceptions import HonchoException + +if TYPE_CHECKING: + from sentry_sdk._types import Event, Hint + + +_UNSET = object() + + +def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: + """Filter out events raised from known non-actionable exceptions before Sentry sees them.""" + if not hint: + return event + + exc_info = hint.get("exc_info") + if not exc_info: + return event + + _, exc_value, _ = exc_info + if isinstance( + exc_value, HonchoException | ValidationError + ): # Filters out HonchoExceptions and ValidationErrors (typically coming from Pydantic) + return None + + return event + + +# Sentry SDK's default behavior: +# - Captures INFO+ level logs as breadcrumbs +# - Captures ERROR+ level logs as Sentry events +# +# For custom log levels, use the LoggingIntegration class: +# sentry_sdk.init(..., integrations=[LoggingIntegration(level=logging.INFO, event_level=logging.ERROR)]) +def initialize_sentry( + *, + integrations: Sequence[Any], +) -> None: + sentry_sdk.init( + dsn=settings.SENTRY.DSN, + enable_tracing=True, + release=settings.SENTRY.RELEASE, + environment=settings.SENTRY.ENVIRONMENT, + traces_sample_rate=settings.SENTRY.TRACES_SAMPLE_RATE, + profiles_sample_rate=settings.SENTRY.PROFILES_SAMPLE_RATE, + before_send=_filter_sentry_event, + integrations=integrations, + ) From 49b22c6f14fb00da3faf756d25ab7762461ceb1c Mon Sep 17 00:00:00 2001 From: Rajat Ahuja Date: Wed, 15 Oct 2025 11:31:12 -0400 Subject: [PATCH 2/6] fix: add integration type --- src/sentry.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/sentry.py b/src/sentry.py index 1980f0e8..7ad467f1 100644 --- a/src/sentry.py +++ b/src/sentry.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Sequence -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import sentry_sdk from pydantic import ValidationError @@ -13,9 +13,7 @@ from src.exceptions import HonchoException if TYPE_CHECKING: from sentry_sdk._types import Event, Hint - - -_UNSET = object() + from sentry_sdk.integrations import Integration def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: @@ -44,7 +42,7 @@ def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: # sentry_sdk.init(..., integrations=[LoggingIntegration(level=logging.INFO, event_level=logging.ERROR)]) def initialize_sentry( *, - integrations: Sequence[Any], + integrations: Sequence[Integration], ) -> None: sentry_sdk.init( dsn=settings.SENTRY.DSN, From ec144d17eb0bcebe14eb11b4d9d58181c389a5d2 Mon Sep 17 00:00:00 2001 From: Rajat Ahuja Date: Wed, 15 Oct 2025 11:44:37 -0400 Subject: [PATCH 3/6] fix: filter out fastapi validation error from sentry --- src/sentry.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sentry.py b/src/sentry.py index 7ad467f1..e1fae034 100644 --- a/src/sentry.py +++ b/src/sentry.py @@ -6,6 +6,7 @@ from collections.abc import Sequence from typing import TYPE_CHECKING import sentry_sdk +from fastapi.exceptions import RequestValidationError from pydantic import ValidationError from src.config import settings @@ -27,7 +28,7 @@ def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: _, exc_value, _ = exc_info if isinstance( - exc_value, HonchoException | ValidationError + exc_value, HonchoException | ValidationError | RequestValidationError ): # Filters out HonchoExceptions and ValidationErrors (typically coming from Pydantic) return None @@ -44,6 +45,11 @@ def initialize_sentry( *, integrations: Sequence[Integration], ) -> None: + """Initialize Sentry SDK with project settings. + + Args: + integrations: Sentry SDK integrations to enable (e.g., Starlette, FastAPI). + """ sentry_sdk.init( dsn=settings.SENTRY.DSN, enable_tracing=True, From 4dbf667debda6aa4076d4bcf2fa65cbd20368b8b Mon Sep 17 00:00:00 2001 From: Rajat Ahuja Date: Wed, 15 Oct 2025 11:48:17 -0400 Subject: [PATCH 4/6] fix: add log for sentry filters for debugging --- src/sentry.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/sentry.py b/src/sentry.py index e1fae034..498392f3 100644 --- a/src/sentry.py +++ b/src/sentry.py @@ -2,6 +2,7 @@ from __future__ import annotations +import logging from collections.abc import Sequence from typing import TYPE_CHECKING @@ -16,6 +17,8 @@ if TYPE_CHECKING: from sentry_sdk._types import Event, Hint from sentry_sdk.integrations import Integration +logger = logging.getLogger(__name__) + def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: """Filter out events raised from known non-actionable exceptions before Sentry sees them.""" @@ -27,9 +30,12 @@ def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: return event _, exc_value, _ = exc_info - if isinstance( - exc_value, HonchoException | ValidationError | RequestValidationError - ): # Filters out HonchoExceptions and ValidationErrors (typically coming from Pydantic) + if isinstance(exc_value, HonchoException): + return None + + # Filters out ValidationErrors and RequestValidationErrors (typically coming from Pydantic) + if isinstance(exc_value, ValidationError | RequestValidationError): + logger.info(f"Filtering out validation error from Sentry: {exc_value}") return None return event From f0b246197e657f31ffde8c6f2a8832bfed707cac Mon Sep 17 00:00:00 2001 From: Rajat Ahuja Date: Wed, 15 Oct 2025 11:57:56 -0400 Subject: [PATCH 5/6] fix: keep internal validation errors --- src/main.py | 30 +++++++++++++++++++++++++++++- src/sentry.py | 29 +++-------------------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/main.py b/src/main.py index 4d317b38..6ae59192 100644 --- a/src/main.py +++ b/src/main.py @@ -3,12 +3,15 @@ import re import uuid from collections.abc import Awaitable, Callable from contextlib import asynccontextmanager +from typing import TYPE_CHECKING import sentry_sdk from fastapi import FastAPI, Request, Response +from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi_pagination import add_pagination +from pydantic import ValidationError from sentry_sdk.integrations.fastapi import FastApiIntegration from sentry_sdk.integrations.starlette import StarletteIntegration @@ -28,6 +31,9 @@ from src.security import create_admin_jwt from src.sentry import initialize_sentry from src.utils.logging import get_route_template +if TYPE_CHECKING: + from sentry_sdk._types import Event, Hint + def get_log_level() -> int: """ @@ -64,6 +70,27 @@ async def setup_admin_jwt(): print(f"\n ADMIN JWT: {token}\n") +def before_send(event: "Event", hint: "Hint | None") -> "Event | None": + """Filter out events raised from known non-actionable exceptions before Sentry sees them.""" + if not hint: + return event + + exc_info = hint.get("exc_info") + if not exc_info: + return event + + _, exc_value, _ = exc_info + if isinstance(exc_value, HonchoException): + return None + + # Filters out ValidationErrors and RequestValidationErrors (typically coming from Pydantic) + if isinstance(exc_value, ValidationError | RequestValidationError): + logger.info(f"Filtering out validation error from Sentry: {exc_value}") + return None + + return event + + # Sentry Setup SENTRY_ENABLED = settings.SENTRY.ENABLED if SENTRY_ENABLED: @@ -75,7 +102,8 @@ if SENTRY_ENABLED: FastApiIntegration( transaction_style="endpoint", ), - ] + ], + before_send=before_send, ) diff --git a/src/sentry.py b/src/sentry.py index 498392f3..f213844c 100644 --- a/src/sentry.py +++ b/src/sentry.py @@ -7,40 +7,16 @@ from collections.abc import Sequence from typing import TYPE_CHECKING import sentry_sdk -from fastapi.exceptions import RequestValidationError -from pydantic import ValidationError from src.config import settings -from src.exceptions import HonchoException if TYPE_CHECKING: - from sentry_sdk._types import Event, Hint + from sentry_sdk._types import EventProcessor from sentry_sdk.integrations import Integration logger = logging.getLogger(__name__) -def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: - """Filter out events raised from known non-actionable exceptions before Sentry sees them.""" - if not hint: - return event - - exc_info = hint.get("exc_info") - if not exc_info: - return event - - _, exc_value, _ = exc_info - if isinstance(exc_value, HonchoException): - return None - - # Filters out ValidationErrors and RequestValidationErrors (typically coming from Pydantic) - if isinstance(exc_value, ValidationError | RequestValidationError): - logger.info(f"Filtering out validation error from Sentry: {exc_value}") - return None - - return event - - # Sentry SDK's default behavior: # - Captures INFO+ level logs as breadcrumbs # - Captures ERROR+ level logs as Sentry events @@ -50,6 +26,7 @@ def _filter_sentry_event(event: Event, hint: Hint | None) -> Event | None: def initialize_sentry( *, integrations: Sequence[Integration], + before_send: EventProcessor | None = None, ) -> None: """Initialize Sentry SDK with project settings. @@ -63,6 +40,6 @@ def initialize_sentry( environment=settings.SENTRY.ENVIRONMENT, traces_sample_rate=settings.SENTRY.TRACES_SAMPLE_RATE, profiles_sample_rate=settings.SENTRY.PROFILES_SAMPLE_RATE, - before_send=_filter_sentry_event, + before_send=before_send, integrations=integrations, ) From cffee2bf5003a03b0c3642545b9e21c2b66f2834 Mon Sep 17 00:00:00 2001 From: Rajat Ahuja Date: Wed, 15 Oct 2025 12:07:16 -0400 Subject: [PATCH 6/6] Update src/sentry.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/sentry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sentry.py b/src/sentry.py index f213844c..b11fe57c 100644 --- a/src/sentry.py +++ b/src/sentry.py @@ -32,6 +32,7 @@ def initialize_sentry( Args: integrations: Sentry SDK integrations to enable (e.g., Starlette, FastAPI). + before_send: Optional event filter callback to suppress specific exceptions. """ sentry_sdk.init( dsn=settings.SENTRY.DSN,