@@ -530,6 +530,7 @@ def create_transport(
530
530
tracer_provider : typing .Optional ["TracerProvider" ] = None ,
531
531
request_hook : typing .Optional ["RequestHook" ] = None ,
532
532
response_hook : typing .Optional ["ResponseHook" ] = None ,
533
+ ** kwargs ,
533
534
):
534
535
pass
535
536
@@ -539,6 +540,7 @@ def create_client(
539
540
transport : typing .Union [
540
541
SyncOpenTelemetryTransport , AsyncOpenTelemetryTransport , None
541
542
] = None ,
543
+ ** kwargs ,
542
544
):
543
545
pass
544
546
@@ -643,22 +645,70 @@ def test_not_recording_not_set_attribute_in_exception_new_semconv(
643
645
self .assertFalse (mock_span .set_attribute .called )
644
646
self .assertFalse (mock_span .set_status .called )
645
647
648
+ @respx .mock
649
+ def test_client_mounts_with_instrumented_transport (self ):
650
+ https_url = "https://mock/status/200"
651
+ respx .get (https_url ).mock (httpx .Response (200 ))
652
+ proxy_mounts = {
653
+ "http://" : self .create_transport (
654
+ proxy = httpx .Proxy ("http://localhost:8080" )
655
+ ),
656
+ "https://" : self .create_transport (
657
+ proxy = httpx .Proxy ("http://localhost:8443" )
658
+ ),
659
+ }
660
+ client1 = self .create_client (mounts = proxy_mounts )
661
+ client2 = self .create_client (mounts = proxy_mounts )
662
+ self .perform_request (self .URL , client = client1 )
663
+ self .perform_request (https_url , client = client2 )
664
+ spans = self .assert_span (num_spans = 2 )
665
+ self .assertEqual (
666
+ spans [0 ].attributes [SpanAttributes .HTTP_URL ], self .URL
667
+ )
668
+ self .assertEqual (
669
+ spans [1 ].attributes [SpanAttributes .HTTP_URL ], https_url
670
+ )
671
+
646
672
class BaseInstrumentorTest (BaseTest , metaclass = abc .ABCMeta ):
647
673
@abc .abstractmethod
648
674
def create_client (
649
675
self ,
650
676
transport : typing .Union [
651
677
SyncOpenTelemetryTransport , AsyncOpenTelemetryTransport , None
652
678
] = None ,
679
+ ** kwargs ,
653
680
):
654
681
pass
655
682
683
+ @abc .abstractmethod
684
+ def create_proxy_transport (self , url : str ):
685
+ pass
686
+
656
687
def setUp (self ):
657
688
super ().setUp ()
658
689
HTTPXClientInstrumentor ().instrument ()
659
690
self .client = self .create_client ()
660
691
HTTPXClientInstrumentor ().uninstrument ()
661
692
693
+ def create_proxy_mounts (self ):
694
+ return {
695
+ "http://" : self .create_proxy_transport (
696
+ "http://localhost:8080"
697
+ ),
698
+ "https://" : self .create_proxy_transport (
699
+ "http://localhost:8080"
700
+ ),
701
+ }
702
+
703
+ def assert_proxy_mounts (self , mounts , num_mounts , transport_type ):
704
+ self .assertEqual (len (mounts ), num_mounts )
705
+ for transport in mounts :
706
+ with self .subTest (transport ):
707
+ self .assertIsInstance (
708
+ transport ,
709
+ transport_type ,
710
+ )
711
+
662
712
def test_custom_tracer_provider (self ):
663
713
resource = resources .Resource .create ({})
664
714
result = self .create_tracer_provider (resource = resource )
@@ -855,6 +905,71 @@ def test_uninstrument_new_client(self):
855
905
self .assertEqual (result .text , "Hello!" )
856
906
self .assert_span ()
857
907
908
+ def test_instrument_proxy (self ):
909
+ proxy_mounts = self .create_proxy_mounts ()
910
+ HTTPXClientInstrumentor ().instrument ()
911
+ client = self .create_client (mounts = proxy_mounts )
912
+ self .perform_request (self .URL , client = client )
913
+ self .assert_span (num_spans = 1 )
914
+ self .assert_proxy_mounts (
915
+ client ._mounts .values (),
916
+ 2 ,
917
+ (SyncOpenTelemetryTransport , AsyncOpenTelemetryTransport ),
918
+ )
919
+ HTTPXClientInstrumentor ().uninstrument ()
920
+
921
+ def test_instrument_client_with_proxy (self ):
922
+ proxy_mounts = self .create_proxy_mounts ()
923
+ client = self .create_client (mounts = proxy_mounts )
924
+ self .assert_proxy_mounts (
925
+ client ._mounts .values (),
926
+ 2 ,
927
+ (httpx .HTTPTransport , httpx .AsyncHTTPTransport ),
928
+ )
929
+ HTTPXClientInstrumentor ().instrument_client (client )
930
+ result = self .perform_request (self .URL , client = client )
931
+ self .assertEqual (result .text , "Hello!" )
932
+ self .assert_span (num_spans = 1 )
933
+ self .assert_proxy_mounts (
934
+ client ._mounts .values (),
935
+ 2 ,
936
+ (SyncOpenTelemetryTransport , AsyncOpenTelemetryTransport ),
937
+ )
938
+ HTTPXClientInstrumentor ().uninstrument_client (client )
939
+
940
+ def test_uninstrument_client_with_proxy (self ):
941
+ proxy_mounts = self .create_proxy_mounts ()
942
+ HTTPXClientInstrumentor ().instrument ()
943
+ client = self .create_client (mounts = proxy_mounts )
944
+ self .assert_proxy_mounts (
945
+ client ._mounts .values (),
946
+ 2 ,
947
+ (SyncOpenTelemetryTransport , AsyncOpenTelemetryTransport ),
948
+ )
949
+
950
+ HTTPXClientInstrumentor ().uninstrument_client (client )
951
+ result = self .perform_request (self .URL , client = client )
952
+
953
+ self .assertEqual (result .text , "Hello!" )
954
+ self .assert_span (num_spans = 0 )
955
+ self .assert_proxy_mounts (
956
+ client ._mounts .values (),
957
+ 2 ,
958
+ (httpx .HTTPTransport , httpx .AsyncHTTPTransport ),
959
+ )
960
+ # Test that other clients as well as instance client is still
961
+ # instrumented
962
+ client2 = self .create_client ()
963
+ result = self .perform_request (self .URL , client = client2 )
964
+ self .assertEqual (result .text , "Hello!" )
965
+ self .assert_span ()
966
+
967
+ self .memory_exporter .clear ()
968
+
969
+ result = self .perform_request (self .URL )
970
+ self .assertEqual (result .text , "Hello!" )
971
+ self .assert_span ()
972
+
858
973
859
974
class TestSyncIntegration (BaseTestCases .BaseManualTest ):
860
975
def setUp (self ):
@@ -871,8 +986,9 @@ def create_transport(
871
986
tracer_provider : typing .Optional ["TracerProvider" ] = None ,
872
987
request_hook : typing .Optional ["RequestHook" ] = None ,
873
988
response_hook : typing .Optional ["ResponseHook" ] = None ,
989
+ ** kwargs ,
874
990
):
875
- transport = httpx .HTTPTransport ()
991
+ transport = httpx .HTTPTransport (** kwargs )
876
992
telemetry_transport = SyncOpenTelemetryTransport (
877
993
transport ,
878
994
tracer_provider = tracer_provider ,
@@ -884,8 +1000,9 @@ def create_transport(
884
1000
def create_client (
885
1001
self ,
886
1002
transport : typing .Optional [SyncOpenTelemetryTransport ] = None ,
1003
+ ** kwargs ,
887
1004
):
888
- return httpx .Client (transport = transport )
1005
+ return httpx .Client (transport = transport , ** kwargs )
889
1006
890
1007
def perform_request (
891
1008
self ,
@@ -921,8 +1038,9 @@ def create_transport(
921
1038
tracer_provider : typing .Optional ["TracerProvider" ] = None ,
922
1039
request_hook : typing .Optional ["AsyncRequestHook" ] = None ,
923
1040
response_hook : typing .Optional ["AsyncResponseHook" ] = None ,
1041
+ ** kwargs ,
924
1042
):
925
- transport = httpx .AsyncHTTPTransport ()
1043
+ transport = httpx .AsyncHTTPTransport (** kwargs )
926
1044
telemetry_transport = AsyncOpenTelemetryTransport (
927
1045
transport ,
928
1046
tracer_provider = tracer_provider ,
@@ -934,8 +1052,9 @@ def create_transport(
934
1052
def create_client (
935
1053
self ,
936
1054
transport : typing .Optional [AsyncOpenTelemetryTransport ] = None ,
1055
+ ** kwargs ,
937
1056
):
938
- return httpx .AsyncClient (transport = transport )
1057
+ return httpx .AsyncClient (transport = transport , ** kwargs )
939
1058
940
1059
def perform_request (
941
1060
self ,
@@ -977,8 +1096,9 @@ class TestSyncInstrumentationIntegration(BaseTestCases.BaseInstrumentorTest):
977
1096
def create_client (
978
1097
self ,
979
1098
transport : typing .Optional [SyncOpenTelemetryTransport ] = None ,
1099
+ ** kwargs ,
980
1100
):
981
- return httpx .Client ()
1101
+ return httpx .Client (** kwargs )
982
1102
983
1103
def perform_request (
984
1104
self ,
@@ -991,6 +1111,9 @@ def perform_request(
991
1111
return self .client .request (method , url , headers = headers )
992
1112
return client .request (method , url , headers = headers )
993
1113
1114
+ def create_proxy_transport (self , url ):
1115
+ return httpx .HTTPTransport (proxy = httpx .Proxy (url ))
1116
+
994
1117
995
1118
class TestAsyncInstrumentationIntegration (BaseTestCases .BaseInstrumentorTest ):
996
1119
response_hook = staticmethod (_async_response_hook )
@@ -1007,8 +1130,9 @@ def setUp(self):
1007
1130
def create_client (
1008
1131
self ,
1009
1132
transport : typing .Optional [AsyncOpenTelemetryTransport ] = None ,
1133
+ ** kwargs ,
1010
1134
):
1011
- return httpx .AsyncClient ()
1135
+ return httpx .AsyncClient (** kwargs )
1012
1136
1013
1137
def perform_request (
1014
1138
self ,
@@ -1027,6 +1151,9 @@ async def _perform_request():
1027
1151
1028
1152
return _async_call (_perform_request ())
1029
1153
1154
+ def create_proxy_transport (self , url ):
1155
+ return httpx .AsyncHTTPTransport (proxy = httpx .Proxy (url ))
1156
+
1030
1157
def test_basic_multiple (self ):
1031
1158
# We need to create separate clients because in httpx >= 0.19,
1032
1159
# closing the client after "with" means the second http call fails
0 commit comments