Skip to content

Commit dda34f0

Browse files
committed
update unit tests
1 parent 4dd75fd commit dda34f0

File tree

2 files changed

+232
-7
lines changed

2 files changed

+232
-7
lines changed

instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ def response_hook(span, request_obj, response):
9393
from __future__ import annotations
9494

9595
import functools
96-
import os
9796
import types
9897
from timeit import default_timer
9998
from typing import Any, Callable, Collection, Mapping, Optional
@@ -157,6 +156,7 @@ def response_hook(span, request_obj, response):
157156
ExcludeList,
158157
SanitizeValue,
159158
detect_synthetic_user_agent,
159+
get_custom_headers,
160160
get_excluded_urls,
161161
normalise_request_header_name,
162162
normalise_response_header_name,
@@ -213,8 +213,7 @@ def _get_custom_header_attributes(
213213
If None or empty, no headers will be captured.
214214
sensitive_headers: List of header regexes whose values should be sanitized
215215
(redacted). If None, no sanitization is applied.
216-
normalize_function: Function to normalize header names
217-
(e.g., normalise_request_header_name or normalise_response_header_name).
216+
normalize_function: Function to normalize header names.
218217
219218
Returns:
220219
Dictionary of normalized header attribute names to their values
@@ -557,13 +556,13 @@ def _instrument(self, **kwargs: Any):
557556
else parse_excluded_urls(excluded_urls)
558557
),
559558
sem_conv_opt_in_mode=semconv_opt_in_mode,
560-
captured_request_headers=os.environ.get(
559+
captured_request_headers=get_custom_headers(
561560
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST
562561
),
563-
captured_response_headers=os.environ.get(
562+
captured_response_headers=get_custom_headers(
564563
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE
565564
),
566-
sensitive_headers=os.environ.get(
565+
sensitive_headers=get_custom_headers(
567566
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
568567
),
569568
)

instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py

Lines changed: 227 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@
6969
from opentelemetry.test.mock_textmap import MockTextMapPropagator
7070
from opentelemetry.test.test_base import TestBase
7171
from opentelemetry.trace import StatusCode
72-
from opentelemetry.util.http import get_excluded_urls
72+
from opentelemetry.util.http import (
73+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST,
74+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE,
75+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
76+
get_excluded_urls,
77+
)
7378

7479

7580
class TransportMock:
@@ -717,6 +722,227 @@ def test_if_headers_equals_none(self):
717722
self.assertEqual(result.text, "Hello!")
718723
self.assert_span()
719724

725+
@mock.patch.dict(
726+
"os.environ",
727+
{
728+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST: "X-Custom-Header,X-Another-Header",
729+
},
730+
)
731+
def test_custom_request_headers_captured(self):
732+
"""Test that specified request headers are captured as span attributes."""
733+
RequestsInstrumentor().uninstrument()
734+
RequestsInstrumentor().instrument()
735+
736+
headers = {
737+
"X-Custom-Header": "custom-value",
738+
"X-Another-Header": "another-value",
739+
"X-Excluded-Header": "excluded-value",
740+
}
741+
httpretty.register_uri(httpretty.GET, self.URL, body="Hello!")
742+
result = requests.get(self.URL, headers=headers, timeout=5)
743+
self.assertEqual(result.text, "Hello!")
744+
745+
span = self.assert_span()
746+
self.assertEqual(
747+
span.attributes["http.request.header.x_custom_header"],
748+
("custom-value",),
749+
)
750+
self.assertEqual(
751+
span.attributes["http.request.header.x_another_header"],
752+
("another-value",),
753+
)
754+
self.assertNotIn("http.request.x_excluded_header", span.attributes)
755+
756+
@mock.patch.dict(
757+
"os.environ",
758+
{
759+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE: "X-Custom-Header,X-Another-Header",
760+
},
761+
)
762+
def test_custom_response_headers_captured(self):
763+
"""Test that specified request headers are captured as span attributes."""
764+
RequestsInstrumentor().uninstrument()
765+
RequestsInstrumentor().instrument()
766+
767+
headers = {
768+
"X-Custom-Header": "custom-value",
769+
"X-Another-Header": "another-value",
770+
"X-Excluded-Header": "excluded-value",
771+
}
772+
httpretty.register_uri(
773+
httpretty.GET, self.URL, body="Hello!", adding_headers=headers
774+
)
775+
result = requests.get(self.URL, timeout=5)
776+
self.assertEqual(result.text, "Hello!")
777+
778+
span = self.assert_span()
779+
self.assertEqual(
780+
span.attributes["http.response.header.x_custom_header"],
781+
("custom-value",),
782+
)
783+
self.assertEqual(
784+
span.attributes["http.response.header.x_another_header"],
785+
("another-value",),
786+
)
787+
self.assertNotIn("http.response.x_excluded_header", span.attributes)
788+
789+
@mock.patch.dict("os.environ", {})
790+
def test_custom_headers_not_captured_when_not_configured(self):
791+
"""Test that headers are not captured when env vars are not set."""
792+
RequestsInstrumentor().uninstrument()
793+
RequestsInstrumentor().instrument()
794+
headers = {"X-Request-Header": "request-value"}
795+
httpretty.register_uri(
796+
httpretty.GET,
797+
self.URL,
798+
body="Hello!",
799+
adding_headers={"X-Response-Header": "response-value"},
800+
)
801+
result = requests.get(self.URL, headers=headers, timeout=5)
802+
self.assertEqual(result.text, "Hello!")
803+
804+
span = self.assert_span()
805+
self.assertNotIn(
806+
"http.request.header.x_request_header", span.attributes
807+
)
808+
self.assertNotIn(
809+
"http.response.header.x_response_header", span.attributes
810+
)
811+
812+
@mock.patch.dict(
813+
"os.environ",
814+
{
815+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE: "Set-Cookie,X-Secret",
816+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST: "Authorization,X-Api-Key",
817+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS: "Authorization,X-Api-Key,Set-Cookie,X-Secret",
818+
},
819+
)
820+
def test_sensitive_headers_sanitized(self):
821+
"""Test that sensitive header values are redacted."""
822+
RequestsInstrumentor().uninstrument()
823+
RequestsInstrumentor().instrument()
824+
825+
request_headers = {
826+
"Authorization": "Bearer secret-token",
827+
"X-Api-Key": "secret-key",
828+
}
829+
response_headers = {
830+
"Set-Cookie": "session=abc123",
831+
"X-Secret": "secret",
832+
}
833+
httpretty.register_uri(
834+
httpretty.GET,
835+
self.URL,
836+
body="Hello!",
837+
adding_headers=response_headers,
838+
)
839+
result = requests.get(self.URL, headers=request_headers, timeout=5)
840+
self.assertEqual(result.text, "Hello!")
841+
842+
span = self.assert_span()
843+
self.assertEqual(
844+
span.attributes["http.request.header.authorization"],
845+
("[REDACTED]",),
846+
)
847+
self.assertEqual(
848+
span.attributes["http.request.header.x_api_key"],
849+
("[REDACTED]",),
850+
)
851+
self.assertEqual(
852+
span.attributes["http.response.header.set_cookie"],
853+
("[REDACTED]",),
854+
)
855+
self.assertEqual(
856+
span.attributes["http.response.header.x_secret"],
857+
("[REDACTED]",),
858+
)
859+
860+
@mock.patch.dict(
861+
"os.environ",
862+
{
863+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE: "X-Custom-Response-.*",
864+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST: "X-Custom-Request-.*",
865+
},
866+
)
867+
def test_custom_headers_with_regex(self):
868+
"""Test that header capture works with regex patterns."""
869+
RequestsInstrumentor().uninstrument()
870+
RequestsInstrumentor().instrument()
871+
request_headers = {
872+
"X-Custom-Request-One": "value-one",
873+
"X-Custom-Request-Two": "value-two",
874+
"X-Other-Request-Header": "other-value",
875+
}
876+
response_headers = {
877+
"X-Custom-Response-A": "value-A",
878+
"X-Custom-Response-B": "value-B",
879+
"X-Other-Response-Header": "other-value",
880+
}
881+
httpretty.register_uri(
882+
httpretty.GET,
883+
self.URL,
884+
body="Hello!",
885+
adding_headers=response_headers,
886+
)
887+
result = requests.get(self.URL, headers=request_headers, timeout=5)
888+
self.assertEqual(result.text, "Hello!")
889+
890+
span = self.assert_span()
891+
self.assertEqual(
892+
span.attributes["http.request.header.x_custom_request_one"],
893+
("value-one",),
894+
)
895+
self.assertEqual(
896+
span.attributes["http.request.header.x_custom_request_two"],
897+
("value-two",),
898+
)
899+
self.assertNotIn(
900+
"http.request.header.x_other_request_header", span.attributes
901+
)
902+
self.assertEqual(
903+
span.attributes["http.response.header.x_custom_response_a"],
904+
("value-A",),
905+
)
906+
self.assertEqual(
907+
span.attributes["http.response.header.x_custom_response_b"],
908+
("value-B",),
909+
)
910+
self.assertNotIn(
911+
"http.response.header.x_other_response_header", span.attributes
912+
)
913+
914+
@mock.patch.dict(
915+
"os.environ",
916+
{
917+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE: "x-response-header",
918+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST: "x-request-header",
919+
},
920+
)
921+
def test_custom_headers_case_insensitive(self):
922+
"""Test that header capture is case-insensitive."""
923+
RequestsInstrumentor().uninstrument()
924+
RequestsInstrumentor().instrument()
925+
request_headers = {"X-ReQuESt-HeaDER": "custom-value"}
926+
response_headers = {"X-ReSPoNse-HeaDER": "custom-value"}
927+
httpretty.register_uri(
928+
httpretty.GET,
929+
self.URL,
930+
body="Hello!",
931+
adding_headers=response_headers,
932+
)
933+
result = requests.get(self.URL, headers=request_headers, timeout=5)
934+
self.assertEqual(result.text, "Hello!")
935+
936+
span = self.assert_span()
937+
self.assertEqual(
938+
span.attributes["http.request.header.x_request_header"],
939+
("custom-value",),
940+
)
941+
self.assertEqual(
942+
span.attributes["http.response.header.x_response_header"],
943+
("custom-value",),
944+
)
945+
720946

721947
class TestRequestsIntegrationPreparedRequest(
722948
RequestsIntegrationTestBase, TestBase

0 commit comments

Comments
 (0)