RequestsInstrumentor: Add support for prepared requests (#1040)
in addition to Session.request instrument Session.send to also create spans for prepared requests.
This commit is contained in:
parent
8d061a079f
commit
fbd782c7f4
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add support for instrumenting prepared requests
|
||||||
|
([#1040](https://github.com/open-telemetry/opentelemetry-python/pull/1040))
|
||||||
|
|
||||||
## Version 0.12b0
|
## Version 0.12b0
|
||||||
|
|
||||||
Released 2020-08-14
|
Released 2020-08-14
|
||||||
|
|
|
||||||
|
|
@ -29,26 +29,17 @@ Usage
|
||||||
opentelemetry.instrumentation.requests.RequestsInstrumentor().instrument()
|
opentelemetry.instrumentation.requests.RequestsInstrumentor().instrument()
|
||||||
response = requests.get(url="https://www.example.org/")
|
response = requests.get(url="https://www.example.org/")
|
||||||
|
|
||||||
Limitations
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Note that calls that do not use the higher-level APIs but use
|
|
||||||
:code:`requests.sessions.Session.send` (or an alias thereof) directly, are
|
|
||||||
currently not traced. If you find any other way to trigger an untraced HTTP
|
|
||||||
request, please report it via a GitHub issue with :code:`[requests: untraced
|
|
||||||
API]` in the title.
|
|
||||||
|
|
||||||
API
|
API
|
||||||
---
|
---
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import types
|
import types
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from requests import Timeout, URLRequired
|
from requests import Timeout, URLRequired
|
||||||
from requests.exceptions import InvalidSchema, InvalidURL, MissingSchema
|
from requests.exceptions import InvalidSchema, InvalidURL, MissingSchema
|
||||||
from requests.sessions import Session
|
from requests.sessions import Session
|
||||||
|
from requests.structures import CaseInsensitiveDict
|
||||||
|
|
||||||
from opentelemetry import context, propagators
|
from opentelemetry import context, propagators
|
||||||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
||||||
|
|
@ -57,6 +48,10 @@ from opentelemetry.instrumentation.utils import http_status_to_canonical_code
|
||||||
from opentelemetry.trace import SpanKind, get_tracer
|
from opentelemetry.trace import SpanKind, get_tracer
|
||||||
from opentelemetry.trace.status import Status, StatusCanonicalCode
|
from opentelemetry.trace.status import Status, StatusCanonicalCode
|
||||||
|
|
||||||
|
# A key to a context variable to avoid creating duplicate spans when instrumenting
|
||||||
|
# both, Session.request and Session.send, since Session.request calls into Session.send
|
||||||
|
_SUPPRESS_REQUESTS_INSTRUMENTATION_KEY = "suppress_requests_instrumentation"
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def _instrument(tracer_provider=None, span_callback=None):
|
def _instrument(tracer_provider=None, span_callback=None):
|
||||||
|
|
@ -71,15 +66,54 @@ def _instrument(tracer_provider=None, span_callback=None):
|
||||||
# before v1.0.0, Dec 17, 2012, see
|
# before v1.0.0, Dec 17, 2012, see
|
||||||
# https://github.com/psf/requests/commit/4e5c4a6ab7bb0195dececdd19bb8505b872fe120)
|
# https://github.com/psf/requests/commit/4e5c4a6ab7bb0195dececdd19bb8505b872fe120)
|
||||||
|
|
||||||
wrapped = Session.request
|
wrapped_request = Session.request
|
||||||
|
wrapped_send = Session.send
|
||||||
|
|
||||||
@functools.wraps(wrapped)
|
@functools.wraps(wrapped_request)
|
||||||
def instrumented_request(self, method, url, *args, **kwargs):
|
def instrumented_request(self, method, url, *args, **kwargs):
|
||||||
if context.get_value("suppress_instrumentation"):
|
def get_or_create_headers():
|
||||||
return wrapped(self, method, url, *args, **kwargs)
|
headers = kwargs.get("headers")
|
||||||
|
if headers is None:
|
||||||
|
headers = {}
|
||||||
|
kwargs["headers"] = headers
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def call_wrapped():
|
||||||
|
return wrapped_request(self, method, url, *args, **kwargs)
|
||||||
|
|
||||||
|
return _instrumented_requests_call(
|
||||||
|
method, url, call_wrapped, get_or_create_headers
|
||||||
|
)
|
||||||
|
|
||||||
|
@functools.wraps(wrapped_send)
|
||||||
|
def instrumented_send(self, request, **kwargs):
|
||||||
|
def get_or_create_headers():
|
||||||
|
request.headers = (
|
||||||
|
request.headers
|
||||||
|
if request.headers is not None
|
||||||
|
else CaseInsensitiveDict()
|
||||||
|
)
|
||||||
|
return request.headers
|
||||||
|
|
||||||
|
def call_wrapped():
|
||||||
|
return wrapped_send(self, request, **kwargs)
|
||||||
|
|
||||||
|
return _instrumented_requests_call(
|
||||||
|
request.method, request.url, call_wrapped, get_or_create_headers
|
||||||
|
)
|
||||||
|
|
||||||
|
def _instrumented_requests_call(
|
||||||
|
method: str, url: str, call_wrapped, get_or_create_headers
|
||||||
|
):
|
||||||
|
if context.get_value("suppress_instrumentation") or context.get_value(
|
||||||
|
_SUPPRESS_REQUESTS_INSTRUMENTATION_KEY
|
||||||
|
):
|
||||||
|
return call_wrapped()
|
||||||
|
|
||||||
# See
|
# See
|
||||||
# https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client
|
# https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client
|
||||||
|
method = method.upper()
|
||||||
span_name = "HTTP {}".format(method)
|
span_name = "HTTP {}".format(method)
|
||||||
|
|
||||||
exception = None
|
exception = None
|
||||||
|
|
@ -91,17 +125,19 @@ def _instrument(tracer_provider=None, span_callback=None):
|
||||||
span.set_attribute("http.method", method.upper())
|
span.set_attribute("http.method", method.upper())
|
||||||
span.set_attribute("http.url", url)
|
span.set_attribute("http.url", url)
|
||||||
|
|
||||||
headers = kwargs.get("headers", {}) or {}
|
headers = get_or_create_headers()
|
||||||
propagators.inject(type(headers).__setitem__, headers)
|
propagators.inject(type(headers).__setitem__, headers)
|
||||||
kwargs["headers"] = headers
|
|
||||||
|
|
||||||
|
token = context.attach(
|
||||||
|
context.set_value(_SUPPRESS_REQUESTS_INSTRUMENTATION_KEY, True)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = wrapped(
|
result = call_wrapped() # *** PROCEED
|
||||||
self, method, url, *args, **kwargs
|
|
||||||
) # *** PROCEED
|
|
||||||
except Exception as exc: # pylint: disable=W0703
|
except Exception as exc: # pylint: disable=W0703
|
||||||
exception = exc
|
exception = exc
|
||||||
result = getattr(exc, "response", None)
|
result = getattr(exc, "response", None)
|
||||||
|
finally:
|
||||||
|
context.detach(token)
|
||||||
|
|
||||||
if exception is not None:
|
if exception is not None:
|
||||||
span.set_status(
|
span.set_status(
|
||||||
|
|
@ -124,24 +160,34 @@ def _instrument(tracer_provider=None, span_callback=None):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
instrumented_request.opentelemetry_ext_requests_applied = True
|
instrumented_request.opentelemetry_instrumentation_requests_applied = True
|
||||||
|
|
||||||
Session.request = instrumented_request
|
Session.request = instrumented_request
|
||||||
|
|
||||||
# TODO: We should also instrument requests.sessions.Session.send
|
instrumented_send.opentelemetry_instrumentation_requests_applied = True
|
||||||
# but to avoid doubled spans, we would need some context-local
|
Session.send = instrumented_send
|
||||||
# state (i.e., only create a Span if the current context's URL is
|
|
||||||
# different, then push the current URL, pop it afterwards)
|
|
||||||
|
|
||||||
|
|
||||||
def _uninstrument():
|
def _uninstrument():
|
||||||
# pylint: disable=global-statement
|
|
||||||
"""Disables instrumentation of :code:`requests` through this module.
|
"""Disables instrumentation of :code:`requests` through this module.
|
||||||
|
|
||||||
Note that this only works if no other module also patches requests."""
|
Note that this only works if no other module also patches requests."""
|
||||||
if getattr(Session.request, "opentelemetry_ext_requests_applied", False):
|
_uninstrument_from(Session)
|
||||||
original = Session.request.__wrapped__ # pylint:disable=no-member
|
|
||||||
Session.request = original
|
|
||||||
|
def _uninstrument_from(instr_root, restore_as_bound_func=False):
|
||||||
|
for instr_func_name in ("request", "send"):
|
||||||
|
instr_func = getattr(instr_root, instr_func_name)
|
||||||
|
if not getattr(
|
||||||
|
instr_func,
|
||||||
|
"opentelemetry_instrumentation_requests_applied",
|
||||||
|
False,
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
original = instr_func.__wrapped__ # pylint:disable=no-member
|
||||||
|
if restore_as_bound_func:
|
||||||
|
original = types.MethodType(original, instr_root)
|
||||||
|
setattr(instr_root, instr_func_name, original)
|
||||||
|
|
||||||
|
|
||||||
def _exception_to_canonical_code(exc: Exception) -> StatusCanonicalCode:
|
def _exception_to_canonical_code(exc: Exception) -> StatusCanonicalCode:
|
||||||
|
|
@ -179,8 +225,4 @@ class RequestsInstrumentor(BaseInstrumentor):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def uninstrument_session(session):
|
def uninstrument_session(session):
|
||||||
"""Disables instrumentation on the session object."""
|
"""Disables instrumentation on the session object."""
|
||||||
if getattr(
|
_uninstrument_from(session, restore_as_bound_func=True)
|
||||||
session.request, "opentelemetry_ext_requests_applied", False
|
|
||||||
):
|
|
||||||
original = session.request.__wrapped__ # pylint:disable=no-member
|
|
||||||
session.request = types.MethodType(original, session)
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import abc
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import httpretty
|
import httpretty
|
||||||
|
|
@ -26,32 +27,47 @@ from opentelemetry.test.test_base import TestBase
|
||||||
from opentelemetry.trace.status import StatusCanonicalCode
|
from opentelemetry.trace.status import StatusCanonicalCode
|
||||||
|
|
||||||
|
|
||||||
class TestRequestsIntegration(TestBase):
|
class RequestsIntegrationTestBase(abc.ABC):
|
||||||
|
# pylint: disable=no-member
|
||||||
|
|
||||||
URL = "http://httpbin.org/status/200"
|
URL = "http://httpbin.org/status/200"
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
RequestsInstrumentor().instrument()
|
RequestsInstrumentor().instrument()
|
||||||
httpretty.enable()
|
httpretty.enable()
|
||||||
httpretty.register_uri(
|
httpretty.register_uri(httpretty.GET, self.URL, body="Hello!")
|
||||||
httpretty.GET, self.URL, body="Hello!",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
RequestsInstrumentor().uninstrument()
|
RequestsInstrumentor().uninstrument()
|
||||||
httpretty.disable()
|
httpretty.disable()
|
||||||
|
|
||||||
def test_basic(self):
|
def assert_span(self, exporter=None, num_spans=1):
|
||||||
result = requests.get(self.URL)
|
if exporter is None:
|
||||||
self.assertEqual(result.text, "Hello!")
|
exporter = self.memory_exporter
|
||||||
|
span_list = exporter.get_finished_spans()
|
||||||
|
self.assertEqual(num_spans, len(span_list))
|
||||||
|
if num_spans == 0:
|
||||||
|
return None
|
||||||
|
if num_spans == 1:
|
||||||
|
return span_list[0]
|
||||||
|
return span_list
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
@staticmethod
|
||||||
self.assertEqual(len(span_list), 1)
|
@abc.abstractmethod
|
||||||
span = span_list[0]
|
def perform_request(url: str, session: requests.Session = None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
result = self.perform_request(self.URL)
|
||||||
|
self.assertEqual(result.text, "Hello!")
|
||||||
|
span = self.assert_span()
|
||||||
|
|
||||||
self.assertIs(span.kind, trace.SpanKind.CLIENT)
|
self.assertIs(span.kind, trace.SpanKind.CLIENT)
|
||||||
self.assertEqual(span.name, "HTTP get")
|
self.assertEqual(span.name, "HTTP GET")
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
span.attributes,
|
span.attributes,
|
||||||
|
|
@ -77,12 +93,10 @@ class TestRequestsIntegration(TestBase):
|
||||||
httpretty.register_uri(
|
httpretty.register_uri(
|
||||||
httpretty.GET, url_404, status=404,
|
httpretty.GET, url_404, status=404,
|
||||||
)
|
)
|
||||||
result = requests.get(url_404)
|
result = self.perform_request(url_404)
|
||||||
self.assertEqual(result.status_code, 404)
|
self.assertEqual(result.status_code, 404)
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
span = self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
span = span_list[0]
|
|
||||||
|
|
||||||
self.assertEqual(span.attributes.get("http.status_code"), 404)
|
self.assertEqual(span.attributes.get("http.status_code"), 404)
|
||||||
self.assertEqual(span.attributes.get("http.status_text"), "Not Found")
|
self.assertEqual(span.attributes.get("http.status_text"), "Not Found")
|
||||||
|
|
@ -92,31 +106,11 @@ class TestRequestsIntegration(TestBase):
|
||||||
trace.status.StatusCanonicalCode.NOT_FOUND,
|
trace.status.StatusCanonicalCode.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_url(self):
|
|
||||||
url = "http://[::1/nope"
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
requests.post(url)
|
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
span = span_list[0]
|
|
||||||
|
|
||||||
self.assertEqual(span.name, "HTTP post")
|
|
||||||
self.assertEqual(
|
|
||||||
span.attributes,
|
|
||||||
{"component": "http", "http.method": "POST", "http.url": url},
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
span.status.canonical_code, StatusCanonicalCode.INVALID_ARGUMENT
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_uninstrument(self):
|
def test_uninstrument(self):
|
||||||
RequestsInstrumentor().uninstrument()
|
RequestsInstrumentor().uninstrument()
|
||||||
result = requests.get(self.URL)
|
result = self.perform_request(self.URL)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
self.assert_span(num_spans=0)
|
||||||
self.assertEqual(len(span_list), 0)
|
|
||||||
# instrument again to avoid annoying warning message
|
# instrument again to avoid annoying warning message
|
||||||
RequestsInstrumentor().instrument()
|
RequestsInstrumentor().instrument()
|
||||||
|
|
||||||
|
|
@ -124,49 +118,43 @@ class TestRequestsIntegration(TestBase):
|
||||||
session1 = requests.Session()
|
session1 = requests.Session()
|
||||||
RequestsInstrumentor().uninstrument_session(session1)
|
RequestsInstrumentor().uninstrument_session(session1)
|
||||||
|
|
||||||
result = session1.get(self.URL)
|
result = self.perform_request(self.URL, session1)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
self.assert_span(num_spans=0)
|
||||||
self.assertEqual(len(span_list), 0)
|
|
||||||
|
|
||||||
# Test that other sessions as well as global requests is still
|
# Test that other sessions as well as global requests is still
|
||||||
# instrumented
|
# instrumented
|
||||||
session2 = requests.Session()
|
session2 = requests.Session()
|
||||||
result = session2.get(self.URL)
|
result = self.perform_request(self.URL, session2)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
|
|
||||||
self.memory_exporter.clear()
|
self.memory_exporter.clear()
|
||||||
|
|
||||||
result = requests.get(self.URL)
|
result = self.perform_request(self.URL)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
|
|
||||||
def test_suppress_instrumentation(self):
|
def test_suppress_instrumentation(self):
|
||||||
token = context.attach(
|
token = context.attach(
|
||||||
context.set_value("suppress_instrumentation", True)
|
context.set_value("suppress_instrumentation", True)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
result = requests.get(self.URL)
|
result = self.perform_request(self.URL)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
finally:
|
finally:
|
||||||
context.detach(token)
|
context.detach(token)
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
self.assert_span(num_spans=0)
|
||||||
self.assertEqual(len(span_list), 0)
|
|
||||||
|
|
||||||
def test_distributed_context(self):
|
def test_distributed_context(self):
|
||||||
previous_propagator = propagators.get_global_httptextformat()
|
previous_propagator = propagators.get_global_httptextformat()
|
||||||
try:
|
try:
|
||||||
propagators.set_global_httptextformat(MockHTTPTextFormat())
|
propagators.set_global_httptextformat(MockHTTPTextFormat())
|
||||||
result = requests.get(self.URL)
|
result = self.perform_request(self.URL)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
span = self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
span = span_list[0]
|
|
||||||
|
|
||||||
headers = dict(httpretty.last_request().headers)
|
headers = dict(httpretty.last_request().headers)
|
||||||
self.assertIn(MockHTTPTextFormat.TRACE_ID_KEY, headers)
|
self.assertIn(MockHTTPTextFormat.TRACE_ID_KEY, headers)
|
||||||
|
|
@ -195,13 +183,10 @@ class TestRequestsIntegration(TestBase):
|
||||||
tracer_provider=self.tracer_provider, span_callback=span_callback,
|
tracer_provider=self.tracer_provider, span_callback=span_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = requests.get(self.URL)
|
result = self.perform_request(self.URL)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
span = self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
span = span_list[0]
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
span.attributes,
|
span.attributes,
|
||||||
{
|
{
|
||||||
|
|
@ -221,28 +206,21 @@ class TestRequestsIntegration(TestBase):
|
||||||
RequestsInstrumentor().uninstrument()
|
RequestsInstrumentor().uninstrument()
|
||||||
RequestsInstrumentor().instrument(tracer_provider=tracer_provider)
|
RequestsInstrumentor().instrument(tracer_provider=tracer_provider)
|
||||||
|
|
||||||
result = requests.get(self.URL)
|
result = self.perform_request(self.URL)
|
||||||
self.assertEqual(result.text, "Hello!")
|
self.assertEqual(result.text, "Hello!")
|
||||||
|
|
||||||
span_list = exporter.get_finished_spans()
|
span = self.assert_span(exporter=exporter)
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
span = span_list[0]
|
|
||||||
|
|
||||||
self.assertIs(span.resource, resource)
|
self.assertIs(span.resource, resource)
|
||||||
|
|
||||||
def test_if_headers_equals_none(self):
|
@mock.patch(
|
||||||
result = requests.get(self.URL, headers=None)
|
"requests.adapters.HTTPAdapter.send",
|
||||||
self.assertEqual(result.text, "Hello!")
|
side_effect=requests.RequestException,
|
||||||
|
)
|
||||||
@mock.patch("requests.Session.send", side_effect=requests.RequestException)
|
|
||||||
def test_requests_exception_without_response(self, *_, **__):
|
def test_requests_exception_without_response(self, *_, **__):
|
||||||
|
|
||||||
with self.assertRaises(requests.RequestException):
|
with self.assertRaises(requests.RequestException):
|
||||||
requests.get(self.URL)
|
self.perform_request(self.URL)
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
span = self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
span = span_list[0]
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
span.attributes,
|
span.attributes,
|
||||||
{"component": "http", "http.method": "GET", "http.url": self.URL},
|
{"component": "http", "http.method": "GET", "http.url": self.URL},
|
||||||
|
|
@ -256,17 +234,14 @@ class TestRequestsIntegration(TestBase):
|
||||||
mocked_response.reason = "Internal Server Error"
|
mocked_response.reason = "Internal Server Error"
|
||||||
|
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
"requests.Session.send",
|
"requests.adapters.HTTPAdapter.send",
|
||||||
side_effect=requests.RequestException(response=mocked_response),
|
side_effect=requests.RequestException(response=mocked_response),
|
||||||
)
|
)
|
||||||
def test_requests_exception_with_response(self, *_, **__):
|
def test_requests_exception_with_response(self, *_, **__):
|
||||||
|
|
||||||
with self.assertRaises(requests.RequestException):
|
with self.assertRaises(requests.RequestException):
|
||||||
requests.get(self.URL)
|
self.perform_request(self.URL)
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
span = self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
span = span_list[0]
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
span.attributes,
|
span.attributes,
|
||||||
{
|
{
|
||||||
|
|
@ -281,27 +256,66 @@ class TestRequestsIntegration(TestBase):
|
||||||
span.status.canonical_code, StatusCanonicalCode.INTERNAL
|
span.status.canonical_code, StatusCanonicalCode.INTERNAL
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch("requests.Session.send", side_effect=Exception)
|
@mock.patch("requests.adapters.HTTPAdapter.send", side_effect=Exception)
|
||||||
def test_requests_basic_exception(self, *_, **__):
|
def test_requests_basic_exception(self, *_, **__):
|
||||||
|
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
requests.get(self.URL)
|
self.perform_request(self.URL)
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
span = self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
span_list[0].status.canonical_code, StatusCanonicalCode.UNKNOWN
|
span.status.canonical_code, StatusCanonicalCode.UNKNOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch("requests.Session.send", side_effect=requests.Timeout)
|
@mock.patch(
|
||||||
|
"requests.adapters.HTTPAdapter.send", side_effect=requests.Timeout
|
||||||
|
)
|
||||||
def test_requests_timeout_exception(self, *_, **__):
|
def test_requests_timeout_exception(self, *_, **__):
|
||||||
|
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
requests.get(self.URL)
|
self.perform_request(self.URL)
|
||||||
|
|
||||||
span_list = self.memory_exporter.get_finished_spans()
|
span = self.assert_span()
|
||||||
self.assertEqual(len(span_list), 1)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
span_list[0].status.canonical_code,
|
span.status.canonical_code, StatusCanonicalCode.DEADLINE_EXCEEDED
|
||||||
StatusCanonicalCode.DEADLINE_EXCEEDED,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRequestsIntegration(RequestsIntegrationTestBase, TestBase):
|
||||||
|
@staticmethod
|
||||||
|
def perform_request(url: str, session: requests.Session = None):
|
||||||
|
if session is None:
|
||||||
|
return requests.get(url)
|
||||||
|
return session.get(url)
|
||||||
|
|
||||||
|
def test_invalid_url(self):
|
||||||
|
url = "http://[::1/nope"
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
requests.post(url)
|
||||||
|
|
||||||
|
span = self.assert_span()
|
||||||
|
|
||||||
|
self.assertEqual(span.name, "HTTP POST")
|
||||||
|
self.assertEqual(
|
||||||
|
span.attributes,
|
||||||
|
{"component": "http", "http.method": "POST", "http.url": url},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
span.status.canonical_code, StatusCanonicalCode.INVALID_ARGUMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_if_headers_equals_none(self):
|
||||||
|
result = requests.get(self.URL, headers=None)
|
||||||
|
self.assertEqual(result.text, "Hello!")
|
||||||
|
self.assert_span()
|
||||||
|
|
||||||
|
|
||||||
|
class TestRequestsIntegrationPreparedRequest(
|
||||||
|
RequestsIntegrationTestBase, TestBase
|
||||||
|
):
|
||||||
|
@staticmethod
|
||||||
|
def perform_request(url: str, session: requests.Session = None):
|
||||||
|
if session is None:
|
||||||
|
session = requests.Session()
|
||||||
|
request = requests.Request("GET", url)
|
||||||
|
prepared_request = session.prepare_request(request)
|
||||||
|
return session.send(prepared_request)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue