From 0941368a6ff385786fb7d8dde73d75c29bbc80f2 Mon Sep 17 00:00:00 2001 From: Ehsan Zilaei Date: Wed, 14 Aug 2024 13:39:04 +0200 Subject: [PATCH] feat(sentry): add Sentry watcher (#120) * Add dependency Sentry * Add and initialize Sentry * Add Sentry configuration info to .env.example --- fai-rag-app/fai-backend/.env.example | 12 ++++ fai-rag-app/fai-backend/fai_backend/config.py | 6 ++ fai-rag-app/fai-backend/fai_backend/main.py | 9 ++- .../fai_backend/sentry/__init__.py | 0 .../fai-backend/fai_backend/sentry/watcher.py | 63 +++++++++++++++++++ fai-rag-app/fai-backend/fai_backend/setup.py | 16 +++++ fai-rag-app/fai-backend/poetry.lock | 51 ++++++++++++++- fai-rag-app/fai-backend/pyproject.toml | 1 + 8 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 fai-rag-app/fai-backend/fai_backend/sentry/__init__.py create mode 100644 fai-rag-app/fai-backend/fai_backend/sentry/watcher.py diff --git a/fai-rag-app/fai-backend/.env.example b/fai-rag-app/fai-backend/.env.example index 54ab08e5..9a4be736 100644 --- a/fai-rag-app/fai-backend/.env.example +++ b/fai-rag-app/fai-backend/.env.example @@ -109,3 +109,15 @@ CHAT_MODEL=gpt-4o # Model name to use for RAG scoring prompt SCORING_MODEL=gpt-3.5-turbo + +# Enable/Disable Sentry error tracking +SENTRY_ENABLED=false + +# Sentry configuration +# Refs: https://docs.sentry.io/platforms/python/configuration/ +# +SENTRY_DSN=your-sentry-dsn +SENTRY_LOGGING_LEVEL=ERROR +SENTRY_EVENT_LEVEL=ERROR +SENTRY_TRACE_SAMPLE_RATE=0.1 +SENTRY_ENVIRONMENT=development diff --git a/fai-rag-app/fai-backend/fai_backend/config.py b/fai-rag-app/fai-backend/fai_backend/config.py index aef88aea..ac6f0645 100644 --- a/fai-rag-app/fai-backend/fai_backend/config.py +++ b/fai-rag-app/fai-backend/fai_backend/config.py @@ -31,6 +31,12 @@ class Settings(BaseSettings, extra=Extra.ignore): DEFAULT_LANGUAGE: str = 'en' FILE_UPLOAD_PATH: str = 'uploads' LLM_BACKEND: Literal['parrot', 'openai'] = 'parrot' + SENTRY_ENABLED: bool = False + SENTRY_DSN: SecretStr = '' + SENTRY_LOGGING_LEVEL: str = 'ERROR' + SENTRY_EVENT_LEVEL: str = 'ERROR' + SENTRY_TRACE_SAMPLE_RATE: float = 0.1 + SENTRY_ENVIRONMENT: str = 'development' class Config: env_file = '.env' diff --git a/fai-rag-app/fai-backend/fai_backend/main.py b/fai-rag-app/fai-backend/fai_backend/main.py index 5b279626..57141643 100644 --- a/fai-rag-app/fai-backend/fai_backend/main.py +++ b/fai-rag-app/fai-backend/fai_backend/main.py @@ -3,6 +3,7 @@ from fastapi import Depends, FastAPI, Header, Request from fastapi.middleware.cors import CORSMiddleware +from sentry_sdk import Hub, capture_message from sse_starlette import EventSourceResponse, ServerSentEvent from starlette.responses import HTMLResponse, RedirectResponse @@ -26,18 +27,24 @@ from fai_backend.repositories import chat_history_repo from fai_backend.schema import ProjectUser from fai_backend.serializer.impl.base64 import Base64Serializer -from fai_backend.setup import setup_db, setup_project +from fai_backend.setup import setup_db, setup_project, setup_sentry from fai_backend.vector.routes import router as vector_router @asynccontextmanager async def lifespan(_app: FastAPI): + console.log('Try setup Sentry') + await setup_sentry() console.log('Try setup db') await setup_db() console.log('Try setup initial project') await setup_project() yield console.log('😴 Unmounting app ...') + console.log('Shutting down Sentry') + client = Hub.current.client + if client is not None: + client.close(timeout=2.0) app = FastAPI(title='FAI RAG App', redirect_slashes=True, lifespan=lifespan) diff --git a/fai-rag-app/fai-backend/fai_backend/sentry/__init__.py b/fai-rag-app/fai-backend/fai_backend/sentry/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fai-rag-app/fai-backend/fai_backend/sentry/watcher.py b/fai-rag-app/fai-backend/fai_backend/sentry/watcher.py new file mode 100644 index 00000000..fb76b833 --- /dev/null +++ b/fai-rag-app/fai-backend/fai_backend/sentry/watcher.py @@ -0,0 +1,63 @@ +import logging + +import sentry_sdk +from sentry_sdk.integrations.logging import LoggingIntegration + + +class Watcher: + """ + A class to configure and initialize Sentry integration for a Python application. + + Attributes: + ----------- + dns : str + The Sentry DSN (Data Source Name) pointing to the Sentry project. + level : int + The minimum logging level at which logs should be captured. + event_level : int + The minimum logging level at which logs should be sent as events to Sentry. + trace_sample_rate : float + The rate at which traces should be sampled and sent to Sentry. Range [0.0, 1.0]. + + Methods: + -------- + initialize(): + Initializes the Sentry SDK with the provided configuration. + get_config() -> dict[str, str]: + Returns the current Sentry configuration. + """ + + def __init__( + self, + dsn: str, + environment: str, + level: str = 'ERROR', + event_level: str = 'ERROR', + trace_sample_rate: float = 0.1 + ) -> None: + self.dsn = dsn + self.level = logging.getLevelName(level) + self.event_level = logging.getLevelName(event_level) + self.trace_sample_rate = trace_sample_rate + self.environment = environment + + def initialize(self) -> None: + sentry_logging = LoggingIntegration( + level=self.level, + event_level=self.event_level + ) + + sentry_sdk.init( + dsn=self.dsn, + integrations=[sentry_logging], + traces_sample_rate=self.trace_sample_rate, + environment=self.environment + ) + + def get_config(self) -> dict[str, str]: + return { + "dsn": self.dsn, + "level": self.level, + "event_level": self.event_level, + "trace_sample_rate": self.trace_sample_rate + } diff --git a/fai-rag-app/fai-backend/fai_backend/setup.py b/fai-rag-app/fai-backend/fai_backend/setup.py index a466892c..a17c3b93 100644 --- a/fai-rag-app/fai-backend/fai_backend/setup.py +++ b/fai-rag-app/fai-backend/fai_backend/setup.py @@ -10,6 +10,7 @@ from fai_backend.projects.schema import ProjectMember, ProjectRole from fai_backend.repositories import ConversationDocument, PinCodeModel, ProjectModel, projects_repo from fai_backend.assistant.models import AssistantTemplate, AssistantChatHistoryModel +from fai_backend.sentry.watcher import Watcher def use_route_names_as_operation_ids(app: FastAPI) -> None: @@ -91,3 +92,18 @@ async def setup_db(): database=client[settings.MONGO_DB_NAME], document_models=[ProjectModel, PinCodeModel, ConversationDocument, AssistantChatHistoryModel], ) + + +async def setup_sentry(): + if not settings.SENTRY_ENABLED: + return + + sentry_logger = Watcher( + dsn=settings.SENTRY_DSN.get_secret_value(), + environment=settings.SENTRY_ENVIRONMENT, + level=settings.SENTRY_LOGGING_LEVEL, + event_level=settings.SENTRY_EVENT_LEVEL, + trace_sample_rate=settings.SENTRY_TRACE_SAMPLE_RATE + ) + + sentry_logger.initialize() diff --git a/fai-rag-app/fai-backend/poetry.lock b/fai-rag-app/fai-backend/poetry.lock index 225ae583..23c5faa2 100644 --- a/fai-rag-app/fai-backend/poetry.lock +++ b/fai-rag-app/fai-backend/poetry.lock @@ -1847,9 +1847,13 @@ files = [ {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, @@ -4674,6 +4678,51 @@ files = [ {file = "sentinels-1.0.0.tar.gz", hash = "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1"}, ] +[[package]] +name = "sentry-sdk" +version = "1.31.0" +description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = "*" +files = [ + {file = "sentry-sdk-1.31.0.tar.gz", hash = "sha256:6de2e88304873484207fed836388e422aeff000609b104c802749fd89d56ba5b"}, + {file = "sentry_sdk-1.31.0-py2.py3-none-any.whl", hash = "sha256:64a7141005fb775b9db298a30de93e3b83e0ddd1232dc6f36eb38aebc1553291"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +loguru = ["loguru (>=0.5)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +tornado = ["tornado (>=5)"] + [[package]] name = "setuptools" version = "70.1.0" @@ -5866,4 +5915,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.11.9" -content-hash = "8cf518fb888652885f6cb72eb6d4a6e2c68f9851c07eda4df67945c4f0e3a4ff" +content-hash = "c86aa5c4956330085fd611d3dd2b862e9d0cfe475319c1e28cb8dda561803bd6" diff --git a/fai-rag-app/fai-backend/pyproject.toml b/fai-rag-app/fai-backend/pyproject.toml index 012421e2..ba2b5f63 100644 --- a/fai-rag-app/fai-backend/pyproject.toml +++ b/fai-rag-app/fai-backend/pyproject.toml @@ -26,6 +26,7 @@ langstream = "~0.3.1" openai = "~1.33.0" python-dotenv = "~1.0.1" sse-starlette = "~2.1.0" +sentry-sdk = "1.31.0" [tool.poetry.group.unstructured.dependencies] unstructured = { extras = ["md", "pdf", "docx"], version = "0.13.7" }