def set_code(self, code):
     self.code = code
     # use details if we already have it, otherwise the status description
     details = self.details or code.value[1]
     self._active_span.set_status(
         Status(status_code=StatusCode(code.value[0]), description=details))
     return self._servicer_context.set_code(code)
def _apply_response(span: Span,
                    response: urllib3.response.HTTPResponse) -> None:
    if not span.is_recording():
        return
    span.set_attribute("http.status_code", response.status)
    span.set_attribute("http.status_text", response.reason)
    span.set_status(Status(http_status_to_status_code(response.status)))
    def process_response(
        self, req, resp, resource, req_succeeded=None
    ):  # pylint:disable=R0201
        span = req.env.get(_ENVIRON_SPAN_KEY)
        if not span or not span.is_recording():
            return

        status = resp.status
        reason = None
        if resource is None:
            status = "404"
            reason = "NotFound"

        exc_type, exc, _ = sys.exc_info()
        if exc_type and not req_succeeded:
            if "HTTPNotFound" in exc_type.__name__:
                status = "404"
                reason = "NotFound"
            else:
                status = "500"
                reason = "{}: {}".format(exc_type.__name__, exc)

        status = status.split(" ")[0]
        try:
            status_code = int(status)
        except ValueError:
            pass
        finally:
            span.set_attribute("http.status_code", status_code)
            span.set_status(
                Status(
                    status_code=http_status_to_status_code(status_code),
                    description=reason,
                )
            )
    def intercept_unary(self, request, metadata, client_info, invoker):
        if not metadata:
            mutable_metadata = OrderedDict()
        else:
            mutable_metadata = OrderedDict(metadata)

        with self._start_guarded_span(client_info.full_method) as guarded_span:
            _inject_span_context(mutable_metadata)
            metadata = tuple(mutable_metadata.items())

            rpc_info = RpcInfo(
                full_method=client_info.full_method,
                metadata=metadata,
                timeout=client_info.timeout,
                request=request,
            )

            try:
                result = invoker(request, metadata)
            except grpc.RpcError as err:
                guarded_span.generated_span.set_status(Status(
                    StatusCode.ERROR))
                guarded_span.generated_span.set_attribute(
                    "rpc.grpc.status_code",
                    err.code().value[0])
                raise err

            return self._trace_result(guarded_span, rpc_info, result)
Example #5
0
    def use_span(
        self,
        span: trace_api.Span,
        end_on_exit: bool = False,
    ) -> Iterator[trace_api.Span]:
        try:
            token = context_api.attach(context_api.set_value(SPAN_KEY, span))
            try:
                yield span
            finally:
                context_api.detach(token)

        except Exception as exc:  # pylint: disable=broad-except
            # Record the exception as an event
            if isinstance(span, Span) and span.is_recording():
                # pylint:disable=protected-access
                if span._record_exception:
                    span.record_exception(exc)

                # Records status if use_span is used
                # i.e. with tracer.start_as_current_span() as span:
                if (span.status.status_code is StatusCode.UNSET
                        and span._set_status_on_exception):
                    span.set_status(
                        Status(
                            status_code=StatusCode.ERROR,
                            description="{}: {}".format(
                                type(exc).__name__, exc),
                        ))
            raise

        finally:
            if end_on_exit:
                span.end()
Example #6
0
    def instrumented_request(self, method, url, *args, **kwargs):
        if context.get_value("suppress_instrumentation"):
            return wrapped(self, method, url, *args, **kwargs)

        # See
        # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client
        try:
            parsed_url = urlparse(url)
        except ValueError as exc:  # Invalid URL
            path = "<Unparsable URL: {}>".format(exc)
        else:
            if parsed_url is None:
                path = "<URL parses to None>"
            path = parsed_url.path

        with tracer.start_as_current_span(path, kind=SpanKind.CLIENT) as span:
            span.set_attribute("component", "http")
            span.set_attribute("http.method", method.upper())
            span.set_attribute("http.url", url)

            headers = kwargs.setdefault("headers", {})
            propagators.inject(type(headers).__setitem__, headers)
            result = wrapped(self, method, url, *args, **kwargs)  # *** PROCEED

            span.set_attribute("http.status_code", result.status_code)
            span.set_attribute("http.status_text", result.reason)
            span.set_status(
                Status(_http_status_to_canonical_code(result.status_code)))

            return result
