diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f252e8290..87171a0796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added +- `opentelemetry-instrumentation-requests` Added support for post-injection-hook. + ([#3657](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3657)) + ## Version 1.36.0/0.57b0 (2025-07-29) ### Fixed diff --git a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py index 7cfc3a4fee..5f46d9b3af 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py @@ -86,6 +86,31 @@ def response_hook(span, request_obj, response): will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. +Post Injection Hook +******************* +To customize headers after the current context is injected, you can provide a function that will be called with +both the headers and request object. + +For example, to avoid propagating `tracestate`/`baggage` to non-allowed hosts while still creating a span: + +.. code:: python + + allowed_hosts = frozenset(["my-company.com", "my-cloud-provider.com"]) + + def post_injection_hook(headers, request): + # Parse the domain from the request URL + parsed_url = urlparse(request.url) + request_host = parsed_url.hostname + + # Remove headers if the host is NOT in the allowed list + if request_host not in allowed_hosts: + headers.pop("tracestate", None) + headers.pop("baggage", None) + + RequestsInstrumentor().instrument( + post_injection_hook=post_injection_hook + ) + API --- """ @@ -156,6 +181,9 @@ def response_hook(span, request_obj, response): _RequestHookT = Optional[Callable[[Span, PreparedRequest], None]] _ResponseHookT = Optional[Callable[[Span, PreparedRequest, Response], None]] +_PostInjectionHookT = Optional[ + Callable[[CaseInsensitiveDict[str], PreparedRequest], None] +] def _set_http_status_code_attribute( @@ -194,6 +222,7 @@ def _instrument( response_hook: _ResponseHookT = None, excluded_urls: ExcludeList | None = None, sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, + post_injection_hook: _PostInjectionHookT = None, ): """Enables tracing of all requests calls that go through :code:`requests.session.Session.request` (this includes @@ -300,6 +329,9 @@ def get_or_create_headers(): headers = get_or_create_headers() inject(headers) + if callable(post_injection_hook): + post_injection_hook(headers, request) + with suppress_http_instrumentation(): start_time = default_timer() try: @@ -450,6 +482,7 @@ def _instrument(self, **kwargs: Any): duration_histogram_boundaries = kwargs.get( "duration_histogram_boundaries" ) + post_injection_hook = kwargs.get("post_injection_hook") meter = get_meter( __name__, __version__, @@ -486,6 +519,7 @@ def _instrument(self, **kwargs: Any): else parse_excluded_urls(excluded_urls) ), sem_conv_opt_in_mode=semconv_opt_in_mode, + post_injection_hook=post_injection_hook, ) def _uninstrument(self, **kwargs: Any): diff --git a/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py index ac3d41294b..93a66ae831 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py +++ b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py @@ -322,6 +322,21 @@ def response_hook(span, request_obj, response): self.assertEqual(span.name, "name set from hook") self.assertEqual(span.attributes["response_hook_attr"], "value") + def test_post_injection_hook(self): + def post_injection_hook(headers, request): + headers.pop("traceparent", None) + + RequestsInstrumentor().uninstrument() + RequestsInstrumentor().instrument( + post_injection_hook=post_injection_hook + ) + + self.perform_request(self.URL) + + span = self.assert_span() + self.assertEqual(span.name, "GET") + self.assertNotIn("traceparent", httpretty.last_request().headers) + def test_excluded_urls_explicit(self): url_404 = "http://mock/status/404" httpretty.register_uri(