opentelemetry-instrumentation-requests 0.46b0__tar.gz → 0.47b0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -58,3 +58,6 @@ _build/
58
58
  # mypy
59
59
  .mypy_cache/
60
60
  target
61
+
62
+ # Benchmark result files
63
+ *-benchmark.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: opentelemetry-instrumentation-requests
3
- Version: 0.46b0
3
+ Version: 0.47b0
4
4
  Summary: OpenTelemetry requests instrumentation
5
5
  Project-URL: Homepage, https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-requests
6
6
  Author-email: OpenTelemetry Authors <cncf-opentelemetry-contributors@lists.cncf.io>
@@ -15,11 +15,12 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Requires-Python: >=3.8
19
20
  Requires-Dist: opentelemetry-api~=1.12
20
- Requires-Dist: opentelemetry-instrumentation==0.46b0
21
- Requires-Dist: opentelemetry-semantic-conventions==0.46b0
22
- Requires-Dist: opentelemetry-util-http==0.46b0
21
+ Requires-Dist: opentelemetry-instrumentation==0.47b0
22
+ Requires-Dist: opentelemetry-semantic-conventions==0.47b0
23
+ Requires-Dist: opentelemetry-util-http==0.47b0
23
24
  Provides-Extra: instruments
24
25
  Requires-Dist: requests~=2.0; extra == 'instruments'
25
26
  Description-Content-Type: text/x-rst
@@ -22,12 +22,13 @@ classifiers = [
22
22
  "Programming Language :: Python :: 3.9",
23
23
  "Programming Language :: Python :: 3.10",
24
24
  "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
25
26
  ]
26
27
  dependencies = [
27
28
  "opentelemetry-api ~= 1.12",
28
- "opentelemetry-instrumentation == 0.46b0",
29
- "opentelemetry-semantic-conventions == 0.46b0",
30
- "opentelemetry-util-http == 0.46b0",
29
+ "opentelemetry-instrumentation == 0.47b0",
30
+ "opentelemetry-semantic-conventions == 0.47b0",
31
+ "opentelemetry-util-http == 0.47b0",
31
32
  ]
32
33
 
33
34
  [project.optional-dependencies]
@@ -31,6 +31,30 @@ Usage
31
31
  Configuration
32
32
  -------------
33
33
 
34
+ Request/Response hooks
35
+ **********************
36
+
37
+ The requests instrumentation supports extending tracing behavior with the help of
38
+ request and response hooks. These are functions that are called back by the instrumentation
39
+ right after a Span is created for a request and right before the span is finished processing a response respectively.
40
+ The hooks can be configured as follows:
41
+
42
+ .. code:: python
43
+
44
+ # `request_obj` is an instance of requests.PreparedRequest
45
+ def request_hook(span, request_obj):
46
+ pass
47
+
48
+ # `request_obj` is an instance of requests.PreparedRequest
49
+ # `response` is an instance of requests.Response
50
+ def response_hook(span, request_obj, response)
51
+ pass
52
+
53
+ RequestsInstrumentor().instrument(
54
+ request_hook=request_hook, response_hook=response_hook)
55
+ )
56
+
57
+
34
58
  Exclude lists
35
59
  *************
36
60
  To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_REQUESTS_EXCLUDED_URLS``
@@ -59,10 +83,6 @@ from requests.sessions import Session
59
83
  from requests.structures import CaseInsensitiveDict
60
84
 