Example #7
0
    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""
        if exc_val is not None:
            # Record the exception as an event
            # pylint:disable=protected-access
            if self._record_exception:
                self.record_exception(exception=exc_val, escaped=True)
            # Records status if span is used as context manager
            # i.e. with tracer.start_span() as span:
            if (
                self.status.status_code is StatusCode.UNSET
                and self._set_status_on_exception
            ):
                self.set_status(
                    Status(
                        status_code=StatusCode.ERROR,
                        description="{}: {}".format(
                            exc_type.__name__, exc_val
                        ),
                    )
                )

        super().__exit__(exc_type, exc_val, exc_tb)
    def started(self, event: monitoring.CommandStartedEvent):
        """ Method to handle a pymongo CommandStartedEvent """
        if not self.is_enabled:
            return
        command = event.command.get(event.command_name, "")
        name = event.command_name
        statement = event.command_name
        if command:
            name += "." + str(command)
            statement += " " + str(command)

        try:
            span = self._tracer.start_span(name, kind=SpanKind.CLIENT)
            if span.is_recording():
                span.set_attribute(SpanAttributes.DB_SYSTEM,
                                   DbSystemValues.MONGODB.value)
                span.set_attribute(SpanAttributes.DB_NAME, event.database_name)
                span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
                if event.connection_id is not None:
                    span.set_attribute(SpanAttributes.NET_PEER_NAME,
                                       event.connection_id[0])
                    span.set_attribute(SpanAttributes.NET_PEER_PORT,
                                       event.connection_id[1])

            # Add Span to dictionary
            self._span_dict[_get_span_dict_key(event)] = span
        except Exception as ex:  # noqa pylint: disable=broad-except
            if span is not None and span.is_recording():
                span.set_status(Status(StatusCode.ERROR, str(ex)))
                span.end()
                self._pop_span(event)
    def intercept_stream(self, request_or_iterator, metadata, client_info,
                         invoker):
        if client_info.is_server_stream:
            return self._intercept_server_stream(request_or_iterator, metadata,
                                                 client_info, invoker)

        if not metadata:
            mutable_metadata = OrderedDict()
        else:
            mutable_metadata = OrderedDict(metadata)

        with self._start_guarded_span(client_info.full_method) as guarded_span:
            inject(mutable_metadata, setter=_carrier_setter)
            metadata = tuple(mutable_metadata.items())
            rpc_info = RpcInfo(
                full_method=client_info.full_method,
                metadata=metadata,
                timeout=client_info.timeout,
                request=request_or_iterator,
            )

            rpc_info.request = request_or_iterator

            try:
                result = invoker(request_or_iterator, metadata)
            except grpc.RpcError as err:
                guarded_span.generated_span.set_status(Status(
                    StatusCode.ERROR))
                guarded_span.generated_span.set_attribute(
                    SpanAttributes.RPC_GRPC_STATUS_CODE,
                    err.code().value[0],
                )
                raise err

            return self._trace_result(guarded_span, rpc_info, result)
 def __init__(
     self,
     name: str = None,
     context: trace_api.SpanContext = None,
     parent: Optional[trace_api.SpanContext] = None,
     resource: Resource = Resource.create({}),
     attributes: types.Attributes = None,
     events: Sequence[Event] = None,
     links: Sequence[trace_api.Link] = (),
     kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
     instrumentation_info: InstrumentationInfo = None,
     status: Status = Status(StatusCode.UNSET),
     start_time: Optional[int] = None,
     end_time: Optional[int] = None,
 ) -> None:
     self._name = name
     self._context = context
     self._kind = kind
     self._instrumentation_info = instrumentation_info
     self._parent = parent
     self._start_time = start_time
     self._end_time = end_time
     self._attributes = attributes
     self._events = events
     self._links = links
     self._resource = resource
     self._status = status
    def _intercept_server_stream(self, request_or_iterator, metadata,
                                 client_info, invoker):
        if not metadata:
            mutable_metadata = OrderedDict()
        else:
            mutable_metadata = OrderedDict(metadata)

        with self._start_span(client_info.full_method) as span:
            inject(mutable_metadata, setter=_carrier_setter)
            metadata = tuple(mutable_metadata.items())
            rpc_info = RpcInfo(
                full_method=client_info.full_method,
                metadata=metadata,
                timeout=client_info.timeout,
            )

            if client_info.is_client_stream:
                rpc_info.request = request_or_iterator

            try:
                result = invoker(request_or_iterator, metadata)

                for response in result:
                    yield response
            except grpc.RpcError as err:
                span.set_status(Status(StatusCode.ERROR))
                span.set_attribute(SpanAttributes.RPC_GRPC_STATUS_CODE,
                                   err.code().value[0])
                raise err
    async def _do_execute(self, func, instance, args, kwargs):
        tracer = getattr(asyncpg, _APPLIED)

        exception = None
        params = getattr(instance, "_params", {})
        name = args[0] if args[0] else params.get("database", "postgresql")

        with tracer.start_as_current_span(name, kind=SpanKind.CLIENT) as span:
            if span.is_recording():
                span_attributes = _hydrate_span_from_args(
                    instance,
                    args[0],
                    args[1:] if self.capture_parameters else None,
                )
                for attribute, value in span_attributes.items():
                    span.set_attribute(attribute, value)

            try:
                result = await func(*args, **kwargs)
            except Exception as exc:  # pylint: disable=W0703
                exception = exc
                raise
            finally:
                if span.is_recording() and exception is not None:
                    span.set_status(Status(StatusCode.ERROR))

        return result
