From 7f8f8920f136bee8c0af930f33517b372e5d4bcc Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Fri, 7 Jun 2024 15:15:01 +0200 Subject: [PATCH 01/13] feat(otel): Autoinstrumentation skeleton --- .../integrations/opentelemetry/distro.py | 61 +++++++++++++++++++ .../integrations/opentelemetry/integration.py | 28 +++------ setup.py | 61 ++++++++++++++++--- 3 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 sentry_sdk/integrations/opentelemetry/distro.py diff --git a/sentry_sdk/integrations/opentelemetry/distro.py b/sentry_sdk/integrations/opentelemetry/distro.py new file mode 100644 index 0000000000..b370c3825a --- /dev/null +++ b/sentry_sdk/integrations/opentelemetry/distro.py @@ -0,0 +1,61 @@ +from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator +from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor +from sentry_sdk.utils import logger +from sentry_sdk._types import TYPE_CHECKING + +try: + from opentelemetry import trace + from opentelemetry.instrumentation.distro import BaseDistro + from opentelemetry.propagate import set_global_textmap + from opentelemetry.sdk.trace import TracerProvider +except ImportError: + raise DidNotEnable("opentelemetry not installed") + + +try: + from opentelemetry.instrumentation.django import DjangoInstrumentor +except ImportError: + DjangoInstrumentor = None + +try: + from opentelemetry.instrumentation.flask import FlaskInstrumentor +except ImportError: + FlaskInstrumentor = None + +if TYPE_CHECKING: + # XXX pkg_resources is deprecated, there's a PR to switch to importlib: + # https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2181 + # we should align this when the PR gets merged + from pkg_resources import EntryPoint + from typing import Any + + +CONFIGURABLE_INSTRUMENTATIONS = { + DjangoInstrumentor: {"is_sql_commentor_enabled": True}, + FlaskInstrumentor: {"enable_commenter": True}, +} + + +class SentryDistro(BaseDistro): + def _configure(self, **kwargs): + # type: (Any) -> None + provider = TracerProvider() + provider.add_span_processor(SentrySpanProcessor()) + trace.set_tracer_provider(provider) + set_global_textmap(SentryPropagator()) + + def load_instrumentor(self, entry_point, **kwargs): + # type: (EntryPoint, Any) -> None + instrumentor = entry_point.load() + + if instrumentor in CONFIGURABLE_INSTRUMENTATIONS: + for key, value in CONFIGURABLE_INSTRUMENTATIONS[instrumentor].items(): + kwargs[key] = value + + instrumentor().instrument(**kwargs) + logger.debug( + "[OTel] %s instrumented (%s)", + entry_point.name, + ", ".join([f"{k}: {v}" for k, v in kwargs.items()]), + ) diff --git a/sentry_sdk/integrations/opentelemetry/integration.py b/sentry_sdk/integrations/opentelemetry/integration.py index 9e62d1feca..087ad8cb49 100644 --- a/sentry_sdk/integrations/opentelemetry/integration.py +++ b/sentry_sdk/integrations/opentelemetry/integration.py @@ -8,19 +8,14 @@ from importlib import import_module from sentry_sdk.integrations import DidNotEnable, Integration -from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor -from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator +from sentry_sdk.integrations.opentelemetry.distro import SentryDistro from sentry_sdk.utils import logger, _get_installed_modules from sentry_sdk._types import TYPE_CHECKING try: - from opentelemetry import trace # type: ignore from opentelemetry.instrumentation.auto_instrumentation._load import ( # type: ignore - _load_distro, _load_instrumentors, ) - from opentelemetry.propagate import set_global_textmap # type: ignore - from opentelemetry.sdk.trace import TracerProvider # type: ignore except ImportError: raise DidNotEnable("opentelemetry not installed") @@ -34,6 +29,7 @@ # instrumentation took place. "fastapi": "fastapi.FastAPI", "flask": "flask.Flask", + # XXX Add a mapping for all instrumentors that instrument a class } @@ -51,8 +47,13 @@ def setup_once(): original_classes = _record_unpatched_classes() try: - distro = _load_distro() + distro = SentryDistro() distro.configure() + # XXX This does some initial checks before loading instrumentations + # (checks OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, checks version + # compat). If we don't want this in the future, we can implement our + # own _load_instrumentors (it anyway just iterates over + # opentelemetry_instrumentor entry points). _load_instrumentors(distro) except Exception: logger.exception("[OTel] Failed to auto-initialize OpenTelemetry") @@ -65,8 +66,6 @@ def setup_once(): "You might have to make sure sentry_sdk.init() is called before importing anything else." ) - _setup_sentry_tracing() - logger.debug("[OTel] Finished setting up OpenTelemetry integration") @@ -161,14 +160,3 @@ def _import_by_path(path): # type: (str) -> type parts = path.rsplit(".", maxsplit=1) return getattr(import_module(parts[0]), parts[-1]) - - -def _setup_sentry_tracing(): - # type: () -> None - provider = TracerProvider() - - provider.add_span_processor(SentrySpanProcessor()) - - trace.set_tracer_provider(provider) - - set_global_textmap(SentryPropagator()) diff --git a/setup.py b/setup.py index 21a1c60c72..3ea9cc9184 100644 --- a/setup.py +++ b/setup.py @@ -66,14 +66,59 @@ def get_file_text(file_name): "openai": ["openai>=1.0.0", "tiktoken>=0.3.0"], "opentelemetry": ["opentelemetry-distro>=0.35b0"], "opentelemetry-experimental": [ - "opentelemetry-distro~=0.40b0", - "opentelemetry-instrumentation-aiohttp-client~=0.40b0", - "opentelemetry-instrumentation-django~=0.40b0", - "opentelemetry-instrumentation-fastapi~=0.40b0", - "opentelemetry-instrumentation-flask~=0.40b0", - "opentelemetry-instrumentation-requests~=0.40b0", - "opentelemetry-instrumentation-sqlite3~=0.40b0", - "opentelemetry-instrumentation-urllib~=0.40b0", + # There's an umbrella package called + # opentelemetry-contrib-instrumentations that installs all + # available instrumentation packages, however it's broken in recent + # versions (after 0.41b0), see + # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2053 + "opentelemetry-instrumentation-aio-pika==0.46b0", + "opentelemetry-instrumentation-aiohttp-client==0.46b0", + # "opentelemetry-instrumentation-aiohttp-server==0.46b0", # broken package + "opentelemetry-instrumentation-aiopg==0.46b0", + "opentelemetry-instrumentation-asgi==0.46b0", + "opentelemetry-instrumentation-asyncio==0.46b0", + "opentelemetry-instrumentation-asyncpg==0.46b0", + "opentelemetry-instrumentation-aws-lambda==0.46b0", + "opentelemetry-instrumentation-boto==0.46b0", + "opentelemetry-instrumentation-boto3sqs==0.46b0", + "opentelemetry-instrumentation-botocore==0.46b0", + "opentelemetry-instrumentation-cassandra==0.46b0", + "opentelemetry-instrumentation-celery==0.46b0", + "opentelemetry-instrumentation-confluent-kafka==0.46b0", + "opentelemetry-instrumentation-dbapi==0.46b0", + "opentelemetry-instrumentation-django==0.46b0", + "opentelemetry-instrumentation-elasticsearch==0.46b0", + "opentelemetry-instrumentation-falcon==0.46b0", + "opentelemetry-instrumentation-fastapi==0.46b0", + "opentelemetry-instrumentation-flask==0.46b0", + "opentelemetry-instrumentation-grpc==0.46b0", + "opentelemetry-instrumentation-httpx==0.46b0", + "opentelemetry-instrumentation-jinja2==0.46b0", + "opentelemetry-instrumentation-kafka-python==0.46b0", + "opentelemetry-instrumentation-logging==0.46b0", + "opentelemetry-instrumentation-mysql==0.46b0", + "opentelemetry-instrumentation-mysqlclient==0.46b0", + "opentelemetry-instrumentation-pika==0.46b0", + "opentelemetry-instrumentation-psycopg==0.46b0", + "opentelemetry-instrumentation-psycopg2==0.46b0", + "opentelemetry-instrumentation-pymemcache==0.46b0", + "opentelemetry-instrumentation-pymongo==0.46b0", + "opentelemetry-instrumentation-pymysql==0.46b0", + "opentelemetry-instrumentation-pyramid==0.46b0", + "opentelemetry-instrumentation-redis==0.46b0", + "opentelemetry-instrumentation-remoulade==0.46b0", + "opentelemetry-instrumentation-requests==0.46b0", + "opentelemetry-instrumentation-sklearn==0.46b0", + "opentelemetry-instrumentation-sqlalchemy==0.46b0", + "opentelemetry-instrumentation-sqlite3==0.46b0", + "opentelemetry-instrumentation-starlette==0.46b0", + "opentelemetry-instrumentation-system-metrics==0.46b0", + "opentelemetry-instrumentation-threading==0.46b0", + "opentelemetry-instrumentation-tornado==0.46b0", + "opentelemetry-instrumentation-tortoiseorm==0.46b0", + "opentelemetry-instrumentation-urllib==0.46b0", + "opentelemetry-instrumentation-urllib3==0.46b0", + "opentelemetry-instrumentation-wsgi==0.46b0", ], "pure_eval": ["pure_eval", "executing", "asttokens"], "pymongo": ["pymongo>=3.1"], From f45dd838a8dc98bdba9a00c78339ef79c39418c9 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 11:17:28 +0200 Subject: [PATCH 02/13] install otel for linting --- linter-requirements.txt | 1 + sentry_sdk/integrations/opentelemetry/distro.py | 1 - tests/integrations/opentelemetry/test_experimental.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/linter-requirements.txt b/linter-requirements.txt index 289df0cd7f..2b90edc972 100644 --- a/linter-requirements.txt +++ b/linter-requirements.txt @@ -7,6 +7,7 @@ types-redis types-setuptools pymongo # There is no separate types module. loguru # There is no separate types module. +opentelemetry-distro flake8-bugbear pep8-naming pre-commit # local linting diff --git a/sentry_sdk/integrations/opentelemetry/distro.py b/sentry_sdk/integrations/opentelemetry/distro.py index b370c3825a..ae5fcd2ace 100644 --- a/sentry_sdk/integrations/opentelemetry/distro.py +++ b/sentry_sdk/integrations/opentelemetry/distro.py @@ -12,7 +12,6 @@ except ImportError: raise DidNotEnable("opentelemetry not installed") - try: from opentelemetry.instrumentation.django import DjangoInstrumentor except ImportError: diff --git a/tests/integrations/opentelemetry/test_experimental.py b/tests/integrations/opentelemetry/test_experimental.py index 06672a8657..fa68a73df2 100644 --- a/tests/integrations/opentelemetry/test_experimental.py +++ b/tests/integrations/opentelemetry/test_experimental.py @@ -1,7 +1,7 @@ -import pytest - from unittest.mock import MagicMock +import pytest + from sentry_sdk.integrations.opentelemetry.integration import OpenTelemetryIntegration From 21d3084297b821af62e295fe33aa4adcdf33808e Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 12:39:20 +0200 Subject: [PATCH 03/13] type: ingores --- linter-requirements.txt | 1 - sentry_sdk/integrations/opentelemetry/distro.py | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/linter-requirements.txt b/linter-requirements.txt index 2b90edc972..289df0cd7f 100644 --- a/linter-requirements.txt +++ b/linter-requirements.txt @@ -7,7 +7,6 @@ types-redis types-setuptools pymongo # There is no separate types module. loguru # There is no separate types module. -opentelemetry-distro flake8-bugbear pep8-naming pre-commit # local linting diff --git a/sentry_sdk/integrations/opentelemetry/distro.py b/sentry_sdk/integrations/opentelemetry/distro.py index ae5fcd2ace..2cf9381298 100644 --- a/sentry_sdk/integrations/opentelemetry/distro.py +++ b/sentry_sdk/integrations/opentelemetry/distro.py @@ -5,20 +5,20 @@ from sentry_sdk._types import TYPE_CHECKING try: - from opentelemetry import trace - from opentelemetry.instrumentation.distro import BaseDistro - from opentelemetry.propagate import set_global_textmap - from opentelemetry.sdk.trace import TracerProvider + from opentelemetry import trace # type: ignore + from opentelemetry.instrumentation.distro import BaseDistro # type: ignore + from opentelemetry.propagate import set_global_textmap # type: ignore + from opentelemetry.sdk.trace import TracerProvider # type: ignore except ImportError: raise DidNotEnable("opentelemetry not installed") try: - from opentelemetry.instrumentation.django import DjangoInstrumentor + from opentelemetry.instrumentation.django import DjangoInstrumentor # type: ignore except ImportError: DjangoInstrumentor = None try: - from opentelemetry.instrumentation.flask import FlaskInstrumentor + from opentelemetry.instrumentation.flask import FlaskInstrumentor # type: ignore except ImportError: FlaskInstrumentor = None From c8a3660fd24f61e343261cfc5d161a6412f576de Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 12:44:53 +0200 Subject: [PATCH 04/13] more mypy --- sentry_sdk/integrations/opentelemetry/distro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/opentelemetry/distro.py b/sentry_sdk/integrations/opentelemetry/distro.py index 2cf9381298..6489bdb6c2 100644 --- a/sentry_sdk/integrations/opentelemetry/distro.py +++ b/sentry_sdk/integrations/opentelemetry/distro.py @@ -36,7 +36,7 @@ } -class SentryDistro(BaseDistro): +class SentryDistro(BaseDistro): # type: ignore[misc] def _configure(self, **kwargs): # type: (Any) -> None provider = TracerProvider() From 363b203258154dad3309b06565e60d3865f0c4b7 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 15:01:10 +0200 Subject: [PATCH 05/13] add otel experimental test target --- .../test-integrations-miscellaneous.yml | 8 ++++ .../split-tox-gh-actions.py | 1 + .../integrations/opentelemetry/distro.py | 8 +++- .../integrations/opentelemetry/integration.py | 4 +- .../opentelemetry/test_experimental.py | 40 +++++++++++++++++-- tox.ini | 8 ++++ 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-integrations-miscellaneous.yml b/.github/workflows/test-integrations-miscellaneous.yml index f56e5004a5..e33b69838c 100644 --- a/.github/workflows/test-integrations-miscellaneous.yml +++ b/.github/workflows/test-integrations-miscellaneous.yml @@ -50,6 +50,10 @@ jobs: run: | set -x # print commands that are executed ./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry-latest" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + - name: Test opentelemetry_experimental latest + run: | + set -x # print commands that are executed + ./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry_experimental-latest" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch - name: Test pure_eval latest run: | set -x # print commands that are executed @@ -98,6 +102,10 @@ jobs: run: | set -x # print commands that are executed ./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-opentelemetry" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + - name: Test opentelemetry_experimental pinned + run: | + set -x # print commands that are executed + ./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-opentelemetry_experimental" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch - name: Test pure_eval pinned run: | set -x # print commands that are executed diff --git a/scripts/split-tox-gh-actions/split-tox-gh-actions.py b/scripts/split-tox-gh-actions/split-tox-gh-actions.py index a4e4038156..f171e26eb2 100755 --- a/scripts/split-tox-gh-actions/split-tox-gh-actions.py +++ b/scripts/split-tox-gh-actions/split-tox-gh-actions.py @@ -117,6 +117,7 @@ "Miscellaneous": [ "loguru", "opentelemetry", + "opentelemetry_experimental", "pure_eval", "trytond", ], diff --git a/sentry_sdk/integrations/opentelemetry/distro.py b/sentry_sdk/integrations/opentelemetry/distro.py index 6489bdb6c2..a475139ba1 100644 --- a/sentry_sdk/integrations/opentelemetry/distro.py +++ b/sentry_sdk/integrations/opentelemetry/distro.py @@ -1,3 +1,9 @@ +""" +IMPORTANT: The contents of this file are part of a proof of concept and as such +are experimental and not suitable for production use. They may be changed or +removed at any time without prior notice. +""" + from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor @@ -36,7 +42,7 @@ } -class SentryDistro(BaseDistro): # type: ignore[misc] +class _SentryDistro(BaseDistro): # type: ignore[misc] def _configure(self, **kwargs): # type: (Any) -> None provider = TracerProvider() diff --git a/sentry_sdk/integrations/opentelemetry/integration.py b/sentry_sdk/integrations/opentelemetry/integration.py index 087ad8cb49..dfb847dd5b 100644 --- a/sentry_sdk/integrations/opentelemetry/integration.py +++ b/sentry_sdk/integrations/opentelemetry/integration.py @@ -8,7 +8,7 @@ from importlib import import_module from sentry_sdk.integrations import DidNotEnable, Integration -from sentry_sdk.integrations.opentelemetry.distro import SentryDistro +from sentry_sdk.integrations.opentelemetry.distro import _SentryDistro from sentry_sdk.utils import logger, _get_installed_modules from sentry_sdk._types import TYPE_CHECKING @@ -47,7 +47,7 @@ def setup_once(): original_classes = _record_unpatched_classes() try: - distro = SentryDistro() + distro = _SentryDistro() distro.configure() # XXX This does some initial checks before loading instrumentations # (checks OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, checks version diff --git a/tests/integrations/opentelemetry/test_experimental.py b/tests/integrations/opentelemetry/test_experimental.py index fa68a73df2..0192f9d8ce 100644 --- a/tests/integrations/opentelemetry/test_experimental.py +++ b/tests/integrations/opentelemetry/test_experimental.py @@ -4,8 +4,22 @@ from sentry_sdk.integrations.opentelemetry.integration import OpenTelemetryIntegration +try: + import opentelemetry.instrumentation.asyncio # noqa: F401 + + # We actually expect all OTel instrumentation packages to be available, but + # for simplicity we just check for one here. + instrumentation_packages_installed = True +except ImportError: + instrumentation_packages_installed = False + + +needs_potel = pytest.mark.skipif( + not instrumentation_packages_installed, + reason="needs experimental OTel support enabled", +) + -@pytest.mark.forked def test_integration_enabled_if_option_is_on(sentry_init): OpenTelemetryIntegration.setup_once = MagicMock() sentry_init( @@ -16,7 +30,6 @@ def test_integration_enabled_if_option_is_on(sentry_init): OpenTelemetryIntegration.setup_once.assert_called_once() -@pytest.mark.forked def test_integration_not_enabled_if_option_is_off(sentry_init): OpenTelemetryIntegration.setup_once = MagicMock() sentry_init( @@ -27,8 +40,29 @@ def test_integration_not_enabled_if_option_is_off(sentry_init): OpenTelemetryIntegration.setup_once.assert_not_called() -@pytest.mark.forked def test_integration_not_enabled_if_option_is_missing(sentry_init): OpenTelemetryIntegration.setup_once = MagicMock() sentry_init() OpenTelemetryIntegration.setup_once.assert_not_called() + + +@needs_potel +def test_instrumentors_applied(sentry_init): + sentry_init( + _experiments={ + "otel_powered_performance": True, + }, + ) + + assert False + + +@needs_potel +def test_post_patching(sentry_init): + sentry_init( + _experiments={ + "otel_powered_performance": True, + }, + ) + + assert False diff --git a/tox.ini b/tox.ini index 6aabb51682..123e65edfe 100644 --- a/tox.ini +++ b/tox.ini @@ -168,6 +168,10 @@ envlist = # OpenTelemetry (OTel) {py3.7,py3.9,py3.11,py3.12}-opentelemetry + # OpenTelemetry Experimental (POTel) + # XXX add 3.12 when officially supported + {py3.7,py3.9,py3.11}-opentelemetry_experimental + # pure_eval {py3.6,py3.11,py3.12}-pure_eval @@ -487,6 +491,9 @@ deps = # OpenTelemetry (OTel) opentelemetry: opentelemetry-distro + # OpenTelemetry Experimental (POTel) + opentelemetry_experimental: -e .[opentelemetry-experimental] + # pure_eval pure_eval: pure_eval @@ -647,6 +654,7 @@ setenv = loguru: TESTPATH=tests/integrations/loguru openai: TESTPATH=tests/integrations/openai opentelemetry: TESTPATH=tests/integrations/opentelemetry + opentelemetry_experimental: TESTPATH=tests/integrations/opentelemetry pure_eval: TESTPATH=tests/integrations/pure_eval pymongo: TESTPATH=tests/integrations/pymongo pyramid: TESTPATH=tests/integrations/pyramid From 06f9070f631a9db812682830c94fd2b2b42dd59a Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 15:05:35 +0200 Subject: [PATCH 06/13] change target name --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 123e65edfe..6f0c244a12 100644 --- a/tox.ini +++ b/tox.ini @@ -170,7 +170,7 @@ envlist = # OpenTelemetry Experimental (POTel) # XXX add 3.12 when officially supported - {py3.7,py3.9,py3.11}-opentelemetry_experimental + {py3.7,py3.9,py3.11}-potel # pure_eval {py3.6,py3.11,py3.12}-pure_eval @@ -492,7 +492,7 @@ deps = opentelemetry: opentelemetry-distro # OpenTelemetry Experimental (POTel) - opentelemetry_experimental: -e .[opentelemetry-experimental] + potel: -e .[opentelemetry-experimental] # pure_eval pure_eval: pure_eval @@ -654,7 +654,7 @@ setenv = loguru: TESTPATH=tests/integrations/loguru openai: TESTPATH=tests/integrations/openai opentelemetry: TESTPATH=tests/integrations/opentelemetry - opentelemetry_experimental: TESTPATH=tests/integrations/opentelemetry + potel: TESTPATH=tests/integrations/opentelemetry pure_eval: TESTPATH=tests/integrations/pure_eval pymongo: TESTPATH=tests/integrations/pymongo pyramid: TESTPATH=tests/integrations/pyramid From 72bd97532fd79402068fe95417a845f7a8b4191a Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 15:16:29 +0200 Subject: [PATCH 07/13] rename everywhere maybe --- scripts/split-tox-gh-actions/split-tox-gh-actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/split-tox-gh-actions/split-tox-gh-actions.py b/scripts/split-tox-gh-actions/split-tox-gh-actions.py index f171e26eb2..87e774c930 100755 --- a/scripts/split-tox-gh-actions/split-tox-gh-actions.py +++ b/scripts/split-tox-gh-actions/split-tox-gh-actions.py @@ -117,7 +117,7 @@ "Miscellaneous": [ "loguru", "opentelemetry", - "opentelemetry_experimental", + "potel", "pure_eval", "trytond", ], From 952aad22ff9646783bf3a6b63f9376558503760e Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 15:16:40 +0200 Subject: [PATCH 08/13] rename everywhere maybe --- .github/workflows/test-integrations-miscellaneous.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-integrations-miscellaneous.yml b/.github/workflows/test-integrations-miscellaneous.yml index e33b69838c..d925b3f7d9 100644 --- a/.github/workflows/test-integrations-miscellaneous.yml +++ b/.github/workflows/test-integrations-miscellaneous.yml @@ -50,10 +50,10 @@ jobs: run: | set -x # print commands that are executed ./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry-latest" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch - - name: Test opentelemetry_experimental latest + - name: Test potel latest run: | set -x # print commands that are executed - ./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry_experimental-latest" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-potel-latest" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch - name: Test pure_eval latest run: | set -x # print commands that are executed @@ -102,10 +102,10 @@ jobs: run: | set -x # print commands that are executed ./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-opentelemetry" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch - - name: Test opentelemetry_experimental pinned + - name: Test potel pinned run: | set -x # print commands that are executed - ./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-opentelemetry_experimental" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-potel" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch - name: Test pure_eval pinned run: | set -x # print commands that are executed From 61d17e8202a80d111085d96d3fe4d24658ebf426 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 16:31:29 +0200 Subject: [PATCH 09/13] more testing --- sentry_sdk/client.py | 10 +++++--- tests/conftest.py | 13 +++++++++-- .../opentelemetry/test_experimental.py | 23 +++++++++++-------- tox.ini | 1 + 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index dc31e5ce1b..88619a3ce6 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -353,9 +353,13 @@ def _capture_envelope(envelope): "[OTel] Enabling experimental OTel-powered performance monitoring." ) self.options["instrumenter"] = INSTRUMENTER.OTEL - _DEFAULT_INTEGRATIONS.append( - "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration", - ) + if ( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration" + not in _DEFAULT_INTEGRATIONS + ): + _DEFAULT_INTEGRATIONS.append( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration", + ) self.integrations = setup_integrations( self.options["integrations"], diff --git a/tests/conftest.py b/tests/conftest.py index 118408cfc3..1be4507825 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,10 @@ import sentry_sdk from sentry_sdk.envelope import Envelope -from sentry_sdk.integrations import _processed_integrations # noqa: F401 +from sentry_sdk.integrations import ( # noqa: F401 + _DEFAULT_INTEGRATIONS, + _processed_integrations, +) from sentry_sdk.profiler import teardown_profiler from sentry_sdk.transport import Transport from sentry_sdk.utils import reraise @@ -168,7 +171,13 @@ def reset_integrations(): with a clean slate to ensure monkeypatching works well, but this also means some other stuff will be monkeypatched twice. """ - global _processed_integrations + global _DEFAULT_INTEGRATIONS, _processed_integrations + try: + _DEFAULT_INTEGRATIONS.remove( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration" + ) + except ValueError: + pass _processed_integrations.clear() diff --git a/tests/integrations/opentelemetry/test_experimental.py b/tests/integrations/opentelemetry/test_experimental.py index 0192f9d8ce..3ce938d151 100644 --- a/tests/integrations/opentelemetry/test_experimental.py +++ b/tests/integrations/opentelemetry/test_experimental.py @@ -16,38 +16,38 @@ needs_potel = pytest.mark.skipif( not instrumentation_packages_installed, - reason="needs experimental OTel support enabled", + reason="needs OTel instrumentor libraries installed", ) -def test_integration_enabled_if_option_is_on(sentry_init): +def test_integration_enabled_if_option_is_on(sentry_init, reset_integrations): OpenTelemetryIntegration.setup_once = MagicMock() sentry_init( _experiments={ "otel_powered_performance": True, - } + }, ) OpenTelemetryIntegration.setup_once.assert_called_once() -def test_integration_not_enabled_if_option_is_off(sentry_init): +def test_integration_not_enabled_if_option_is_off(sentry_init, reset_integrations): OpenTelemetryIntegration.setup_once = MagicMock() sentry_init( _experiments={ "otel_powered_performance": False, - } + }, ) OpenTelemetryIntegration.setup_once.assert_not_called() -def test_integration_not_enabled_if_option_is_missing(sentry_init): +def test_integration_not_enabled_if_option_is_missing(sentry_init, reset_integrations): OpenTelemetryIntegration.setup_once = MagicMock() sentry_init() OpenTelemetryIntegration.setup_once.assert_not_called() @needs_potel -def test_instrumentors_applied(sentry_init): +def test_instrumentors_applied(sentry_init, reset_integrations): sentry_init( _experiments={ "otel_powered_performance": True, @@ -58,11 +58,16 @@ def test_instrumentors_applied(sentry_init): @needs_potel -def test_post_patching(sentry_init): +def test_post_patching(sentry_init, reset_integrations): + from flask import Flask + sentry_init( _experiments={ "otel_powered_performance": True, }, + debug=True, ) - assert False + app = Flask(__name__) + assert hasattr(app, "_is_instrumented_by_opentelemetry") + assert app._is_instrumented_by_opentelemetry is True diff --git a/tox.ini b/tox.ini index 6f0c244a12..402e4b6582 100644 --- a/tox.ini +++ b/tox.ini @@ -493,6 +493,7 @@ deps = # OpenTelemetry Experimental (POTel) potel: -e .[opentelemetry-experimental] + potel: Flask<3 # pure_eval pure_eval: pure_eval From 7a8155bbbf6014c39be0a3079d49f5d34eeaa7ca Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Mon, 10 Jun 2024 16:42:40 +0200 Subject: [PATCH 10/13] fix tests --- .../opentelemetry/test_experimental.py | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/tests/integrations/opentelemetry/test_experimental.py b/tests/integrations/opentelemetry/test_experimental.py index 3ce938d151..a0452e424c 100644 --- a/tests/integrations/opentelemetry/test_experimental.py +++ b/tests/integrations/opentelemetry/test_experimental.py @@ -1,9 +1,7 @@ -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import pytest -from sentry_sdk.integrations.opentelemetry.integration import OpenTelemetryIntegration - try: import opentelemetry.instrumentation.asyncio # noqa: F401 @@ -21,29 +19,44 @@ def test_integration_enabled_if_option_is_on(sentry_init, reset_integrations): - OpenTelemetryIntegration.setup_once = MagicMock() - sentry_init( - _experiments={ - "otel_powered_performance": True, - }, - ) - OpenTelemetryIntegration.setup_once.assert_called_once() + mocked_setup_once = MagicMock() + + with patch( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration.setup_once", + mocked_setup_once, + ): + sentry_init( + _experiments={ + "otel_powered_performance": True, + }, + ) + mocked_setup_once.assert_called_once() def test_integration_not_enabled_if_option_is_off(sentry_init, reset_integrations): - OpenTelemetryIntegration.setup_once = MagicMock() - sentry_init( - _experiments={ - "otel_powered_performance": False, - }, - ) - OpenTelemetryIntegration.setup_once.assert_not_called() + mocked_setup_once = MagicMock() + + with patch( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration.setup_once", + mocked_setup_once, + ): + sentry_init( + _experiments={ + "otel_powered_performance": False, + }, + ) + mocked_setup_once.assert_not_called() def test_integration_not_enabled_if_option_is_missing(sentry_init, reset_integrations): - OpenTelemetryIntegration.setup_once = MagicMock() - sentry_init() - OpenTelemetryIntegration.setup_once.assert_not_called() + mocked_setup_once = MagicMock() + + with patch( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration.setup_once", + mocked_setup_once, + ): + sentry_init() + mocked_setup_once.assert_not_called() @needs_potel @@ -65,7 +78,6 @@ def test_post_patching(sentry_init, reset_integrations): _experiments={ "otel_powered_performance": True, }, - debug=True, ) app = Flask(__name__) From 6c76f3e5133de22428fe064938f603c8751d0a14 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 13 Jun 2024 15:53:37 +0200 Subject: [PATCH 11/13] more potel tests --- .../integrations/opentelemetry/integration.py | 6 ++- .../opentelemetry/test_experimental.py | 54 +++++++++++++++---- tox.ini | 1 + 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/sentry_sdk/integrations/opentelemetry/integration.py b/sentry_sdk/integrations/opentelemetry/integration.py index dfb847dd5b..5554afb900 100644 --- a/sentry_sdk/integrations/opentelemetry/integration.py +++ b/sentry_sdk/integrations/opentelemetry/integration.py @@ -29,7 +29,7 @@ # instrumentation took place. "fastapi": "fastapi.FastAPI", "flask": "flask.Flask", - # XXX Add a mapping for all instrumentors that instrument a class + # XXX Add a mapping for all instrumentors that patch by replacing a class } @@ -58,6 +58,10 @@ def setup_once(): except Exception: logger.exception("[OTel] Failed to auto-initialize OpenTelemetry") + # XXX: Consider whether this is ok to keep and make default. + # The alternative is asking folks to follow specific import order for + # some integrations (sentry_sdk.init before you even import Flask, for + # instance). try: _patch_remaining_classes(original_classes) except Exception: diff --git a/tests/integrations/opentelemetry/test_experimental.py b/tests/integrations/opentelemetry/test_experimental.py index a0452e424c..17f8e90a0f 100644 --- a/tests/integrations/opentelemetry/test_experimental.py +++ b/tests/integrations/opentelemetry/test_experimental.py @@ -1,6 +1,9 @@ from unittest.mock import MagicMock, patch import pytest +from flask import Flask +from fastapi import FastAPI + try: import opentelemetry.instrumentation.asyncio # noqa: F401 @@ -18,6 +21,7 @@ ) +@pytest.mark.forked def test_integration_enabled_if_option_is_on(sentry_init, reset_integrations): mocked_setup_once = MagicMock() @@ -33,6 +37,7 @@ def test_integration_enabled_if_option_is_on(sentry_init, reset_integrations): mocked_setup_once.assert_called_once() +@pytest.mark.forked def test_integration_not_enabled_if_option_is_off(sentry_init, reset_integrations): mocked_setup_once = MagicMock() @@ -48,6 +53,7 @@ def test_integration_not_enabled_if_option_is_off(sentry_init, reset_integration mocked_setup_once.assert_not_called() +@pytest.mark.forked def test_integration_not_enabled_if_option_is_missing(sentry_init, reset_integrations): mocked_setup_once = MagicMock() @@ -59,20 +65,39 @@ def test_integration_not_enabled_if_option_is_missing(sentry_init, reset_integra mocked_setup_once.assert_not_called() +@pytest.mark.forked @needs_potel def test_instrumentors_applied(sentry_init, reset_integrations): - sentry_init( - _experiments={ - "otel_powered_performance": True, - }, - ) + flask_instrument_mock = MagicMock() + fastapi_instrument_mock = MagicMock() - assert False + with patch( + "opentelemetry.instrumentation.flask.FlaskInstrumentor.instrument", + flask_instrument_mock, + ): + with patch( + "opentelemetry.instrumentation.fastapi.FastAPIInstrumentor.instrument", + fastapi_instrument_mock, + ): + sentry_init( + _experiments={ + "otel_powered_performance": True, + }, + ) + + flask_instrument_mock.assert_called_once() + fastapi_instrument_mock.assert_called_once() +@pytest.mark.forked @needs_potel def test_post_patching(sentry_init, reset_integrations): - from flask import Flask + assert not hasattr( + Flask(__name__), "_is_instrumented_by_opentelemetry" + ), "Flask is not patched at the start" + assert not hasattr( + FastAPI(), "_is_instrumented_by_opentelemetry" + ), "FastAPI is not patched at the start" sentry_init( _experiments={ @@ -80,6 +105,15 @@ def test_post_patching(sentry_init, reset_integrations): }, ) - app = Flask(__name__) - assert hasattr(app, "_is_instrumented_by_opentelemetry") - assert app._is_instrumented_by_opentelemetry is True + flask = Flask(__name__) + fastapi = FastAPI() + + assert hasattr( + flask, "_is_instrumented_by_opentelemetry" + ), "Flask has been patched after init()" + assert flask._is_instrumented_by_opentelemetry is True + + assert hasattr( + fastapi, "_is_instrumented_by_opentelemetry" + ), "FastAPI has been patched after init()" + assert fastapi._is_instrumented_by_opentelemetry is True diff --git a/tox.ini b/tox.ini index 402e4b6582..3bc336b39c 100644 --- a/tox.ini +++ b/tox.ini @@ -494,6 +494,7 @@ deps = # OpenTelemetry Experimental (POTel) potel: -e .[opentelemetry-experimental] potel: Flask<3 + potel: fastapi # pure_eval pure_eval: pure_eval From a1c79c9659c186cdeda9f648bd0750811aaf7217 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 13 Jun 2024 16:10:29 +0200 Subject: [PATCH 12/13] no flask, fastapi with regular otel setup --- tests/integrations/opentelemetry/test_experimental.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/integrations/opentelemetry/test_experimental.py b/tests/integrations/opentelemetry/test_experimental.py index 17f8e90a0f..856858c599 100644 --- a/tests/integrations/opentelemetry/test_experimental.py +++ b/tests/integrations/opentelemetry/test_experimental.py @@ -1,8 +1,12 @@ from unittest.mock import MagicMock, patch import pytest -from flask import Flask -from fastapi import FastAPI + +try: + from flask import Flask + from fastapi import FastAPI +except ImportError: + pass try: From e328f5bd92dafff8f15161116e011c6b2ae432e3 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 13 Jun 2024 16:14:55 +0200 Subject: [PATCH 13/13] fix py versions --- .github/workflows/test-integrations-miscellaneous.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-integrations-miscellaneous.yml b/.github/workflows/test-integrations-miscellaneous.yml index 7a0f650b1a..e49e6e28f9 100644 --- a/.github/workflows/test-integrations-miscellaneous.yml +++ b/.github/workflows/test-integrations-miscellaneous.yml @@ -77,7 +77,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6","3.7","3.8","3.9","3.11","3.12"] + python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 diff --git a/tox.ini b/tox.ini index 3bc336b39c..e73e22d979 100644 --- a/tox.ini +++ b/tox.ini @@ -170,7 +170,7 @@ envlist = # OpenTelemetry Experimental (POTel) # XXX add 3.12 when officially supported - {py3.7,py3.9,py3.11}-potel + {py3.8,py3.9,py3.10,py3.11}-potel # pure_eval {py3.6,py3.11,py3.12}-pure_eval