diff --git a/featureflags/http/app.py b/featureflags/http/app.py index 68028df..c42096e 100644 --- a/featureflags/http/app.py +++ b/featureflags/http/app.py @@ -28,9 +28,9 @@ def create_app() -> FastAPI: configure_lifecycle(app, container) if config.sentry.enabled: - from featureflags.sentry import configure_sentry + from featureflags.sentry import configure_sentry, SentryMode - configure_sentry(config.sentry, env_prefix="http", app=app) + configure_sentry(config.sentry, env_prefix="http", mode=SentryMode.HTTP) return app diff --git a/featureflags/metrics.py b/featureflags/metrics.py index aa3b451..8f9613d 100644 --- a/featureflags/metrics.py +++ b/featureflags/metrics.py @@ -35,8 +35,16 @@ def configure_metrics( ) instrumentator.instrument( app=app, - latency_lowr_buckets=[0.001, 0.005, 0.01, 0.025, 0.05, 0.1, - 0.25, 0.5], + latency_lowr_buckets=[ + 0.001, + 0.005, + 0.01, + 0.025, + 0.05, + 0.1, + 0.25, + 0.5, + ], ) log.info("Http instrumentation initialized") diff --git a/featureflags/models.py b/featureflags/models.py index f2ba38a..6347ba1 100644 --- a/featureflags/models.py +++ b/featureflags/models.py @@ -3,8 +3,6 @@ from datetime import datetime from typing import Any, ClassVar -from featureflags.protobuf.graph_pb2 import Check as CheckProto -from featureflags.protobuf.graph_pb2 import Variable as VariableProto from sqlalchemy import Index, Integer, UniqueConstraint from sqlalchemy.dialects.postgresql import ( ARRAY, @@ -17,6 +15,8 @@ from sqlalchemy.types import Boolean, Enum, String from featureflags.graph.types import Action +from featureflags.protobuf.graph_pb2 import Check as CheckProto +from featureflags.protobuf.graph_pb2 import Variable as VariableProto from featureflags.utils import ArrayOfEnum metadata = MetaData() diff --git a/featureflags/rpc/app.py b/featureflags/rpc/app.py index 65864c8..363260c 100644 --- a/featureflags/rpc/app.py +++ b/featureflags/rpc/app.py @@ -44,9 +44,11 @@ async def main() -> None: set_internal_user_session() if config.sentry.enabled: - from featureflags.sentry import configure_sentry + from featureflags.sentry import configure_sentry, SentryMode - configure_sentry(config.sentry, env_prefix="rpc") + configure_sentry( + config.sentry, env_prefix="rpc", mode=SentryMode.GRPC + ) server = await create_server() stack.enter_context(graceful_exit([server])) # type: ignore diff --git a/featureflags/rpc/db.py b/featureflags/rpc/db.py index 7c6bd1f..b83b892 100644 --- a/featureflags/rpc/db.py +++ b/featureflags/rpc/db.py @@ -5,11 +5,11 @@ from uuid import UUID, uuid4 from aiopg.sa import SAConnection -from featureflags.protobuf import service_pb2 from sqlalchemy import and_, select from sqlalchemy.dialects.postgresql import insert from featureflags.models import Flag, Project, Variable, VariableType +from featureflags.protobuf import service_pb2 from featureflags.utils import EntityCache, FlagAggStats diff --git a/featureflags/rpc/servicer.py b/featureflags/rpc/servicer.py index 1258642..a189e8d 100644 --- a/featureflags/rpc/servicer.py +++ b/featureflags/rpc/servicer.py @@ -3,7 +3,6 @@ import weakref import aiopg.sa -from featureflags.protobuf import service_grpc, service_pb2 from google.protobuf.empty_pb2 import Empty from grpclib.server import Stream from hiku.engine import Engine @@ -13,6 +12,7 @@ from featureflags.graph.graph import exec_graph from featureflags.graph.proto_adapter import populate_result_proto from featureflags.models import Project +from featureflags.protobuf import service_grpc, service_pb2 from featureflags.rpc.db import add_statistics from featureflags.rpc.metrics import track from featureflags.rpc.utils import debug_cancellation diff --git a/featureflags/sentry.py b/featureflags/sentry.py index 138626a..50ff9fa 100644 --- a/featureflags/sentry.py +++ b/featureflags/sentry.py @@ -1,19 +1,19 @@ import logging - -from fastapi import FastAPI +from enum import Enum try: import sentry_sdk - from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from sentry_sdk.integrations.asyncio import AsyncioIntegration from sentry_sdk.integrations.atexit import AtexitIntegration from sentry_sdk.integrations.dedupe import DedupeIntegration from sentry_sdk.integrations.excepthook import ExcepthookIntegration + from sentry_sdk.integrations.fastapi import FastApiIntegration from sentry_sdk.integrations.grpc import GRPCIntegration from sentry_sdk.integrations.logging import LoggingIntegration + from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration + from sentry_sdk.integrations.starlette import StarletteIntegration from sentry_sdk.integrations.stdlib import StdlibIntegration from sentry_sdk.integrations.threading import ThreadingIntegration - from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration except ImportError: raise ImportError( "`sentry_sdk` is not installed, please install it to use `sentry` " @@ -26,10 +26,15 @@ log = logging.getLogger(__name__) +class SentryMode(Enum): + HTTP = "http" + GRPC = "grpc" + + def configure_sentry( config: SentrySettings, env_prefix: str | None = None, - app: FastAPI | None = None, + mode: SentryMode = SentryMode.HTTP, ) -> None: """ Configure error logging to Sentry. @@ -37,6 +42,32 @@ def configure_sentry( env = f"{env_prefix}-{config.env}" if env_prefix else config.env + integrations = [ + AsyncioIntegration(), + AtexitIntegration(), + ExcepthookIntegration(), + DedupeIntegration(), + StdlibIntegration(), + ThreadingIntegration(), + LoggingIntegration(), + SqlalchemyIntegration(), + ] + + match mode: + case mode.HTTP: + # Add FastApi specific integrations. + integrations.extend( + [ + StarletteIntegration(transaction_style="endpoint"), + FastApiIntegration(transaction_style="endpoint"), + ] + ) + case mode.GRPC: + # Add gRPC specific integrations. + integrations.append(GRPCIntegration()) + case _: + raise ValueError(f"{mode} option is not supported") + sentry_sdk.init( dsn=config.dsn, environment=env, @@ -48,21 +79,6 @@ def configure_sentry( max_breadcrumbs=1000, enable_tracing=config.enable_tracing, traces_sample_rate=config.traces_sample_rate, - integrations=[ - AsyncioIntegration(), - AtexitIntegration(), - ExcepthookIntegration(), - DedupeIntegration(), - StdlibIntegration(), - ThreadingIntegration(), - LoggingIntegration(), - GRPCIntegration(), - SqlalchemyIntegration(), - ], + integrations=integrations, ) - - if app is not None: - # Add FastApi specific middleware. - app.add_middleware(SentryAsgiMiddleware) - - log.info(f"Sentry initialized with env: `{env}`") + log.info(f"Sentry initialized with env: `{env}` in mode: `{mode}`") diff --git a/featureflags/tests/test_graph.py b/featureflags/tests/test_graph.py index 7eb07b7..2fd0b45 100644 --- a/featureflags/tests/test_graph.py +++ b/featureflags/tests/test_graph.py @@ -2,7 +2,6 @@ from uuid import uuid4 import pytest -from featureflags.protobuf import graph_pb2 from google.protobuf.wrappers_pb2 import BoolValue # type: ignore from hiku.builder import Q, build from hiku.result import denormalize @@ -11,6 +10,7 @@ from featureflags.graph.proto_adapter import populate_result_proto from featureflags.graph.types import Action from featureflags.graph.utils import is_valid_uuid +from featureflags.protobuf import graph_pb2 from featureflags.services.auth import ( EmptyAccessTokenState, ExpiredAccessTokenState, diff --git a/featureflags/web/app.py b/featureflags/web/app.py index 56cbe2d..21bf991 100644 --- a/featureflags/web/app.py +++ b/featureflags/web/app.py @@ -38,9 +38,9 @@ def create_app() -> FastAPI: configure_lifecycle(app, container) if config.sentry.enabled: - from featureflags.sentry import configure_sentry + from featureflags.sentry import configure_sentry, SentryMode - configure_sentry(config.sentry, env_prefix="web", app=app) + configure_sentry(config.sentry, env_prefix="web", mode=SentryMode.HTTP) return app diff --git a/pdm.lock b/pdm.lock index 412870c..4d7af3e 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev", "docs", "lint", "test", "sentry"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:f58ff5ada62f011fb7b2dd8897413cbaa02369b9de83f1657b685180a6b11c3a" +content_hash = "sha256:1038b6ba8d08abeee75246a9ebae1d4c2f5aa80c919f5aa8d084fbe06d1ea88a" [[package]] name = "aiopg" @@ -434,7 +434,7 @@ name = "grpcio" version = "1.60.0" requires_python = ">=3.7" summary = "HTTP/2-based RPC framework" -groups = ["default"] +groups = ["default", "sentry"] files = [ {file = "grpcio-1.60.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:fb464479934778d7cc5baf463d959d361954d6533ad34c3a4f1d267e86ee25fd"}, {file = "grpcio-1.60.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:4b44d7e39964e808b071714666a812049765b26b3ea48c4434a3b317bac82f14"}, @@ -1279,7 +1279,7 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.40.4" +version = "1.40.5" summary = "Python client for Sentry (https://sentry.io)" groups = ["sentry"] dependencies = [ @@ -1287,23 +1287,39 @@ dependencies = [ "urllib3>=1.26.11; python_version >= \"3.6\"", ] files = [ - {file = "sentry-sdk-1.40.4.tar.gz", hash = "sha256:657abae98b0050a0316f0873d7149f951574ae6212f71d2e3a1c4c88f62d6456"}, - {file = "sentry_sdk-1.40.4-py2.py3-none-any.whl", hash = "sha256:ac5cf56bb897ec47135d239ddeedf7c1c12d406fb031a4c0caa07399ed014d7e"}, + {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, + {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, +] + +[[package]] +name = "sentry-sdk" +version = "1.40.5" +extras = ["fastapi", "grpcio"] +summary = "Python client for Sentry (https://sentry.io)" +groups = ["sentry"] +dependencies = [ + "fastapi>=0.79.0", + "grpcio>=1.21.1", + "sentry-sdk==1.40.5", +] +files = [ + {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, + {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, ] [[package]] name = "sentry-sdk" -version = "1.40.4" +version = "1.40.5" extras = ["fastapi"] summary = "Python client for Sentry (https://sentry.io)" groups = ["sentry"] dependencies = [ "fastapi>=0.79.0", - "sentry-sdk==1.40.4", + "sentry-sdk==1.40.5", ] files = [ - {file = "sentry-sdk-1.40.4.tar.gz", hash = "sha256:657abae98b0050a0316f0873d7149f951574ae6212f71d2e3a1c4c88f62d6456"}, - {file = "sentry_sdk-1.40.4-py2.py3-none-any.whl", hash = "sha256:ac5cf56bb897ec47135d239ddeedf7c1c12d406fb031a4c0caa07399ed014d7e"}, + {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, + {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 245ca7b..11b4c3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ license = {text = "MIT"} [project.optional-dependencies] sentry = [ - "sentry-sdk[fastapi]>=1.40.4", + "sentry-sdk[fastapi,grpcio]>=1.40.5", ] [build-system] @@ -111,6 +111,7 @@ extend-exclude = ''' | .venv | venv | .ve + | featureflags/protobuf )/ ''' @@ -170,6 +171,7 @@ exclude = [ ".ve", "__pycache__", "featureflags/migrations", + "featureflags/protobuf", ] line-length = 80 # Allow unused variables when underscore-prefixed. @@ -216,6 +218,7 @@ exclude = [ "venv", ".ve", "featureflags/migrations", + "featureflags/protobuf", "featureflags/tests", ] @@ -224,7 +227,7 @@ module = "hiku.*" follow_imports = "skip" [[tool.mypy.overrides]] -module = "featureflags_protobuf.*" +module = "featureflags.protobuf.*" follow_imports = "skip" disallow_untyped_decorators = false disable_error_code = [