Example #13
0
    def __call__(self, environ, start_response):
        """The WSGI application

        Args:
            environ: A WSGI environment.
            start_response: The WSGI start_response callable.
        """

        token = context.attach(propagators.extract(carrier_getter, environ))
        span_name = self.name_callback(environ)

        span = self.tracer.start_span(
            span_name,
            kind=trace.SpanKind.SERVER,
            attributes=collect_request_attributes(environ),
        )

        try:
            with self.tracer.use_span(span):
                start_response = self._create_start_response(
                    span, start_response)
                iterable = self.wsgi(environ, start_response)
                return _end_span_after_iterating(iterable, span, self.tracer,
                                                 token)
        except Exception as ex:
            if span.is_recording():
                span.set_status(Status(StatusCode.ERROR, str(ex)))
            span.end()
            context.detach(token)
            raise
Example #14
0
    def use_span(self,
                 span: trace_api.Span,
                 end_on_exit: bool = False) -> Iterator[trace_api.Span]:
        try:
            token = context_api.attach(context_api.set_value(SPAN_KEY, span))
            try:
                yield span
            finally:
                context_api.detach(token)

        except Exception as error:  # pylint: disable=broad-except
            if (isinstance(span, Span) and span.status is None
                    and span._set_status_on_exception  # pylint:disable=protected-access  # noqa
                ):
                span.set_status(
                    Status(
                        canonical_code=StatusCanonicalCode.UNKNOWN,
                        description="{}: {}".format(
                            type(error).__name__, error),
                    ))

            raise

        finally:
            if end_on_exit:
                span.end()