61
85
  from opentelemetry.instrumentation._semconv import (
62
- _METRIC_ATTRIBUTES_CLIENT_DURATION_NAME,
63
- _SPAN_ATTRIBUTES_ERROR_TYPE,
64
- _SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS,
65
- _SPAN_ATTRIBUTES_NETWORK_PEER_PORT,
66
86
  _client_duration_attrs_new,
67
87
  _client_duration_attrs_old,
68
88
  _filter_semconv_duration_attrs,
@@ -91,7 +111,15 @@ from opentelemetry.instrumentation.utils import (
91
111
  )
92
112
  from opentelemetry.metrics import Histogram, get_meter
93
113
  from opentelemetry.propagate import inject
114
+ from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
115
+ from opentelemetry.semconv.attributes.network_attributes import (
116
+ NETWORK_PEER_ADDRESS,
117
+ NETWORK_PEER_PORT,
118
+ )
94
119
  from opentelemetry.semconv.metrics import MetricInstruments
120
+ from opentelemetry.semconv.metrics.http_metrics import (
121
+ HTTP_CLIENT_REQUEST_DURATION,
122
+ )
95
123
  from opentelemetry.trace import SpanKind, Tracer, get_tracer
96
124
  from opentelemetry.trace.span import Span
97
125
  from opentelemetry.trace.status import StatusCode
@@ -160,13 +188,19 @@ def _instrument(
160
188
 
161
189
  span_attributes = {}
162
190
  _set_http_method(
163
- span_attributes, method, span_name, sem_conv_opt_in_mode
191
+ span_attributes,
192
+ method,
193
+ sanitize_method(method),
194
+ sem_conv_opt_in_mode,
164
195
  )
165
196
  _set_http_url(span_attributes, url, sem_conv_opt_in_mode)
166
197
 
167
198
  metric_labels = {}
168
199
  _set_http_method(
169
- metric_labels, method, span_name, sem_conv_opt_in_mode
200
+ metric_labels,
201
+ method,
202
+ sanitize_method(method),
203
+ sem_conv_opt_in_mode,
170
204
  )
171
205
 
172
206
  try:
@@ -191,9 +225,7 @@ def _instrument(
191
225
  sem_conv_opt_in_mode,
192
226
  )
193
227
  # Use semconv library when available
194
- span_attributes[_SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS] = (
195
- parsed_url.hostname
196
- )
228
+ span_attributes[NETWORK_PEER_ADDRESS] = parsed_url.hostname
197
229
  if parsed_url.port:
198
230
  _set_http_peer_port_client(
199
231
  metric_labels, parsed_url.port, sem_conv_opt_in_mode
@@ -203,9 +235,7 @@ def _instrument(
203
235
  span_attributes, parsed_url.port, sem_conv_opt_in_mode
204
236
  )
205
237
  # Use semconv library when available
206
- span_attributes[_SPAN_ATTRIBUTES_NETWORK_PEER_PORT] = (
207
- parsed_url.port
208
- )
238
+ span_attributes[NETWORK_PEER_PORT] = parsed_url.port
209
239
  except ValueError:
210
240
  pass
211
241
 
@@ -250,12 +280,8 @@ def _instrument(
250
280
  _report_new(sem_conv_opt_in_mode)
251
281
  and status_code is StatusCode.ERROR
252
282
  ):
253
- span_attributes[_SPAN_ATTRIBUTES_ERROR_TYPE] = str(
254
- result.status_code
255
- )
256
- metric_labels[_SPAN_ATTRIBUTES_ERROR_TYPE] = str(
257
- result.status_code
258
- )
283
+ span_attributes[ERROR_TYPE] = str(result.status_code)
284
+ metric_labels[ERROR_TYPE] = str(result.status_code)
259
285
 
260
286
  if result.raw is not None:
261
287
  version = getattr(result.raw, "version", None)
@@ -278,12 +304,8 @@ def _instrument(
278
304
  response_hook(span, request, result)
279
305
 
280
306
  if exception is not None and _report_new(sem_conv_opt_in_mode):
281
- span.set_attribute(
282
- _SPAN_ATTRIBUTES_ERROR_TYPE, type(exception).__qualname__
283
- )
284
- metric_labels[_SPAN_ATTRIBUTES_ERROR_TYPE] = type(
285
- exception
286
- ).__qualname__
307
+ span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
308
+ metric_labels[ERROR_TYPE] = type(exception).__qualname__
287
309
 
288
310
  if duration_histogram_old is not None:
289
311
  duration_attrs_old = _filter_semconv_duration_attrs(
@@ -349,7 +371,7 @@ def get_default_span_name(method):
349
371
  Returns:
350
372
  span name
351
373
  """
352
- method = sanitize_method(method.upper().strip())
374
+ method = sanitize_method(method.strip())
353
375
  if method == "_OTHER":
354
376
  return "HTTP"
355
377
  return method
@@ -403,7 +425,7 @@ class RequestsInstrumentor(BaseInstrumentor):
403
425
  duration_histogram_new = None
404
426
  if _report_new(semconv_opt_in_mode):
405
427
  duration_histogram_new = meter.create_histogram(
406
- name=_METRIC_ATTRIBUTES_CLIENT_DURATION_NAME,
428
+ name=HTTP_CLIENT_REQUEST_DURATION,
407
429
  unit="s",
408
430
  description="Duration of HTTP client requests.",
409
431
  )
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "0.46b0"
15
+ __version__ = "0.47b0"
@@ -23,9 +23,6 @@ from requests.models import Response
23
23
  import opentelemetry.instrumentation.requests
24
24
  from opentelemetry import trace
25
25
  from opentelemetry.instrumentation._semconv import (
26
- _SPAN_ATTRIBUTES_ERROR_TYPE,
27
- _SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS,
28
- _SPAN_ATTRIBUTES_NETWORK_PEER_PORT,
29
26
  OTEL_SEMCONV_STABILITY_OPT_IN,
30
27
  _OpenTelemetrySemanticConventionStability,
31
28
  )
@@ -36,6 +33,22 @@ from opentelemetry.instrumentation.utils import (
36
33
  )
37
34
  from opentelemetry.propagate import get_global_textmap, set_global_textmap
38
35
  from opentelemetry.sdk import resources
36
+ from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
37
+ from opentelemetry.semconv.attributes.http_attributes import (
38
+ HTTP_REQUEST_METHOD,
39
+ HTTP_REQUEST_METHOD_ORIGINAL,
40
+ HTTP_RESPONSE_STATUS_CODE,
41
+ )
42
+ from opentelemetry.semconv.attributes.network_attributes import (
43
+ NETWORK_PEER_ADDRESS,
44
+ NETWORK_PEER_PORT,
45
+ NETWORK_PROTOCOL_VERSION,
46
+ )
47
+ from opentelemetry.semconv.attributes.server_attributes import (
48
+ SERVER_ADDRESS,
49
+ SERVER_PORT,
50
+ )
51
+ from opentelemetry.semconv.attributes.url_attributes import URL_FULL
39
52
  from opentelemetry.semconv.trace import SpanAttributes
40
53
  from opentelemetry.test.mock_textmap import MockTextMapPropagator
41
54
  from opentelemetry.test.test_base import TestBase
@@ -176,14 +189,14 @@ class RequestsIntegrationTestBase(abc.ABC):
176
189
  self.assertEqual(
177
190
  span.attributes,
178
191
  {
179
- SpanAttributes.HTTP_REQUEST_METHOD: "GET",
180
- SpanAttributes.URL_FULL: url_with_port,
181
- SpanAttributes.SERVER_ADDRESS: "mock",
182
- _SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS: "mock",
183
- SpanAttributes.HTTP_RESPONSE_STATUS_CODE: 200,
184
- SpanAttributes.NETWORK_PROTOCOL_VERSION: "1.1",
185
- SpanAttributes.SERVER_PORT: 80,
186
- _SPAN_ATTRIBUTES_NETWORK_PEER_PORT: 80,
192
+ HTTP_REQUEST_METHOD: "GET",
193
+ URL_FULL: url_with_port,
194
+ SERVER_ADDRESS: "mock",
195
+ NETWORK_PEER_ADDRESS: "mock",
196
+ HTTP_RESPONSE_STATUS_CODE: 200,
197
+ NETWORK_PROTOCOL_VERSION: "1.1",
198
+ SERVER_PORT: 80,
199
+ NETWORK_PEER_PORT: 80,
187
200
  },
188
201
  )
189
202
 
@@ -213,19 +226,19 @@ class RequestsIntegrationTestBase(abc.ABC):
213
226
  span.attributes,
214
227
  {
215
228
  SpanAttributes.HTTP_METHOD: "GET",
216
- SpanAttributes.HTTP_REQUEST_METHOD: "GET",
229
+ HTTP_REQUEST_METHOD: "GET",
217
230
  SpanAttributes.HTTP_URL: url_with_port,
218
- SpanAttributes.URL_FULL: url_with_port,
231
+ URL_FULL: url_with_port,
219
232
  SpanAttributes.HTTP_HOST: "mock",
220
- SpanAttributes.SERVER_ADDRESS: "mock",
221
- _SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS: "mock",
233
+ SERVER_ADDRESS: "mock",
234
+ NETWORK_PEER_ADDRESS: "mock",
222
235
  SpanAttributes.NET_PEER_PORT: 80,
223
236
  SpanAttributes.HTTP_STATUS_CODE: 200,
224
- SpanAttributes.HTTP_RESPONSE_STATUS_CODE: 200,
237
+ HTTP_RESPONSE_STATUS_CODE: 200,
225
238
  SpanAttributes.HTTP_FLAVOR: "1.1",
226
- SpanAttributes.NETWORK_PROTOCOL_VERSION: "1.1",
227
- SpanAttributes.SERVER_PORT: 80,
228
- _SPAN_ATTRIBUTES_NETWORK_PEER_PORT: 80,
239
+ NETWORK_PROTOCOL_VERSION: "1.1",
240
+ SERVER_PORT: 80,
241
+ NETWORK_PEER_PORT: 80,
229
242
  },
230
243
  )
231
244
 
@@ -235,6 +248,48 @@ class RequestsIntegrationTestBase(abc.ABC):
235
248
  span, opentelemetry.instrumentation.requests
236
249
  )
237
250
 
251
+ @mock.patch("httpretty.http.HttpBaseClass.METHODS", ("NONSTANDARD",))
252
+ def test_nonstandard_http_method(self):
253
+ httpretty.register_uri("NONSTANDARD", self.URL, status=405)
254
+ session = requests.Session()
255
+ session.request("NONSTANDARD", self.URL)
256
+ span = self.assert_span()
257
+ self.assertIs(span.kind, trace.SpanKind.CLIENT)
258
+ self.assertEqual(span.name, "HTTP")
259
+ self.assertEqual(
260
+ span.attributes,
261
+ {
262
+ SpanAttributes.HTTP_METHOD: "_OTHER",
263
+ SpanAttributes.HTTP_URL: self.URL,
264
+ SpanAttributes.HTTP_STATUS_CODE: 405,
265
+ },
266
+ )
267
+
268
+ self.assertIs(span.status.status_code, trace.StatusCode.ERROR)
269
+
270
+ @mock.patch("httpretty.http.HttpBaseClass.METHODS", ("NONSTANDARD",))
271
+ def test_nonstandard_http_method_new_semconv(self):
272
+ httpretty.register_uri("NONSTANDARD", self.URL, status=405)
273
+ session = requests.Session()
274
+ session.request("NONSTANDARD", self.URL)
275
+ span = self.assert_span()
276
+ self.assertIs(span.kind, trace.SpanKind.CLIENT)
277
+ self.assertEqual(span.name, "HTTP")
278
+ self.assertEqual(
279
+ span.attributes,
280
+ {
281
+ HTTP_REQUEST_METHOD: "_OTHER",
282
+ URL_FULL: self.URL,
283
+ SERVER_ADDRESS: "mock",
284
+ NETWORK_PEER_ADDRESS: "mock",
285
+ HTTP_RESPONSE_STATUS_CODE: 405,
286
+ NETWORK_PROTOCOL_VERSION: "1.1",
287
+ ERROR_TYPE: "405",
288
+ HTTP_REQUEST_METHOD_ORIGINAL: "NONSTANDARD",
289
+ },
290
+ )
291
+ self.assertIs(span.status.status_code, trace.StatusCode.ERROR)
292
+
238
293
  def test_hooks(self):
239
294
  def request_hook(span, request_obj):
240
295
  span.update_name("name set from hook")
@@ -328,12 +383,8 @@ class RequestsIntegrationTestBase(abc.ABC):
328
383
 
329
384
  span = self.assert_span()
330
385
 
331
- self.assertEqual(
332
- span.attributes.get(SpanAttributes.HTTP_RESPONSE_STATUS_CODE), 404
333
- )
334
- self.assertEqual(
335
- span.attributes.get(_SPAN_ATTRIBUTES_ERROR_TYPE), "404"
336
- )
386
+ self.assertEqual(span.attributes.get(HTTP_RESPONSE_STATUS_CODE), 404)
387
+ self.assertEqual(span.attributes.get(ERROR_TYPE), "404")
337
388
 
338
389
  self.assertIs(
339
390
  span.status.status_code,
@@ -355,12 +406,8 @@ class RequestsIntegrationTestBase(abc.ABC):
355
406
  self.assertEqual(
356
407
  span.attributes.get(SpanAttributes.HTTP_STATUS_CODE), 404
357
408
  )
358
- self.assertEqual(
359
- span.attributes.get(SpanAttributes.HTTP_RESPONSE_STATUS_CODE), 404
360
- )
361
- self.assertEqual(
362
- span.attributes.get(_SPAN_ATTRIBUTES_ERROR_TYPE), "404"
363
- )
409
+ self.assertEqual(span.attributes.get(HTTP_RESPONSE_STATUS_CODE), 404)
410
+ self.assertEqual(span.attributes.get(ERROR_TYPE), "404")
364
411
 
365
412
  self.assertIs(
366
413
  span.status.status_code,
@@ -527,13 +574,13 @@ class RequestsIntegrationTestBase(abc.ABC):
527
574
  self.assertEqual(
528
575
  span.attributes,
529
576
  {
530
- SpanAttributes.HTTP_REQUEST_METHOD: "GET",
531
- SpanAttributes.URL_FULL: url_with_port,
532
- SpanAttributes.SERVER_ADDRESS: "mock",
533
- SpanAttributes.SERVER_PORT: 80,
534
- _SPAN_ATTRIBUTES_NETWORK_PEER_PORT: 80,
535
- _SPAN_ATTRIBUTES_NETWORK_PEER_ADDRESS: "mock",
536
- _SPAN_ATTRIBUTES_ERROR_TYPE: "RequestException",
577
+ HTTP_REQUEST_METHOD: "GET",
578
+ URL_FULL: url_with_port,
579
+ SERVER_ADDRESS: "mock",
580
+ SERVER_PORT: 80,
581
+ NETWORK_PEER_PORT: 80,
582
+ NETWORK_PEER_ADDRESS: "mock",
583
+ ERROR_TYPE: "RequestException",
537
584
  },
538
585
  )
539
586
  self.assertEqual(span.status.status_code, StatusCode.ERROR)
@@ -724,11 +771,11 @@ class TestRequestsIntergrationMetric(TestBase):
724
771
  self.perform_request(self.URL)
725
772
 
726
773
  expected_attributes = {
727
- SpanAttributes.HTTP_RESPONSE_STATUS_CODE: 200,
728
- SpanAttributes.SERVER_ADDRESS: "examplehost",
729
- SpanAttributes.SERVER_PORT: 8000,
730
- SpanAttributes.HTTP_REQUEST_METHOD: "GET",
731
- SpanAttributes.NETWORK_PROTOCOL_VERSION: "1.1",
774
+ HTTP_RESPONSE_STATUS_CODE: 200,
775
+ SERVER_ADDRESS: "examplehost",
776
+ SERVER_PORT: 8000,
777
+ HTTP_REQUEST_METHOD: "GET",
778
+ NETWORK_PROTOCOL_VERSION: "1.1",
732
779
  }
733
780
  for (
734
781
  resource_metrics
@@ -760,11 +807,11 @@ class TestRequestsIntergrationMetric(TestBase):
760
807
  }
761
808
 
762
809
  expected_attributes_new = {
763
- SpanAttributes.HTTP_RESPONSE_STATUS_CODE: 200,
764
- SpanAttributes.SERVER_ADDRESS: "examplehost",
765
- SpanAttributes.SERVER_PORT: 8000,
766
- SpanAttributes.HTTP_REQUEST_METHOD: "GET",
767
- SpanAttributes.NETWORK_PROTOCOL_VERSION: "1.1",
810
+ HTTP_RESPONSE_STATUS_CODE: 200,
811
+ SERVER_ADDRESS: "examplehost",
812
+ SERVER_PORT: 8000,
813
+ HTTP_REQUEST_METHOD: "GET",
814
+ NETWORK_PROTOCOL_VERSION: "1.1",
768
815
  }
769
816
 
770
817
  for (