Skip to content

Commit

Permalink
Restoring metrics in requests
Browse files Browse the repository at this point in the history
  • Loading branch information
ashu658 committed May 31, 2022
1 parent 100ecfe commit 784bb02
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@

import functools
import types
from time import time
from typing import Collection
from urllib.parse import urlparse

from requests.models import Response
from requests.sessions import Session
Expand All @@ -64,6 +66,7 @@
_SUPPRESS_INSTRUMENTATION_KEY,
http_status_to_status_code,
)
from opentelemetry.metrics import get_meter
from opentelemetry.propagate import inject
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import SpanKind, get_tracer
Expand All @@ -87,7 +90,11 @@
# pylint: disable=unused-argument
# pylint: disable=R0915
def _instrument(
tracer, span_callback=None, name_callback=None, excluded_urls=None
tracer,
span_callback=None,
name_callback=None,
excluded_urls=None,
metric_recorder=None,
):
"""Enables tracing of all requests calls that go through
:code:`requests.session.Session.request` (this includes
Expand Down Expand Up @@ -166,6 +173,7 @@ def _instrumented_requests_call(
SpanAttributes.HTTP_METHOD: method,
SpanAttributes.HTTP_URL: url,
}
parsed_url = urlparse(url)

with tracer.start_as_current_span(
span_name, kind=SpanKind.CLIENT, attributes=span_attributes
Expand All @@ -178,6 +186,8 @@ def _instrumented_requests_call(
token = context.attach(
context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True)
)
start_time = time()
metric_labels = {}
try:
result = call_wrapped() # *** PROCEED
except Exception as exc: # pylint: disable=W0703
Expand All @@ -194,9 +204,30 @@ def _instrumented_requests_call(
span.set_status(
Status(http_status_to_status_code(result.status_code))
)

metric_labels["http.status_code"] = result.status_code
metric_labels["http.flavor"] = (
"1.1" if result.raw.version == 11 else "1.0"
)

if span_callback is not None:
span_callback(span, result)

elapsed_time = time() - start_time

metric_labels.update(
{
"http.method": method,
"http.host": parsed_url.hostname,
"http.scheme": parsed_url.scheme,
"http.url": url,
}
)

metric_recorder.record(
elapsed_time * 1000, attributes=metric_labels
)

if exception is not None:
raise exception.with_traceback(exception.__traceback__)

Expand Down Expand Up @@ -261,13 +292,25 @@ def _instrument(self, **kwargs):
tracer_provider = kwargs.get("tracer_provider")
tracer = get_tracer(__name__, __version__, tracer_provider)
excluded_urls = kwargs.get("excluded_urls")
meter_provider = kwargs.get("meter_provider")
self._meter = get_meter(
__name__,
__version__,
meter_provider,
)
self._metric_recorder = self._meter.create_histogram(
name="http.client.duration",
unit="ms",
description="measures the duration of the outbound HTTP request",
)
_instrument(
tracer,
span_callback=kwargs.get("span_callback"),
name_callback=kwargs.get("name_callback"),
excluded_urls=_excluded_urls_from_env
if excluded_urls is None
else parse_excluded_urls(excluded_urls),
metric_recorder=self._metric_recorder,
)

def _uninstrument(self, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
from opentelemetry.propagate import get_global_textmap, set_global_textmap
from opentelemetry.sdk import resources
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.mock_textmap import MockTextMapPropagator
from opentelemetry.test.test_base import TestBase
Expand Down Expand Up @@ -472,3 +474,43 @@ def perform_request(url: str, session: requests.Session = None):
request = requests.Request("GET", url)
prepared_request = session.prepare_request(request)
return session.send(prepared_request)


class TestRequestsIntergrationMetric(TestBase):
URL = "http://httpbin.org/status/200"

def setUp(self):
super().setUp()
self.reader = InMemoryMetricReader()
self.meter_provider = MeterProvider(metric_readers=[self.reader])
RequestsInstrumentor().instrument(meter_provider=self.meter_provider)

httpretty.enable()
httpretty.register_uri(httpretty.GET, self.URL, body="Hello!")

@staticmethod
def perform_request(url: str) -> requests.Response:
return requests.get(url)

def test_basic_http_success(self):
response = self.perform_request(self.URL)

expected_attributes = {
"http.status_code": 200,
"http.flavor": "1.1",
"http.method": "GET",
"http.host": "httpbin.org",
"http.scheme": "http",
"http.url": self.URL,
}

for (
resource_metrics
) in self.reader.get_metrics_data().resource_metrics:
for scope_metrics in resource_metrics.scope_metrics:
for metric in scope_metrics.metrics:
for data_point in metric.data.data_points:
self.assertDictEqual(
expected_attributes, dict(data_point.attributes)
)
self.assertEqual(data_point.count, 1)

0 comments on commit 784bb02

Please sign in to comment.