Example #15
0
    def instrumented_request(self, method, url, *args, **kwargs):
        if context.get_value("suppress_instrumentation"):
            return wrapped(self, method, url, *args, **kwargs)

        # See
        # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client
        try:
            parsed_url = urlparse(url)
            span_name = parsed_url.path
        except ValueError as exc:  # Invalid URL
            span_name = "<Unparsable URL: {}>".format(exc)

        exception = None

        with tracer.start_as_current_span(span_name,
                                          kind=SpanKind.CLIENT) as span:
            span.set_attribute("component", "http")
            span.set_attribute("http.method", method.upper())
            span.set_attribute("http.url", url)

            headers = kwargs.setdefault("headers", {})
            propagators.inject(type(headers).__setitem__, headers)

            try:
                result = wrapped(self, method, url, *args,
                                 **kwargs)  # *** PROCEED
            except Exception as exc:  # pylint: disable=W0703
                exception = exc
                result = getattr(exc, "response", None)

            if exception is not None:
                span.set_status(Status(
                    _exception_to_canonical_code(exception)))

            if result is not None:
                span.set_attribute("http.status_code", result.status_code)
                span.set_attribute("http.status_text", result.reason)
                span.set_status(
                    Status(_http_status_to_canonical_code(result.status_code)))

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

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

        return result
    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
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-client
        method = method.upper()
        span_name = ""
        if name_callback is not None:
            span_name = name_callback(method, url)
        if not span_name or not isinstance(span_name, str):
            span_name = get_default_span_name(method)

        labels = {}
        labels["http.method"] = method
        labels["http.url"] = url

        with get_tracer(__name__, __version__,
                        tracer_provider).start_as_current_span(
                            span_name, kind=SpanKind.CLIENT) as span:
            exception = None
            if span.is_recording():
                span.set_attribute("http.method", method)
                span.set_attribute("http.url", url)

            headers = get_or_create_headers()
            inject(type(headers).__setitem__, headers)

            token = context.attach(
                context.set_value(_SUPPRESS_REQUESTS_INSTRUMENTATION_KEY,
                                  True))
            try:
                result = call_wrapped()  # *** PROCEED
            except Exception as exc:  # pylint: disable=W0703
                exception = exc
                result = getattr(exc, "response", None)
            finally:
                context.detach(token)

            if isinstance(result, Response):
                if span.is_recording():
                    span.set_attribute("http.status_code", result.status_code)
                    span.set_attribute("http.status_text", result.reason)
                    span.set_status(
                        Status(http_status_to_status_code(result.status_code)))
                labels["http.status_code"] = str(result.status_code)
                if result.raw and result.raw.version:
                    labels["http.flavor"] = (str(result.raw.version)[:1] +
                                             "." +
                                             str(result.raw.version)[:-1])
            if span_callback is not None:
                span_callback(span, result)

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

        return result
Example #17
0
    def test_export_protobuf_max_tag_length(self):
        service_name = "test-service"

        span_context = trace_api.SpanContext(
            0x0E0C63257DE34C926F9EFCD03927272E,
            0x04BF92DEEFC58C92,
            is_remote=False,
            trace_flags=TraceFlags(TraceFlags.SAMPLED),
        )

        span = trace._Span(
            name="test-span",
            context=span_context,
        )

        span.start()
        span.resource = Resource({})
        # added here to preserve order
        span.set_attribute("k1", "v" * 500)
        span.set_attribute("k2", "v" * 50)
        span.set_status(Status(StatusCode.ERROR, "Example description"))
        span.end()

        exporter = ZipkinSpanExporter(
            service_name,
            transport_format=TRANSPORT_FORMAT_PROTOBUF,
        )
        mock_post = MagicMock()
        with patch("requests.post", mock_post):
            mock_post.return_value = MockResponse(200)
            status = exporter.export([span])
            self.assertEqual(SpanExportResult.SUCCESS, status)

        # pylint: disable=unsubscriptable-object
        kwargs = mock_post.call_args[1]
        actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"])
        span_tags = actual_spans.spans[0].tags

        self.assertEqual(len(span_tags["k1"]), 128)
        self.assertEqual(len(span_tags["k2"]), 50)

        exporter = ZipkinSpanExporter(
            service_name,
            transport_format=TRANSPORT_FORMAT_PROTOBUF,
            max_tag_value_length=2,
        )
        mock_post = MagicMock()
        with patch("requests.post", mock_post):
            mock_post.return_value = MockResponse(200)
            status = exporter.export([span])
            self.assertEqual(SpanExportResult.SUCCESS, status)

        # pylint: disable=unsubscriptable-object
        kwargs = mock_post.call_args[1]
        actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"])
        span_tags = actual_spans.spans[0].tags

        self.assertEqual(len(span_tags["k1"]), 2)
        self.assertEqual(len(span_tags["k2"]), 2)
