1414
1515"""
1616This library allows tracing HTTP requests made by the
17- `requests <https://requests.kennethreitz.org /en/master/>`_ library.
17+ `requests <https://requests.readthedocs.io /en/master/>`_ library.
1818
1919Usage
2020-----
2323
2424 import requests
2525 import opentelemetry.ext.http_requests
26- from opentelemetry.trace import TracerProvider
2726
28- opentelemetry.ext.http_requests.enable(TracerProvider())
29- response = requests.get(url='https://www.example.org/')
27+ # You can optionally pass a custom TracerProvider to RequestInstrumentor.instrument()
28+ opentelemetry.ext.http_requests.RequestInstrumentor.instrument()
29+ response = requests.get(url="https://www.example.org/")
3030
3131Limitations
3232-----------
4747
4848from requests .sessions import Session
4949
50- from opentelemetry import context , propagators
50+ from opentelemetry import context , propagators , trace
51+ from opentelemetry .auto_instrumentation .instrumentor import BaseInstrumentor
5152from opentelemetry .ext .http_requests .version import __version__
52- from opentelemetry .trace import SpanKind
53+ from opentelemetry .trace import SpanKind , get_tracer
54+ from opentelemetry .trace .status import Status , StatusCanonicalCode
5355
5456
55- # NOTE: Currently we force passing a tracer. But in turn, this forces the user
56- # to configure a SDK before enabling this integration. In turn, this means that
57- # if the SDK/tracer is already using `requests` they may, in theory, bypass our
58- # instrumentation when using `import from`, etc. (currently we only instrument
59- # a instance method so the probability for that is very low).
60- def enable (tracer_provider ):
57+ # pylint: disable=unused-argument
58+ def _instrument (tracer_provider = None ):
6159 """Enables tracing of all requests calls that go through
6260 :code:`requests.session.Session.request` (this includes
6361 :code:`requests.get`, etc.)."""
@@ -69,20 +67,17 @@ def enable(tracer_provider):
6967 # before v1.0.0, Dec 17, 2012, see
7068 # https://github.com/psf/requests/commit/4e5c4a6ab7bb0195dececdd19bb8505b872fe120)
7169
72- # Guard against double instrumentation
73- disable ()
74-
75- tracer = tracer_provider .get_tracer (__name__ , __version__ )
76-
7770 wrapped = Session .request
7871
72+ tracer = trace .get_tracer (__name__ , __version__ , tracer_provider )
73+
7974 @functools .wraps (wrapped )
8075 def instrumented_request (self , method , url , * args , ** kwargs ):
8176 if context .get_value ("suppress_instrumentation" ):
8277 return wrapped (self , method , url , * args , ** kwargs )
8378
8479 # See
85- # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions .md#http-client
80+ # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http .md#http-client
8681 try :
8782 parsed_url = urlparse (url )
8883 except ValueError as exc : # Invalid URL
@@ -103,12 +98,12 @@ def instrumented_request(self, method, url, *args, **kwargs):
10398
10499 span .set_attribute ("http.status_code" , result .status_code )
105100 span .set_attribute ("http.status_text" , result .reason )
101+ span .set_status (
102+ Status (_http_status_to_canonical_code (result .status_code ))
103+ )
106104
107105 return result
108106
109- # TODO: How to handle exceptions? Should we create events for them? Set
110- # certain attributes?
111-
112107 instrumented_request .opentelemetry_ext_requests_applied = True
113108
114109 Session .request = instrumented_request
@@ -119,18 +114,59 @@ def instrumented_request(self, method, url, *args, **kwargs):
119114 # different, then push the current URL, pop it afterwards)
120115
121116
122- def disable ():
117+ def _uninstrument ():
118+ # pylint: disable=global-statement
123119 """Disables instrumentation of :code:`requests` through this module.
124120
125121 Note that this only works if no other module also patches requests."""
126-
127122 if getattr (Session .request , "opentelemetry_ext_requests_applied" , False ):
128123 original = Session .request .__wrapped__ # pylint:disable=no-member
129124 Session .request = original
130125
131126
132- def disable_session (session ):
133- """Disables instrumentation on the session object."""
134- if getattr (session .request , "opentelemetry_ext_requests_applied" , False ):
135- original = session .request .__wrapped__ # pylint:disable=no-member
136- session .request = types .MethodType (original , session )
127+ def _http_status_to_canonical_code (code : int , allow_redirect : bool = True ):
128+ # pylint:disable=too-many-branches,too-many-return-statements
129+ if code < 100 :
130+ return StatusCanonicalCode .UNKNOWN
131+ if code <= 299 :
132+ return StatusCanonicalCode .OK
133+ if code <= 399 :
134+ if allow_redirect :
135+ return StatusCanonicalCode .OK
136+ return StatusCanonicalCode .DEADLINE_EXCEEDED
137+ if code <= 499 :
138+ if code == 401 : # HTTPStatus.UNAUTHORIZED:
139+ return StatusCanonicalCode .UNAUTHENTICATED
140+ if code == 403 : # HTTPStatus.FORBIDDEN:
141+ return StatusCanonicalCode .PERMISSION_DENIED
142+ if code == 404 : # HTTPStatus.NOT_FOUND:
143+ return StatusCanonicalCode .NOT_FOUND
144+ if code == 429 : # HTTPStatus.TOO_MANY_REQUESTS:
145+ return StatusCanonicalCode .RESOURCE_EXHAUSTED
146+ return StatusCanonicalCode .INVALID_ARGUMENT
147+ if code <= 599 :
148+ if code == 501 : # HTTPStatus.NOT_IMPLEMENTED:
149+ return StatusCanonicalCode .UNIMPLEMENTED
150+ if code == 503 : # HTTPStatus.SERVICE_UNAVAILABLE:
151+ return StatusCanonicalCode .UNAVAILABLE
152+ if code == 504 : # HTTPStatus.GATEWAY_TIMEOUT:
153+ return StatusCanonicalCode .DEADLINE_EXCEEDED
154+ return StatusCanonicalCode .INTERNAL
155+ return StatusCanonicalCode .UNKNOWN
156+
157+
158+ class RequestsInstrumentor (BaseInstrumentor ):
159+ def _instrument (self , ** kwargs ):
160+ _instrument (tracer_provider = kwargs .get ("tracer_provider" ))
161+
162+ def _uninstrument (self , ** kwargs ):
163+ _uninstrument ()
164+
165+ @staticmethod
166+ def uninstrument_session (session ):
167+ """Disables instrumentation on the session object."""
168+ if getattr (
169+ session .request , "opentelemetry_ext_requests_applied" , False
170+ ):
171+ original = session .request .__wrapped__ # pylint:disable=no-member
172+ session .request = types .MethodType (original , session )
0 commit comments