diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a345d939a..a43ec36b00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -417,6 +417,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#1879](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1879)) - Add optional distro and configurator selection for auto-instrumentation ([#1823](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1823)) +- `opentelemetry-instrumentation-django` - Add option to add Opentelemetry middleware at specific position in middleware chain + ([#2912]https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2912) ### Added diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py index d37c45993c..e5851a17c2 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py @@ -285,6 +285,30 @@ def _get_django_middleware_setting() -> str: return "MIDDLEWARE" +def _get_django_otel_middleware_position( + middleware_length, default_middleware_position=0 +): + otel_position = environ.get("OTEL_PYTHON_DJANGO_MIDDLEWARE_POSITION") + try: + middleware_position = int(otel_position) + except (ValueError, TypeError): + _logger.debug( + "Invalid OTEL_PYTHON_DJANGO_MIDDLEWARE_POSITION value: (%s). Using default position: %d.", + otel_position, + default_middleware_position, + ) + middleware_position = default_middleware_position + + if middleware_position < 0 or middleware_position > middleware_length: + _logger.debug( + "Middleware position %d is out of range (0-%d). Using 0 as the position", + middleware_position, + middleware_length, + ) + middleware_position = 0 + return middleware_position + + class DjangoInstrumentor(BaseInstrumentor): """An instrumentor for Django @@ -388,10 +412,18 @@ def _instrument(self, **kwargs): is_sql_commentor_enabled = kwargs.pop("is_sql_commentor_enabled", None) + middleware_position = _get_django_otel_middleware_position( + len(settings_middleware), kwargs.pop("middleware_position", 0) + ) + if is_sql_commentor_enabled: - settings_middleware.insert(0, self._sql_commenter_middleware) + settings_middleware.insert( + middleware_position, self._sql_commenter_middleware + ) - settings_middleware.insert(0, self._opentelemetry_middleware) + settings_middleware.insert( + middleware_position, self._opentelemetry_middleware + ) setattr(settings, _middleware_setting, settings_middleware) diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 85ebbd747f..1c85935892 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -157,6 +157,46 @@ def tearDownClass(cls): super().tearDownClass() conf.settings = conf.LazySettings() + def test_middleware_added_at_position(self): + _django_instrumentor.uninstrument() + if DJANGO_2_0: + middleware = conf.settings.MIDDLEWARE + else: + middleware = conf.settings.MIDDLEWARE_CLASSES + # adding two dummy middlewares + temprory_middelware = "django.utils.deprecation.MiddlewareMixin" + middleware.append(temprory_middelware) + middleware.append(temprory_middelware) + + middleware_position = 1 + _django_instrumentor.instrument( + middleware_position=middleware_position + ) + self.assertEqual( + middleware[middleware_position], + "opentelemetry.instrumentation.django.middleware.otel_middleware._DjangoMiddleware", + ) + + def test_middleware_added_at_position_if_wrong_position(self): + _django_instrumentor.uninstrument() + if DJANGO_2_0: + middleware = conf.settings.MIDDLEWARE + else: + middleware = conf.settings.MIDDLEWARE_CLASSES + # adding middleware + temprory_middelware = "django.utils.deprecation.MiddlewareMixin" + middleware.append(temprory_middelware) + middleware_position = ( + 756 # wrong position out of bound of middleware length + ) + _django_instrumentor.instrument( + middleware_position=middleware_position + ) + self.assertEqual( + middleware[0], + "opentelemetry.instrumentation.django.middleware.otel_middleware._DjangoMiddleware", + ) + def test_templated_route_get(self): Client().get("/route/2020/template/") diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_sqlcommenter.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_sqlcommenter.py index f9b8ed5233..eec02d7a54 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_sqlcommenter.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_sqlcommenter.py @@ -72,6 +72,37 @@ def test_middleware_added(self, sqlcommenter_middleware): in middleware ) + @patch( + "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + ) + def test_middleware_added_at_position(self, sqlcommenter_middleware): + _django_instrumentor.uninstrument() + if DJANGO_2_0: + middleware = conf.settings.MIDDLEWARE + else: + middleware = conf.settings.MIDDLEWARE_CLASSES + + # adding two dummy middlewares + temprory_middelware = "django.utils.deprecation.MiddlewareMixin" + middleware.append(temprory_middelware) + middleware.append(temprory_middelware) + + middleware_position = 1 + _django_instrumentor.instrument( + is_sql_commentor_enabled=True, + middleware_position=middleware_position, + ) + instance = sqlcommenter_middleware.return_value + instance.get_response = HttpResponse() + self.assertEqual( + middleware[middleware_position], + "opentelemetry.instrumentation.django.middleware.otel_middleware._DjangoMiddleware", + ) + self.assertEqual( + middleware[middleware_position + 1], + "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter", + ) + @patch( "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware._get_opentelemetry_values" )