Example #18
0
    def instrumented_request(self, method, url, *args, **kwargs):
        if context.get_value("suppress_instrumentation"):
            return wrapped(self, method, url, *args, **kwargs)

        # See
        # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client
        span_name = "HTTP {}".format(method)

        exception = None

        with get_tracer(__name__, __version__,
                        tracer_provider).start_as_current_span(
                            span_name, kind=SpanKind.CLIENT) as span:
            span.set_attribute("component", "http")
            span.set_attribute("http.method", method.upper())
            span.set_attribute("http.url", url)

            headers = kwargs.get("headers", {}) or {}
            propagators.inject(type(headers).__setitem__, headers)
            kwargs["headers"] = headers

            try:
                result = wrapped(self, method, url, *args,
                                 **kwargs)  # *** PROCEED
            except Exception as exc:  # pylint: disable=W0703
                exception = exc
                result = getattr(exc, "response", None)

            if exception is not None:
                span.set_status(Status(
                    _exception_to_canonical_code(exception)))
                span.record_exception(exception)

            if result is not None:
                span.set_attribute("http.status_code", result.status_code)
                span.set_attribute("http.status_text", result.reason)
                span.set_status(
                    Status(http_status_to_canonical_code(result.status_code)))

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

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

        return result
Example #19
0
 def abort(self, code, details):
     self.code = code
     self.details = details
     self._active_span.set_attribute("rpc.grpc.status_code", code.name)
     self._active_span.set_status(
         Status(status_code=StatusCode.ERROR, description=details)
     )
     return self._servicer_context.abort(code, details)
    def traced_execution(self, query_method: typing.Callable[..., typing.Any],
                         *args: typing.Tuple[typing.Any, typing.Any],
                         **kwargs: typing.Dict[typing.Any, typing.Any]):

        with self._db_api_integration.get_tracer().start_as_current_span(
                self._db_api_integration.name, kind=SpanKind.CLIENT) as span:
            self._populate_span(span, *args)
            try:
                result = query_method(*args, **kwargs)
                if span.is_recording():
                    span.set_status(Status(StatusCanonicalCode.OK))
                return result
            except Exception as ex:  # pylint: disable=broad-except
                if span.is_recording():
                    span.set_status(
                        Status(StatusCanonicalCode.UNKNOWN, str(ex)))
                raise ex
 def set_details(self, details):
     self.details = details
     self._active_span.set_status(
         Status(
             status_code=StatusCode(self.code.value[0]),
             description=details,
         ))
     return self._servicer_context.set_details(details)
Example #22
0
 def succeeded(self, event: monitoring.CommandSucceededEvent):
     span = self._get_span(event)
     if span is not None:
         span.set_attribute("db.mongo.duration_micros",
                            event.duration_micros)
         span.set_status(Status(StatusCanonicalCode.OK, event.reply))
         span.end()
         self._remove_span(event)
Example #23
0
 def failed(self, event: monitoring.CommandFailedEvent):
     span = self._get_span(event)
     if span is not None:
         span.set_attribute("db.mongo.duration_micros",
                            event.duration_micros)
         span.set_status(Status(StatusCanonicalCode.UNKNOWN, event.failure))
         span.end()
         self._remove_span(event)
Example #24
0
    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: Optional[trace_api.SpanContext] = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = None,
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> None:

        self.name = name
        self.context = context
        self.parent = parent
        self.sampler = sampler
        self.trace_config = trace_config
        self.resource = resource
        self.kind = kind
        self._record_exception = record_exception
        self._set_status_on_exception = set_status_on_exception

        self.span_processor = span_processor
        self.status = Status(StatusCode.UNSET)
        self._lock = threading.Lock()

        _filter_attribute_values(attributes)
        if not attributes:
            self.attributes = self._new_attributes()
        else:
            self.attributes = BoundedDict.from_map(
                SPAN_ATTRIBUTE_COUNT_LIMIT, attributes
            )

        self.events = self._new_events()
        if events:
            for event in events:
                _filter_attribute_values(event.attributes)
                # pylint: disable=protected-access
                event._attributes = _create_immutable_attributes(
                    event.attributes
                )
                self.events.append(event)

        if links is None:
            self.links = self._new_links()
        else:
            self.links = BoundedList.from_seq(SPAN_LINK_COUNT_LIMIT, links)

        self._end_time = None  # type: Optional[int]
        self._start_time = None  # type: Optional[int]
        self.instrumentation_info = instrumentation_info
