diff --git a/pyproject.toml b/pyproject.toml index ec95f822..3d489422 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ optional-dependencies = { test-tools = [ "pyfakefs (>=5,<6)", "pytest-django (>=4,<5)", ], common-core = [ + "flagsmith-common[otel]", "django (>4,<6)", "django-health-check", "djangorestframework-recursive", @@ -16,10 +17,6 @@ optional-dependencies = { test-tools = [ "drf-writable-nested", "environs (<16)", "gunicorn (>=19.1)", - "inflection", - "opentelemetry-api (>=1.25,<2)", - "opentelemetry-sdk (>=1.25,<2)", - "opentelemetry-exporter-otlp-proto-http (>=1.25,<2)", "opentelemetry-instrumentation-django (>=0.46b0,<1)", "opentelemetry-instrumentation-psycopg2 (>=0.46b0,<1)", "opentelemetry-instrumentation-redis (>=0.46b0,<1)", @@ -27,8 +24,13 @@ optional-dependencies = { test-tools = [ "prometheus-client (>=0.0.16)", "psycopg2-binary (>=2.9,<3)", "requests", - "sentry-sdk (>=2.0.0,<3.0.0)", "simplejson (>=3,<4)", +], otel = [ + "inflection", + "opentelemetry-api (>=1.25,<2)", + "opentelemetry-sdk (>=1.25,<2)", + "opentelemetry-exporter-otlp-proto-http (>=1.25,<2)", + "sentry-sdk (>=2.0.0,<3.0.0)", "structlog (>=24.4,<26)", "typing_extensions", ], task-processor = [ diff --git a/src/common/core/otel.py b/src/common/core/otel.py index 50daba0b..414182eb 100644 --- a/src/common/core/otel.py +++ b/src/common/core/otel.py @@ -17,9 +17,6 @@ from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( OTLPSpanExporter, ) -from opentelemetry.instrumentation.django import DjangoInstrumentor -from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor -from opentelemetry.instrumentation.redis import RedisInstrumentor from opentelemetry.propagate import set_global_textmap from opentelemetry.propagators.composite import CompositePropagator from opentelemetry.propagators.textmap import TextMapPropagator @@ -195,6 +192,10 @@ def setup_tracing( (e.g. ``"health/liveness,health/readiness"``). If not provided, falls back to the ``OTEL_PYTHON_DJANGO_EXCLUDED_URLS`` env var. """ + from opentelemetry.instrumentation.django import DjangoInstrumentor + from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor + from opentelemetry.instrumentation.redis import RedisInstrumentor + trace.set_tracer_provider(tracer_provider) propagator: TextMapPropagator = CompositePropagator( diff --git a/tests/unit/common/core/test_otel.py b/tests/unit/common/core/test_otel.py index 69152370..afd8a0f0 100644 --- a/tests/unit/common/core/test_otel.py +++ b/tests/unit/common/core/test_otel.py @@ -210,16 +210,20 @@ def test_setup_tracing__called__instruments_and_uninstruments_all_libraries( mocker: pytest_mock.MockerFixture, ) -> None: # Given - mocker.patch("common.core.otel.DjangoInstrumentor.instrument") - mocker.patch("common.core.otel.DjangoInstrumentor.uninstrument") + mocker.patch("opentelemetry.instrumentation.django.DjangoInstrumentor.instrument") + mocker.patch("opentelemetry.instrumentation.django.DjangoInstrumentor.uninstrument") psycopg2_instrument = mocker.patch( - "common.core.otel.Psycopg2Instrumentor.instrument" + "opentelemetry.instrumentation.psycopg2.Psycopg2Instrumentor.instrument" ) psycopg2_uninstrument = mocker.patch( - "common.core.otel.Psycopg2Instrumentor.uninstrument" + "opentelemetry.instrumentation.psycopg2.Psycopg2Instrumentor.uninstrument" + ) + redis_instrument = mocker.patch( + "opentelemetry.instrumentation.redis.RedisInstrumentor.instrument" + ) + redis_uninstrument = mocker.patch( + "opentelemetry.instrumentation.redis.RedisInstrumentor.uninstrument" ) - redis_instrument = mocker.patch("common.core.otel.RedisInstrumentor.instrument") - redis_uninstrument = mocker.patch("common.core.otel.RedisInstrumentor.uninstrument") provider = TracerProvider() # When @@ -232,3 +236,13 @@ def test_setup_tracing__called__instruments_and_uninstruments_all_libraries( # Then — uninstrumented on exit psycopg2_uninstrument.assert_called_once() redis_uninstrument.assert_called_once() + + +def test_otel_module__imported__no_module_level_instrumentor_references() -> None: + # Given / When + import common.core.otel as otel_module + + # Then + assert not hasattr(otel_module, "DjangoInstrumentor") + assert not hasattr(otel_module, "Psycopg2Instrumentor") + assert not hasattr(otel_module, "RedisInstrumentor") diff --git a/uv.lock b/uv.lock index 371e1289..f362922d 100644 --- a/uv.lock +++ b/uv.lock @@ -483,6 +483,15 @@ flagsmith-schemas = [ { name = "simplejson" }, { name = "typing-extensions" }, ] +otel = [ + { name = "inflection" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "sentry-sdk" }, + { name = "structlog" }, + { name = "typing-extensions" }, +] task-processor = [ { name = "backoff" }, { name = "django" }, @@ -529,16 +538,17 @@ requires-dist = [ { name = "drf-spectacular", marker = "extra == 'common-core'", specifier = ">=0.28.0,<1" }, { name = "drf-writable-nested", marker = "extra == 'common-core'" }, { name = "environs", marker = "extra == 'common-core'", specifier = "<16" }, + { name = "flagsmith-common", extras = ["otel"], marker = "extra == 'common-core'" }, { name = "flagsmith-flag-engine", marker = "extra == 'flagsmith-schemas'", specifier = ">6" }, { name = "gunicorn", marker = "extra == 'common-core'", specifier = ">=19.1" }, - { name = "inflection", marker = "extra == 'common-core'" }, - { name = "opentelemetry-api", marker = "extra == 'common-core'", specifier = ">=1.25,<2" }, + { name = "inflection", marker = "extra == 'otel'" }, + { name = "opentelemetry-api", marker = "extra == 'otel'", specifier = ">=1.25,<2" }, { name = "opentelemetry-api", marker = "extra == 'task-processor'", specifier = ">=1.25,<2" }, - { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'common-core'", specifier = ">=1.25,<2" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'otel'", specifier = ">=1.25,<2" }, { name = "opentelemetry-instrumentation-django", marker = "extra == 'common-core'", specifier = ">=0.46b0,<1" }, { name = "opentelemetry-instrumentation-psycopg2", marker = "extra == 'common-core'", specifier = ">=0.46b0,<1" }, { name = "opentelemetry-instrumentation-redis", marker = "extra == 'common-core'", specifier = ">=0.46b0,<1" }, - { name = "opentelemetry-sdk", marker = "extra == 'common-core'", specifier = ">=1.25,<2" }, + { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.25,<2" }, { name = "prometheus-client", marker = "extra == 'common-core'", specifier = ">=0.0.16" }, { name = "prometheus-client", marker = "extra == 'task-processor'", specifier = ">=0.0.16" }, { name = "psycopg2-binary", marker = "extra == 'common-core'", specifier = ">=2.9,<3" }, @@ -546,14 +556,14 @@ requires-dist = [ { name = "pytest-django", marker = "extra == 'test-tools'", specifier = ">=4,<5" }, { name = "redis", marker = "extra == 'common-core'", specifier = ">=5,<6" }, { name = "requests", marker = "extra == 'common-core'" }, - { name = "sentry-sdk", marker = "extra == 'common-core'", specifier = ">=2.0.0,<3.0.0" }, + { name = "sentry-sdk", marker = "extra == 'otel'", specifier = ">=2.0.0,<3.0.0" }, { name = "simplejson", marker = "extra == 'common-core'", specifier = ">=3,<4" }, { name = "simplejson", marker = "extra == 'flagsmith-schemas'" }, - { name = "structlog", marker = "extra == 'common-core'", specifier = ">=24.4,<26" }, - { name = "typing-extensions", marker = "extra == 'common-core'" }, + { name = "structlog", marker = "extra == 'otel'", specifier = ">=24.4,<26" }, { name = "typing-extensions", marker = "extra == 'flagsmith-schemas'" }, + { name = "typing-extensions", marker = "extra == 'otel'" }, ] -provides-extras = ["test-tools", "common-core", "task-processor", "flagsmith-schemas"] +provides-extras = ["test-tools", "common-core", "otel", "task-processor", "flagsmith-schemas"] [package.metadata.requires-dev] dev = [