Skip to content

Commit 784bb02

Browse files
author
Ashutosh Goel
committed
Restoring metrics in requests
1 parent 100ecfe commit 784bb02

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

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

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050

5151
import functools
5252
import types
53+
from time import time
5354
from typing import Collection
55+
from urllib.parse import urlparse
5456

5557
from requests.models import Response
5658
from requests.sessions import Session
@@ -64,6 +66,7 @@
6466
_SUPPRESS_INSTRUMENTATION_KEY,
6567
http_status_to_status_code,
6668
)
69+
from opentelemetry.metrics import get_meter
6770
from opentelemetry.propagate import inject
6871
from opentelemetry.semconv.trace import SpanAttributes
6972
from opentelemetry.trace import SpanKind, get_tracer
@@ -87,7 +90,11 @@
8790
# pylint: disable=unused-argument
8891
# pylint: disable=R0915
8992
def _instrument(
90-
tracer, span_callback=None, name_callback=None, excluded_urls=None
93+
tracer,
94+
span_callback=None,
95+
name_callback=None,
96+
excluded_urls=None,
97+
metric_recorder=None,
9198
):
9299
"""Enables tracing of all requests calls that go through
93100
:code:`requests.session.Session.request` (this includes
@@ -166,6 +173,7 @@ def _instrumented_requests_call(
166173
SpanAttributes.HTTP_METHOD: method,
167174
SpanAttributes.HTTP_URL: url,
168175
}
176+
parsed_url = urlparse(url)
169177

170178
with tracer.start_as_current_span(
171179
span_name, kind=SpanKind.CLIENT, attributes=span_attributes
@@ -178,6 +186,8 @@ def _instrumented_requests_call(
178186
token = context.attach(
179187
context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True)
180188
)
189+
start_time = time()
190+
metric_labels = {}
181191
try:
182192
result = call_wrapped() # *** PROCEED
183193
except Exception as exc: # pylint: disable=W0703
@@ -194,9 +204,30 @@ def _instrumented_requests_call(
194204
span.set_status(
195205
Status(http_status_to_status_code(result.status_code))
196206
)
207+
208+
metric_labels["http.status_code"] = result.status_code
209+
metric_labels["http.flavor"] = (
210+
"1.1" if result.raw.version == 11 else "1.0"
211+
)
212+
197213
if span_callback is not None:
198214
span_callback(span, result)
199215

216+
elapsed_time = time() - start_time
217+
218+
metric_labels.update(
219+
{
220+
"http.method": method,
221+
"http.host": parsed_url.hostname,
222+
"http.scheme": parsed_url.scheme,
223+
"http.url": url,
224+
}
225+
)
226+
227+
metric_recorder.record(
228+
elapsed_time * 1000, attributes=metric_labels
229+
)
230+
200231
if exception is not None:
201232
raise exception.with_traceback(exception.__traceback__)
202233

@@ -261,13 +292,25 @@ def _instrument(self, **kwargs):
261292
tracer_provider = kwargs.get("tracer_provider")
262293
tracer = get_tracer(__name__, __version__, tracer_provider)
263294
excluded_urls = kwargs.get("excluded_urls")
295+
meter_provider = kwargs.get("meter_provider")
296+
self._meter = get_meter(
297+
__name__,
298+
__version__,
299+
meter_provider,
300+
)
301+
self._metric_recorder = self._meter.create_histogram(
302+
name="http.client.duration",
303+
unit="ms",
304+
description="measures the duration of the outbound HTTP request",
305+
)
264306
_instrument(
265307
tracer,
266308
span_callback=kwargs.get("span_callback"),
267309
name_callback=kwargs.get("name_callback"),
268310
excluded_urls=_excluded_urls_from_env
269311
if excluded_urls is None
270312
else parse_excluded_urls(excluded_urls),
313+
metric_recorder=self._metric_recorder,
271314
)
272315

273316
def _uninstrument(self, **kwargs):

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
2727
from opentelemetry.propagate import get_global_textmap, set_global_textmap
2828
from opentelemetry.sdk import resources
29+
from opentelemetry.sdk.metrics import MeterProvider
30+
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
2931
from opentelemetry.semconv.trace import SpanAttributes
3032
from opentelemetry.test.mock_textmap import MockTextMapPropagator
3133
from opentelemetry.test.test_base import TestBase
@@ -472,3 +474,43 @@ def perform_request(url: str, session: requests.Session = None):
472474
request = requests.Request("GET", url)
473475
prepared_request = session.prepare_request(request)
474476
return session.send(prepared_request)
477+
478+
479+
class TestRequestsIntergrationMetric(TestBase):
480+
URL = "http://httpbin.org/status/200"
481+
482+
def setUp(self):
483+
super().setUp()
484+
self.reader = InMemoryMetricReader()
485+
self.meter_provider = MeterProvider(metric_readers=[self.reader])
486+
RequestsInstrumentor().instrument(meter_provider=self.meter_provider)
487+
488+
httpretty.enable()
489+
httpretty.register_uri(httpretty.GET, self.URL, body="Hello!")
490+
491+
@staticmethod
492+
def perform_request(url: str) -> requests.Response:
493+
return requests.get(url)
494+
495+
def test_basic_http_success(self):
496+
response = self.perform_request(self.URL)
497+
498+
expected_attributes = {
499+
"http.status_code": 200,
500+
"http.flavor": "1.1",
501+
"http.method": "GET",
502+
"http.host": "httpbin.org",
503+
"http.scheme": "http",
504+
"http.url": self.URL,
505+
}
506+
507+
for (
508+
resource_metrics
509+
) in self.reader.get_metrics_data().resource_metrics:
510+
for scope_metrics in resource_metrics.scope_metrics:
511+
for metric in scope_metrics.metrics:
512+
for data_point in metric.data.data_points:
513+
self.assertDictEqual(
514+
expected_attributes, dict(data_point.attributes)
515+
)
516+
self.assertEqual(data_point.count, 1)

0 commit comments

Comments
 (0)