Example #25
0
def add_response_attributes(span, start_response_status, response_headers):  # pylint: disable=unused-argument
    """Adds HTTP response attributes to span using the arguments
    passed to a PEP3333-conforming start_response callable."""

    status_code, status_text = start_response_status.split(" ", 1)
    span.set_attribute("http.status_text", status_text)

    try:
        status_code = int(status_code)
    except ValueError:
        span.set_status(
            Status(
                StatusCanonicalCode.UNKNOWN,
                "Non-integer HTTP status: " + repr(status_code),
            ))
    else:
        span.set_attribute("http.status_code", status_code)
        span.set_status(Status(http_status_to_canonical_code(status_code)))
Example #26
0
def add_response_attributes(span, start_response_status, response_headers):  # pylint: disable=unused-argument
    """Adds HTTP response attributes to span using the arguments
    passed to a PEP3333-conforming start_response callable."""
    if not span.is_recording():
        return
    status_code, _ = start_response_status.split(" ", 1)

    try:
        status_code = int(status_code)
    except ValueError:
        span.set_status(
            Status(
                StatusCode.ERROR,
                "Non-integer HTTP status: " + repr(status_code),
            ))
    else:
        span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
        span.set_status(Status(http_status_to_status_code(status_code)))
def set_status_code(span, status_code):
    """Adds HTTP response attributes to span using the status_code argument."""
    if not span.is_recording():
        return
    try:
        status_code = int(status_code)
    except ValueError:
        span.set_status(
            Status(
                StatusCode.ERROR,
                "Non-integer HTTP status: " + repr(status_code),
            )
        )
    else:
        span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
        span.set_status(
            Status(http_status_to_status_code(status_code, server_span=True))
        )
 def test_invalid_description(self):
     with self.assertLogs(level=WARNING) as warning:
         status = Status(description={"test": "val"})  # type: ignore
         self.assertIs(status.status_code, StatusCode.UNSET)
         self.assertEqual(status.description, None)
         self.assertIn(
             "Invalid status description type, expected str",
             warning.output[0],
         )
Example #29
0
 def set_details(self, details):
     self.details = details
     if self.code != grpc.StatusCode.OK:
         self._active_span.set_status(
             Status(
                 status_code=StatusCode.ERROR,
                 description="{}:{}".format(self.code, details),
             ))
     return self._servicer_context.set_details(details)
Example #30
0
    def _instrumented_requests_call(
        method: str, url: str, call_wrapped, get_or_create_headers
    ):
        if context.get_value(
            _SUPPRESS_INSTRUMENTATION_KEY
        ) or context.get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY):
            return call_wrapped()

        # See
        # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-client
        method = method.upper()
        span_name = ""
        if name_callback is not None:
            span_name = name_callback(method, url)
        if not span_name or not isinstance(span_name, str):
            span_name = get_default_span_name(method)

        url = remove_url_credentials(url)

        with tracer.start_as_current_span(
            span_name, kind=SpanKind.CLIENT
        ) as span:
            exception = None
            if span.is_recording():
                span.set_attribute(SpanAttributes.HTTP_METHOD, method)
                span.set_attribute(SpanAttributes.HTTP_URL, url)

            headers = get_or_create_headers()
            inject(headers)

            token = context.attach(
                context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True)
            )
            try:
                result = call_wrapped()  # *** PROCEED
            except Exception as exc:  # pylint: disable=W0703
                exception = exc
                result = getattr(exc, "response", None)
            finally:
                context.detach(token)

            if isinstance(result, Response):
                if span.is_recording():
                    span.set_attribute(
                        SpanAttributes.HTTP_STATUS_CODE, result.status_code
                    )
                    span.set_status(
                        Status(http_status_to_status_code(result.status_code))
                    )
            if span_callback is not None:
                span_callback(span, result)

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

        return result