feat(instrumentation-httpx): Add support for HTTP metrics (#3513)
* Add support for metrics * Updated changelog * Update package.py * generate and lint * Update * Update --------- Co-authored-by: Tammy Baylis <96076570+tammy-baylis-swi@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
This commit is contained in:
parent
701d65b022
commit
ccf9cabeee
|
|
@ -60,8 +60,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
([#3012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3012))
|
||||
|
||||
### Added
|
||||
|
||||
- `opentelemetry-instrumentation-aiohttp-client` Add support for HTTP metrics
|
||||
([#3517](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3517))
|
||||
([#3517](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3517))
|
||||
- `opentelemetry-instrumentation-httpx` Add support for HTTP metrics
|
||||
([#3513](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3513))
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
| [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.92 | Yes | migration
|
||||
| [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0 | Yes | migration
|
||||
| [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio >= 1.42.0 | No | development
|
||||
| [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0 | No | migration
|
||||
| [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0 | Yes | migration
|
||||
| [opentelemetry-instrumentation-jinja2](./opentelemetry-instrumentation-jinja2) | jinja2 >= 2.7, < 4.0 | No | development
|
||||
| [opentelemetry-instrumentation-kafka-python](./opentelemetry-instrumentation-kafka-python) | kafka-python >= 2.0, < 3.0,kafka-python-ng >= 2.0, < 3.0 | No | development
|
||||
| [opentelemetry-instrumentation-logging](./opentelemetry-instrumentation-logging) | logging | No | development
|
||||
|
|
|
|||
|
|
@ -209,22 +209,32 @@ import logging
|
|||
import typing
|
||||
from asyncio import iscoroutinefunction
|
||||
from functools import partial
|
||||
from timeit import default_timer
|
||||
from types import TracebackType
|
||||
|
||||
import httpx
|
||||
from wrapt import wrap_function_wrapper
|
||||
|
||||
from opentelemetry.instrumentation._semconv import (
|
||||
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
||||
HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
||||
_client_duration_attrs_new,
|
||||
_client_duration_attrs_old,
|
||||
_filter_semconv_duration_attrs,
|
||||
_get_schema_url,
|
||||
_OpenTelemetrySemanticConventionStability,
|
||||
_OpenTelemetryStabilitySignalType,
|
||||
_report_new,
|
||||
_report_old,
|
||||
_set_http_host_client,
|
||||
_set_http_method,
|
||||
_set_http_net_peer_name_client,
|
||||
_set_http_network_protocol_version,
|
||||
_set_http_peer_port_client,
|
||||
_set_http_scheme,
|
||||
_set_http_status_code,
|
||||
_set_http_url,
|
||||
_set_status,
|
||||
_StabilityMode,
|
||||
)
|
||||
from opentelemetry.instrumentation.httpx.package import _instruments
|
||||
|
|
@ -235,12 +245,17 @@ from opentelemetry.instrumentation.utils import (
|
|||
is_http_instrumentation_enabled,
|
||||
unwrap,
|
||||
)
|
||||
from opentelemetry.metrics import Histogram, MeterProvider, get_meter
|
||||
from opentelemetry.propagate import inject
|
||||
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
|
||||
from opentelemetry.semconv.attributes.network_attributes import (
|
||||
NETWORK_PEER_ADDRESS,
|
||||
NETWORK_PEER_PORT,
|
||||
)
|
||||
from opentelemetry.semconv.metrics import MetricInstruments
|
||||
from opentelemetry.semconv.metrics.http_metrics import (
|
||||
HTTP_CLIENT_REQUEST_DURATION,
|
||||
)
|
||||
from opentelemetry.trace import SpanKind, Tracer, TracerProvider, get_tracer
|
||||
from opentelemetry.trace.span import Span
|
||||
from opentelemetry.trace.status import StatusCode
|
||||
|
|
@ -352,6 +367,7 @@ def _extract_response(
|
|||
|
||||
def _apply_request_client_attributes_to_span(
|
||||
span_attributes: dict[str, typing.Any],
|
||||
metric_attributes: dict[str, typing.Any],
|
||||
url: str | httpx.URL,
|
||||
method_original: str,
|
||||
semconv: _StabilityMode,
|
||||
|
|
@ -364,23 +380,44 @@ def _apply_request_client_attributes_to_span(
|
|||
sanitize_method(method_original),
|
||||
semconv,
|
||||
)
|
||||
|
||||
# http semconv transition: http.url -> url.full
|
||||
_set_http_url(span_attributes, str(url), semconv)
|
||||
|
||||
# Set HTTP method in metric labels
|
||||
_set_http_method(
|
||||
metric_attributes,
|
||||
method_original,
|
||||
sanitize_method(method_original),
|
||||
semconv,
|
||||
)
|
||||
|
||||
if _report_old(semconv):
|
||||
# TODO: Support opt-in for url.scheme in new semconv
|
||||
_set_http_scheme(metric_attributes, url.scheme, semconv)
|
||||
|
||||
if _report_new(semconv):
|
||||
if url.host:
|
||||
# http semconv transition: http.host -> server.address
|
||||
_set_http_host_client(span_attributes, url.host, semconv)
|
||||
# Add metric labels
|
||||
_set_http_host_client(metric_attributes, url.host, semconv)
|
||||
_set_http_net_peer_name_client(
|
||||
metric_attributes, url.host, semconv
|
||||
)
|
||||
# http semconv transition: net.sock.peer.addr -> network.peer.address
|
||||
span_attributes[NETWORK_PEER_ADDRESS] = url.host
|
||||
if url.port:
|
||||
# http semconv transition: net.sock.peer.port -> network.peer.port
|
||||
_set_http_peer_port_client(span_attributes, url.port, semconv)
|
||||
span_attributes[NETWORK_PEER_PORT] = url.port
|
||||
# Add metric labels
|
||||
_set_http_peer_port_client(metric_attributes, url.port, semconv)
|
||||
|
||||
|
||||
def _apply_response_client_attributes_to_span(
|
||||
span: Span,
|
||||
metric_attributes: dict[str, typing.Any],
|
||||
status_code: int,
|
||||
http_version: str,
|
||||
semconv: _StabilityMode,
|
||||
|
|
@ -396,6 +433,16 @@ def _apply_response_client_attributes_to_span(
|
|||
http_status_code = http_status_to_status_code(status_code)
|
||||
span.set_status(http_status_code)
|
||||
|
||||
# Set HTTP status code in metric attributes
|
||||
_set_status(
|
||||
span,
|
||||
metric_attributes,
|
||||
status_code,
|
||||
str(status_code),
|
||||
server_span=False,
|
||||
sem_conv_opt_in_mode=semconv,
|
||||
)
|
||||
|
||||
if http_status_code == StatusCode.ERROR and _report_new(semconv):
|
||||
# http semconv transition: new error.type
|
||||
span_attributes[ERROR_TYPE] = str(status_code)
|
||||
|
|
@ -403,10 +450,16 @@ def _apply_response_client_attributes_to_span(
|
|||
if http_version and _report_new(semconv):
|
||||
# http semconv transition: http.flavor -> network.protocol.version
|
||||
_set_http_network_protocol_version(
|
||||
span_attributes,
|
||||
metric_attributes,
|
||||
http_version.replace("HTTP/", ""),
|
||||
semconv,
|
||||
)
|
||||
if _report_new(semconv):
|
||||
_set_http_network_protocol_version(
|
||||
span_attributes,
|
||||
http_version.replace("HTTP/", ""),
|
||||
semconv,
|
||||
)
|
||||
|
||||
for key, val in span_attributes.items():
|
||||
span.set_attribute(key, val)
|
||||
|
|
@ -418,6 +471,7 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
|
|||
Args:
|
||||
transport: SyncHTTPTransport instance to wrap
|
||||
tracer_provider: Tracer provider to use
|
||||
meter_provider: Meter provider to use
|
||||
request_hook: A hook that receives the span and request that is called
|
||||
right after the span is created
|
||||
response_hook: A hook that receives the span, request, and response
|
||||
|
|
@ -428,6 +482,7 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
|
|||
self,
|
||||
transport: httpx.BaseTransport,
|
||||
tracer_provider: TracerProvider | None = None,
|
||||
meter_provider: MeterProvider | None = None,
|
||||
request_hook: RequestHook | None = None,
|
||||
response_hook: ResponseHook | None = None,
|
||||
):
|
||||
|
|
@ -435,14 +490,38 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
|
|||
self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||
_OpenTelemetryStabilitySignalType.HTTP,
|
||||
)
|
||||
schema_url = _get_schema_url(self._sem_conv_opt_in_mode)
|
||||
|
||||
self._transport = transport
|
||||
self._tracer = get_tracer(
|
||||
__name__,
|
||||
instrumenting_library_version=__version__,
|
||||
tracer_provider=tracer_provider,
|
||||
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
|
||||
schema_url=schema_url,
|
||||
)
|
||||
meter = get_meter(
|
||||
__name__,
|
||||
__version__,
|
||||
meter_provider,
|
||||
schema_url,
|
||||
)
|
||||
|
||||
self._duration_histogram_old = None
|
||||
if _report_old(self._sem_conv_opt_in_mode):
|
||||
self._duration_histogram_old = meter.create_histogram(
|
||||
name=MetricInstruments.HTTP_CLIENT_DURATION,
|
||||
unit="ms",
|
||||
description="measures the duration of the outbound HTTP request",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
||||
)
|
||||
self._duration_histogram_new = None
|
||||
if _report_new(self._sem_conv_opt_in_mode):
|
||||
self._duration_histogram_new = meter.create_histogram(
|
||||
name=HTTP_CLIENT_REQUEST_DURATION,
|
||||
unit="s",
|
||||
description="Duration of HTTP client requests.",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
||||
)
|
||||
self._request_hook = request_hook
|
||||
self._response_hook = response_hook
|
||||
|
||||
|
|
@ -477,9 +556,11 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
|
|||
method_original = method.decode()
|
||||
span_name = _get_default_span_name(method_original)
|
||||
span_attributes = {}
|
||||
metric_attributes = {}
|
||||
# apply http client response attributes according to semconv
|
||||
_apply_request_client_attributes_to_span(
|
||||
span_attributes,
|
||||
metric_attributes,
|
||||
url,
|
||||
method_original,
|
||||
self._sem_conv_opt_in_mode,
|
||||
|
|
@ -496,11 +577,15 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
|
|||
|
||||
_inject_propagation_headers(headers, args, kwargs)
|
||||
|
||||
start_time = default_timer()
|
||||
|
||||
try:
|
||||
response = self._transport.handle_request(*args, **kwargs)
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
exception = exc
|
||||
response = getattr(exc, "response", None)
|
||||
finally:
|
||||
elapsed_time = max(default_timer() - start_time, 0)
|
||||
|
||||
if isinstance(response, (httpx.Response, tuple)):
|
||||
status_code, headers, stream, extensions, http_version = (
|
||||
|
|
@ -511,6 +596,7 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
|
|||
# apply http client response attributes according to semconv
|
||||
_apply_response_client_attributes_to_span(
|
||||
span,
|
||||
metric_attributes,
|
||||
status_code,
|
||||
http_version,
|
||||
self._sem_conv_opt_in_mode,
|
||||
|
|
@ -529,8 +615,33 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
|
|||
span.set_attribute(
|
||||
ERROR_TYPE, type(exception).__qualname__
|
||||
)
|
||||
metric_attributes[ERROR_TYPE] = type(
|
||||
exception
|
||||
).__qualname__
|
||||
raise exception.with_traceback(exception.__traceback__)
|
||||
|
||||
if self._duration_histogram_old is not None:
|
||||
duration_attrs_old = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.DEFAULT,
|
||||
)
|
||||
self._duration_histogram_old.record(
|
||||
max(round(elapsed_time * 1000), 0),
|
||||
attributes=duration_attrs_old,
|
||||
)
|
||||
if self._duration_histogram_new is not None:
|
||||
duration_attrs_new = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.HTTP,
|
||||
)
|
||||
self._duration_histogram_new.record(
|
||||
elapsed_time, attributes=duration_attrs_new
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def close(self) -> None:
|
||||
|
|
@ -543,6 +654,7 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
Args:
|
||||
transport: AsyncHTTPTransport instance to wrap
|
||||
tracer_provider: Tracer provider to use
|
||||
meter_provider: Meter provider to use
|
||||
request_hook: A hook that receives the span and request that is called
|
||||
right after the span is created
|
||||
response_hook: A hook that receives the span, request, and response
|
||||
|
|
@ -553,6 +665,7 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
self,
|
||||
transport: httpx.AsyncBaseTransport,
|
||||
tracer_provider: TracerProvider | None = None,
|
||||
meter_provider: MeterProvider | None = None,
|
||||
request_hook: AsyncRequestHook | None = None,
|
||||
response_hook: AsyncResponseHook | None = None,
|
||||
):
|
||||
|
|
@ -560,14 +673,40 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||
_OpenTelemetryStabilitySignalType.HTTP,
|
||||
)
|
||||
schema_url = _get_schema_url(self._sem_conv_opt_in_mode)
|
||||
|
||||
self._transport = transport
|
||||
self._tracer = get_tracer(
|
||||
__name__,
|
||||
instrumenting_library_version=__version__,
|
||||
tracer_provider=tracer_provider,
|
||||
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
|
||||
schema_url=schema_url,
|
||||
)
|
||||
|
||||
meter = get_meter(
|
||||
__name__,
|
||||
__version__,
|
||||
meter_provider,
|
||||
schema_url,
|
||||
)
|
||||
|
||||
self._duration_histogram_old = None
|
||||
if _report_old(self._sem_conv_opt_in_mode):
|
||||
self._duration_histogram_old = meter.create_histogram(
|
||||
name=MetricInstruments.HTTP_CLIENT_DURATION,
|
||||
unit="ms",
|
||||
description="measures the duration of the outbound HTTP request",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
||||
)
|
||||
self._duration_histogram_new = None
|
||||
if _report_new(self._sem_conv_opt_in_mode):
|
||||
self._duration_histogram_new = meter.create_histogram(
|
||||
name=HTTP_CLIENT_REQUEST_DURATION,
|
||||
unit="s",
|
||||
description="Duration of HTTP client requests.",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
||||
)
|
||||
|
||||
self._request_hook = request_hook
|
||||
self._response_hook = response_hook
|
||||
|
||||
|
|
@ -600,9 +739,11 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
method_original = method.decode()
|
||||
span_name = _get_default_span_name(method_original)
|
||||
span_attributes = {}
|
||||
metric_attributes = {}
|
||||
# apply http client response attributes according to semconv
|
||||
_apply_request_client_attributes_to_span(
|
||||
span_attributes,
|
||||
metric_attributes,
|
||||
url,
|
||||
method_original,
|
||||
self._sem_conv_opt_in_mode,
|
||||
|
|
@ -619,6 +760,8 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
|
||||
_inject_propagation_headers(headers, args, kwargs)
|
||||
|
||||
start_time = default_timer()
|
||||
|
||||
try:
|
||||
response = await self._transport.handle_async_request(
|
||||
*args, **kwargs
|
||||
|
|
@ -626,6 +769,8 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
except Exception as exc: # pylint: disable=W0703
|
||||
exception = exc
|
||||
response = getattr(exc, "response", None)
|
||||
finally:
|
||||
elapsed_time = max(default_timer() - start_time, 0)
|
||||
|
||||
if isinstance(response, (httpx.Response, tuple)):
|
||||
status_code, headers, stream, extensions, http_version = (
|
||||
|
|
@ -636,6 +781,7 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
# apply http client response attributes according to semconv
|
||||
_apply_response_client_attributes_to_span(
|
||||
span,
|
||||
metric_attributes,
|
||||
status_code,
|
||||
http_version,
|
||||
self._sem_conv_opt_in_mode,
|
||||
|
|
@ -655,8 +801,34 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||
span.set_attribute(
|
||||
ERROR_TYPE, type(exception).__qualname__
|
||||
)
|
||||
metric_attributes[ERROR_TYPE] = type(
|
||||
exception
|
||||
).__qualname__
|
||||
|
||||
raise exception.with_traceback(exception.__traceback__)
|
||||
|
||||
if self._duration_histogram_old is not None:
|
||||
duration_attrs_old = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.DEFAULT,
|
||||
)
|
||||
self._duration_histogram_old.record(
|
||||
max(round(elapsed_time * 1000), 0),
|
||||
attributes=duration_attrs_old,
|
||||
)
|
||||
if self._duration_histogram_new is not None:
|
||||
duration_attrs_new = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.HTTP,
|
||||
)
|
||||
self._duration_histogram_new.record(
|
||||
elapsed_time, attributes=duration_attrs_new
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
async def aclose(self) -> None:
|
||||
|
|
@ -679,6 +851,7 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
Args:
|
||||
**kwargs: Optional arguments
|
||||
``tracer_provider``: a TracerProvider, defaults to global
|
||||
``meter_provider``: a MeterProvider, defaults to global
|
||||
``request_hook``: A ``httpx.Client`` hook that receives the span and request
|
||||
that is called right after the span is created
|
||||
``response_hook``: A ``httpx.Client`` hook that receives the span, request,
|
||||
|
|
@ -687,6 +860,7 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
``async_response_hook``: Async``response_hook`` for ``httpx.AsyncClient``
|
||||
"""
|
||||
tracer_provider = kwargs.get("tracer_provider")
|
||||
meter_provider = kwargs.get("meter_provider")
|
||||
request_hook = kwargs.get("request_hook")
|
||||
response_hook = kwargs.get("response_hook")
|
||||
async_request_hook = kwargs.get("async_request_hook")
|
||||
|
|
@ -706,19 +880,46 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||
_OpenTelemetryStabilitySignalType.HTTP,
|
||||
)
|
||||
schema_url = _get_schema_url(sem_conv_opt_in_mode)
|
||||
|
||||
tracer = get_tracer(
|
||||
__name__,
|
||||
instrumenting_library_version=__version__,
|
||||
tracer_provider=tracer_provider,
|
||||
schema_url=_get_schema_url(sem_conv_opt_in_mode),
|
||||
schema_url=schema_url,
|
||||
)
|
||||
|
||||
meter = get_meter(
|
||||
__name__,
|
||||
__version__,
|
||||
meter_provider,
|
||||
schema_url,
|
||||
)
|
||||
duration_histogram_old = None
|
||||
if _report_old(sem_conv_opt_in_mode):
|
||||
duration_histogram_old = meter.create_histogram(
|
||||
name=MetricInstruments.HTTP_CLIENT_DURATION,
|
||||
unit="ms",
|
||||
description="measures the duration of the outbound HTTP request",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
||||
)
|
||||
duration_histogram_new = None
|
||||
if _report_new(sem_conv_opt_in_mode):
|
||||
duration_histogram_new = meter.create_histogram(
|
||||
name=HTTP_CLIENT_REQUEST_DURATION,
|
||||
unit="s",
|
||||
description="Duration of HTTP client requests.",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
||||
)
|
||||
|
||||
wrap_function_wrapper(
|
||||
"httpx",
|
||||
"HTTPTransport.handle_request",
|
||||
partial(
|
||||
self._handle_request_wrapper,
|
||||
tracer=tracer,
|
||||
duration_histogram_old=duration_histogram_old,
|
||||
duration_histogram_new=duration_histogram_new,
|
||||
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
||||
request_hook=request_hook,
|
||||
response_hook=response_hook,
|
||||
|
|
@ -730,6 +931,8 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
partial(
|
||||
self._handle_async_request_wrapper,
|
||||
tracer=tracer,
|
||||
duration_histogram_old=duration_histogram_old,
|
||||
duration_histogram_new=duration_histogram_new,
|
||||
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
||||
async_request_hook=async_request_hook,
|
||||
async_response_hook=async_response_hook,
|
||||
|
|
@ -747,6 +950,8 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
args: tuple[typing.Any, ...],
|
||||
kwargs: dict[str, typing.Any],
|
||||
tracer: Tracer,
|
||||
duration_histogram_old: Histogram,
|
||||
duration_histogram_new: Histogram,
|
||||
sem_conv_opt_in_mode: _StabilityMode,
|
||||
request_hook: RequestHook,
|
||||
response_hook: ResponseHook,
|
||||
|
|
@ -760,9 +965,11 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
method_original = method.decode()
|
||||
span_name = _get_default_span_name(method_original)
|
||||
span_attributes = {}
|
||||
metric_attributes = {}
|
||||
# apply http client response attributes according to semconv
|
||||
_apply_request_client_attributes_to_span(
|
||||
span_attributes,
|
||||
metric_attributes,
|
||||
url,
|
||||
method_original,
|
||||
sem_conv_opt_in_mode,
|
||||
|
|
@ -779,11 +986,15 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
|
||||
_inject_propagation_headers(headers, args, kwargs)
|
||||
|
||||
start_time = default_timer()
|
||||
|
||||
try:
|
||||
response = wrapped(*args, **kwargs)
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
exception = exc
|
||||
response = getattr(exc, "response", None)
|
||||
finally:
|
||||
elapsed_time = max(default_timer() - start_time, 0)
|
||||
|
||||
if isinstance(response, (httpx.Response, tuple)):
|
||||
status_code, headers, stream, extensions, http_version = (
|
||||
|
|
@ -794,6 +1005,7 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
# apply http client response attributes according to semconv
|
||||
_apply_response_client_attributes_to_span(
|
||||
span,
|
||||
metric_attributes,
|
||||
status_code,
|
||||
http_version,
|
||||
sem_conv_opt_in_mode,
|
||||
|
|
@ -810,8 +1022,33 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
span.set_attribute(
|
||||
ERROR_TYPE, type(exception).__qualname__
|
||||
)
|
||||
metric_attributes[ERROR_TYPE] = type(
|
||||
exception
|
||||
).__qualname__
|
||||
raise exception.with_traceback(exception.__traceback__)
|
||||
|
||||
if duration_histogram_old is not None:
|
||||
duration_attrs_old = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.DEFAULT,
|
||||
)
|
||||
duration_histogram_old.record(
|
||||
max(round(elapsed_time * 1000), 0),
|
||||
attributes=duration_attrs_old,
|
||||
)
|
||||
if duration_histogram_new is not None:
|
||||
duration_attrs_new = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.HTTP,
|
||||
)
|
||||
duration_histogram_new.record(
|
||||
elapsed_time, attributes=duration_attrs_new
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -821,6 +1058,8 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
args: tuple[typing.Any, ...],
|
||||
kwargs: dict[str, typing.Any],
|
||||
tracer: Tracer,
|
||||
duration_histogram_old: Histogram,
|
||||
duration_histogram_new: Histogram,
|
||||
sem_conv_opt_in_mode: _StabilityMode,
|
||||
async_request_hook: AsyncRequestHook,
|
||||
async_response_hook: AsyncResponseHook,
|
||||
|
|
@ -834,9 +1073,11 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
method_original = method.decode()
|
||||
span_name = _get_default_span_name(method_original)
|
||||
span_attributes = {}
|
||||
metric_attributes = {}
|
||||
# apply http client response attributes according to semconv
|
||||
_apply_request_client_attributes_to_span(
|
||||
span_attributes,
|
||||
metric_attributes,
|
||||
url,
|
||||
method_original,
|
||||
sem_conv_opt_in_mode,
|
||||
|
|
@ -853,11 +1094,15 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
|
||||
_inject_propagation_headers(headers, args, kwargs)
|
||||
|
||||
start_time = default_timer()
|
||||
|
||||
try:
|
||||
response = await wrapped(*args, **kwargs)
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
exception = exc
|
||||
response = getattr(exc, "response", None)
|
||||
finally:
|
||||
elapsed_time = max(default_timer() - start_time, 0)
|
||||
|
||||
if isinstance(response, (httpx.Response, tuple)):
|
||||
status_code, headers, stream, extensions, http_version = (
|
||||
|
|
@ -868,6 +1113,7 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
# apply http client response attributes according to semconv
|
||||
_apply_response_client_attributes_to_span(
|
||||
span,
|
||||
metric_attributes,
|
||||
status_code,
|
||||
http_version,
|
||||
sem_conv_opt_in_mode,
|
||||
|
|
@ -887,13 +1133,37 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
)
|
||||
raise exception.with_traceback(exception.__traceback__)
|
||||
|
||||
if duration_histogram_old is not None:
|
||||
duration_attrs_old = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.DEFAULT,
|
||||
)
|
||||
duration_histogram_old.record(
|
||||
max(round(elapsed_time * 1000), 0),
|
||||
attributes=duration_attrs_old,
|
||||
)
|
||||
if duration_histogram_new is not None:
|
||||
duration_attrs_new = _filter_semconv_duration_attrs(
|
||||
metric_attributes,
|
||||
_client_duration_attrs_old,
|
||||
_client_duration_attrs_new,
|
||||
_StabilityMode.HTTP,
|
||||
)
|
||||
duration_histogram_new.record(
|
||||
elapsed_time, attributes=duration_attrs_new
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
@classmethod
|
||||
def instrument_client(
|
||||
cls,
|
||||
client: httpx.Client | httpx.AsyncClient,
|
||||
tracer_provider: TracerProvider | None = None,
|
||||
meter_provider: MeterProvider | None = None,
|
||||
request_hook: RequestHook | AsyncRequestHook | None = None,
|
||||
response_hook: ResponseHook | AsyncResponseHook | None = None,
|
||||
) -> None:
|
||||
|
|
@ -902,6 +1172,7 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
Args:
|
||||
client: The httpx Client or AsyncClient instance
|
||||
tracer_provider: A TracerProvider, defaults to global
|
||||
meter_provider: A MeterProvider, defaults to global
|
||||
request_hook: A hook that receives the span and request that is called
|
||||
right after the span is created
|
||||
response_hook: A hook that receives the span, request, and response
|
||||
|
|
@ -918,12 +1189,35 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||
_OpenTelemetryStabilitySignalType.HTTP,
|
||||
)
|
||||
schema_url = _get_schema_url(sem_conv_opt_in_mode)
|
||||
tracer = get_tracer(
|
||||
__name__,
|
||||
instrumenting_library_version=__version__,
|
||||
tracer_provider=tracer_provider,
|
||||
schema_url=_get_schema_url(sem_conv_opt_in_mode),
|
||||
schema_url=schema_url,
|
||||
)
|
||||
meter = get_meter(
|
||||
__name__,
|
||||
__version__,
|
||||
meter_provider,
|
||||
schema_url,
|
||||
)
|
||||
duration_histogram_old = None
|
||||
if _report_old(sem_conv_opt_in_mode):
|
||||
duration_histogram_old = meter.create_histogram(
|
||||
name=MetricInstruments.HTTP_CLIENT_DURATION,
|
||||
unit="ms",
|
||||
description="measures the duration of the outbound HTTP request",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
||||
)
|
||||
duration_histogram_new = None
|
||||
if _report_new(sem_conv_opt_in_mode):
|
||||
duration_histogram_new = meter.create_histogram(
|
||||
name=HTTP_CLIENT_REQUEST_DURATION,
|
||||
unit="s",
|
||||
description="Duration of HTTP client requests.",
|
||||
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
||||
)
|
||||
|
||||
if iscoroutinefunction(request_hook):
|
||||
async_request_hook = request_hook
|
||||
|
|
@ -946,6 +1240,8 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
partial(
|
||||
cls._handle_request_wrapper,
|
||||
tracer=tracer,
|
||||
duration_histogram_old=duration_histogram_old,
|
||||
duration_histogram_new=duration_histogram_new,
|
||||
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
||||
request_hook=request_hook,
|
||||
response_hook=response_hook,
|
||||
|
|
@ -959,6 +1255,8 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
partial(
|
||||
cls._handle_request_wrapper,
|
||||
tracer=tracer,
|
||||
duration_histogram_old=duration_histogram_old,
|
||||
duration_histogram_new=duration_histogram_new,
|
||||
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
||||
request_hook=request_hook,
|
||||
response_hook=response_hook,
|
||||
|
|
@ -972,6 +1270,8 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
partial(
|
||||
cls._handle_async_request_wrapper,
|
||||
tracer=tracer,
|
||||
duration_histogram_old=duration_histogram_old,
|
||||
duration_histogram_new=duration_histogram_new,
|
||||
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
||||
async_request_hook=async_request_hook,
|
||||
async_response_hook=async_response_hook,
|
||||
|
|
@ -985,6 +1285,8 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||
partial(
|
||||
cls._handle_async_request_wrapper,
|
||||
tracer=tracer,
|
||||
duration_histogram_old=duration_histogram_old,
|
||||
duration_histogram_new=duration_histogram_new,
|
||||
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
||||
async_request_hook=async_request_hook,
|
||||
async_response_hook=async_response_hook,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@
|
|||
|
||||
_instruments = ("httpx >= 0.18.0",)
|
||||
|
||||
_supports_metrics = False
|
||||
_supports_metrics = True
|
||||
|
||||
_semconv_status = "migration"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ from wrapt import ObjectProxy
|
|||
import opentelemetry.instrumentation.httpx
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation._semconv import (
|
||||
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
||||
HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
||||
OTEL_SEMCONV_STABILITY_OPT_IN,
|
||||
_OpenTelemetrySemanticConventionStability,
|
||||
)
|
||||
|
|
@ -67,6 +69,7 @@ if typing.TYPE_CHECKING:
|
|||
ResponseHook,
|
||||
ResponseInfo,
|
||||
)
|
||||
from opentelemetry.metrics import MeterProvider
|
||||
from opentelemetry.sdk.trace.export import SpanExporter
|
||||
from opentelemetry.trace import TracerProvider
|
||||
from opentelemetry.trace.span import Span
|
||||
|
|
@ -187,6 +190,11 @@ class BaseTestCases:
|
|||
return span_list[0]
|
||||
return span_list
|
||||
|
||||
def assert_metrics(self, num_metrics: int = 1):
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), num_metrics)
|
||||
return metrics
|
||||
|
||||
@abc.abstractmethod
|
||||
def perform_request(
|
||||
self,
|
||||
|
|
@ -220,6 +228,29 @@ class BaseTestCases:
|
|||
span, opentelemetry.instrumentation.httpx
|
||||
)
|
||||
|
||||
def test_basic_metrics(self):
|
||||
self.perform_request(self.URL)
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 1)
|
||||
duration_data_point = metrics[0].data.data_points[0]
|
||||
self.assertEqual(duration_data_point.count, 1)
|
||||
self.assertEqual(
|
||||
dict(duration_data_point.attributes),
|
||||
{
|
||||
SpanAttributes.HTTP_STATUS_CODE: 200,
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
SpanAttributes.HTTP_SCHEME: "http",
|
||||
},
|
||||
)
|
||||
self.assertEqual(duration_data_point.count, 1)
|
||||
self.assertTrue(duration_data_point.min >= 0)
|
||||
self.assertTrue(duration_data_point.max >= 0)
|
||||
self.assertTrue(duration_data_point.sum >= 0)
|
||||
self.assertEqual(
|
||||
duration_data_point.explicit_bounds,
|
||||
HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
||||
)
|
||||
|
||||
def test_nonstandard_http_method(self):
|
||||
respx.route(method="NONSTANDARD").mock(
|
||||
return_value=httpx.Response(405)
|
||||
|
|
@ -243,6 +274,19 @@ class BaseTestCases:
|
|||
self.assertEqualSpanInstrumentationScope(
|
||||
span, opentelemetry.instrumentation.httpx
|
||||
)
|
||||
# Validate metrics
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 1)
|
||||
duration_data_point = metrics[0].data.data_points[0]
|
||||
self.assertEqual(duration_data_point.count, 1)
|
||||
self.assertEqual(
|
||||
dict(duration_data_point.attributes),
|
||||
{
|
||||
SpanAttributes.HTTP_STATUS_CODE: 405,
|
||||
SpanAttributes.HTTP_METHOD: "_OTHER",
|
||||
SpanAttributes.HTTP_SCHEME: "http",
|
||||
},
|
||||
)
|
||||
|
||||
def test_nonstandard_http_method_new_semconv(self):
|
||||
respx.route(method="NONSTANDARD").mock(
|
||||
|
|
@ -272,6 +316,25 @@ class BaseTestCases:
|
|||
self.assertEqualSpanInstrumentationScope(
|
||||
span, opentelemetry.instrumentation.httpx
|
||||
)
|
||||
# Validate metrics
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 1)
|
||||
duration_data_point = metrics[0].data.data_points[0]
|
||||
self.assertEqual(duration_data_point.count, 1)
|
||||
self.assertEqual(
|
||||
dict(duration_data_point.attributes),
|
||||
{
|
||||
HTTP_REQUEST_METHOD: "_OTHER",
|
||||
SERVER_ADDRESS: "mock",
|
||||
HTTP_RESPONSE_STATUS_CODE: 405,
|
||||
NETWORK_PROTOCOL_VERSION: "1.1",
|
||||
ERROR_TYPE: "405",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
duration_data_point.explicit_bounds,
|
||||
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
||||
)
|
||||
|
||||
def test_basic_new_semconv(self):
|
||||
url = "http://mock:8080/status/200"
|
||||
|
|
@ -313,6 +376,22 @@ class BaseTestCases:
|
|||
span, opentelemetry.instrumentation.httpx
|
||||
)
|
||||
|
||||
# Validate metrics
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 1)
|
||||
duration_data_point = metrics[0].data.data_points[0]
|
||||
self.assertEqual(duration_data_point.count, 1)
|
||||
self.assertEqual(
|
||||
dict(duration_data_point.attributes),
|
||||
{
|
||||
SERVER_ADDRESS: "mock",
|
||||
HTTP_REQUEST_METHOD: "GET",
|
||||
HTTP_RESPONSE_STATUS_CODE: 200,
|
||||
NETWORK_PROTOCOL_VERSION: "1.1",
|
||||
SERVER_PORT: 8080,
|
||||
},
|
||||
)
|
||||
|
||||
def test_basic_both_semconv(self):
|
||||
url = "http://mock:8080/status/200" # 8080 because httpx returns None for common ports (http, https, wss)
|
||||
respx.get(url).mock(httpx.Response(200, text="Hello!"))
|
||||
|
|
@ -354,6 +433,36 @@ class BaseTestCases:
|
|||
span, opentelemetry.instrumentation.httpx
|
||||
)
|
||||
|
||||
# Validate metrics
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 2)
|
||||
# Old convention
|
||||
self.assertEqual(
|
||||
dict(metrics[0].data.data_points[0].attributes),
|
||||
{
|
||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||
SpanAttributes.HTTP_HOST: "mock",
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
SpanAttributes.HTTP_SCHEME: "http",
|
||||
SpanAttributes.NET_PEER_NAME: "mock",
|
||||
SpanAttributes.NET_PEER_PORT: 8080,
|
||||
SpanAttributes.HTTP_STATUS_CODE: 200,
|
||||
},
|
||||
)
|
||||
self.assertEqual(metrics[0].name, "http.client.duration")
|
||||
# New convention
|
||||
self.assertEqual(
|
||||
dict(metrics[1].data.data_points[0].attributes),
|
||||
{
|
||||
HTTP_REQUEST_METHOD: "GET",
|
||||
SERVER_ADDRESS: "mock",
|
||||
HTTP_RESPONSE_STATUS_CODE: 200,
|
||||
NETWORK_PROTOCOL_VERSION: "1.1",
|
||||
SERVER_PORT: 8080,
|
||||
},
|
||||
)
|
||||
self.assertEqual(metrics[1].name, "http.client.request.duration")
|
||||
|
||||
def test_basic_multiple(self):
|
||||
self.perform_request(self.URL)
|
||||
self.perform_request(self.URL)
|
||||
|
|
@ -375,6 +484,16 @@ class BaseTestCases:
|
|||
span.status.status_code,
|
||||
trace.StatusCode.ERROR,
|
||||
)
|
||||
# Validate metrics
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 1)
|
||||
duration_data_point = metrics[0].data.data_points[0]
|
||||
self.assertEqual(
|
||||
duration_data_point.attributes.get(
|
||||
SpanAttributes.HTTP_STATUS_CODE
|
||||
),
|
||||
404,
|
||||
)
|
||||
|
||||
def test_not_foundbasic_new_semconv(self):
|
||||
url_404 = "http://mock/status/404"
|
||||
|
|
@ -395,6 +514,17 @@ class BaseTestCases:
|
|||
span.status.status_code,
|
||||
trace.StatusCode.ERROR,
|
||||
)
|
||||
# Validate metrics
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 1)
|
||||
duration_data_point = metrics[0].data.data_points[0]
|
||||
self.assertEqual(
|
||||
duration_data_point.attributes.get(HTTP_RESPONSE_STATUS_CODE),
|
||||
404,
|
||||
)
|
||||
self.assertEqual(
|
||||
duration_data_point.attributes.get(ERROR_TYPE), "404"
|
||||
)
|
||||
|
||||
def test_not_foundbasic_both_semconv(self):
|
||||
url_404 = "http://mock/status/404"
|
||||
|
|
@ -417,6 +547,30 @@ class BaseTestCases:
|
|||
span.status.status_code,
|
||||
trace.StatusCode.ERROR,
|
||||
)
|
||||
# Validate metrics
|
||||
metrics = self.get_sorted_metrics()
|
||||
self.assertEqual(len(metrics), 2)
|
||||
# Old convention
|
||||
self.assertEqual(
|
||||
metrics[0]
|
||||
.data.data_points[0]
|
||||
.attributes.get(SpanAttributes.HTTP_STATUS_CODE),
|
||||
404,
|
||||
)
|
||||
self.assertEqual(
|
||||
metrics[0].data.data_points[0].attributes.get(ERROR_TYPE), None
|
||||
)
|
||||
# New convention
|
||||
self.assertEqual(
|
||||
metrics[1]
|
||||
.data.data_points[0]
|
||||
.attributes.get(HTTP_RESPONSE_STATUS_CODE),
|
||||
404,
|
||||
)
|
||||
self.assertEqual(
|
||||
metrics[1].data.data_points[0].attributes.get(ERROR_TYPE),
|
||||
"404",
|
||||
)
|
||||
|
||||
def test_suppress_instrumentation(self):
|
||||
with suppress_http_instrumentation():
|
||||
|
|
@ -584,6 +738,7 @@ class BaseTestCases:
|
|||
def create_transport(
|
||||
self,
|
||||
tracer_provider: typing.Optional["TracerProvider"] = None,
|
||||
meter_provider: typing.Optional["MeterProvider"] = None,
|
||||
request_hook: typing.Optional["RequestHook"] = None,
|
||||
response_hook: typing.Optional["ResponseHook"] = None,
|
||||
**kwargs,
|
||||
|
|
@ -623,6 +778,18 @@ class BaseTestCases:
|
|||
span = self.assert_span(exporter=exporter)
|
||||
self.assertIs(span.resource, resource)
|
||||
|
||||
def test_custom_meter_provider(self):
|
||||
meter_provider, memory_reader = self.create_meter_provider()
|
||||
transport = self.create_transport(meter_provider=meter_provider)
|
||||
client = self.create_client(transport)
|
||||
self.perform_request(self.URL, client=client)
|
||||
metrics = memory_reader.get_metrics_data().resource_metrics[0]
|
||||
self.assertEqual(len(metrics.scope_metrics), 1)
|
||||
data_point = (
|
||||
metrics.scope_metrics[0].metrics[0].data.data_points[0]
|
||||
)
|
||||
self.assertEqual(data_point.count, 1)
|
||||
|
||||
def test_response_hook(self):
|
||||
transport = self.create_transport(
|
||||
tracer_provider=self.tracer_provider,
|
||||
|
|
@ -1096,6 +1263,7 @@ class TestSyncIntegration(BaseTestCases.BaseManualTest):
|
|||
def create_transport(
|
||||
self,
|
||||
tracer_provider: typing.Optional["TracerProvider"] = None,
|
||||
meter_provider: typing.Optional["MeterProvider"] = None,
|
||||
request_hook: typing.Optional["RequestHook"] = None,
|
||||
response_hook: typing.Optional["ResponseHook"] = None,
|
||||
**kwargs,
|
||||
|
|
@ -1104,6 +1272,7 @@ class TestSyncIntegration(BaseTestCases.BaseManualTest):
|
|||
telemetry_transport = SyncOpenTelemetryTransport(
|
||||
transport,
|
||||
tracer_provider=tracer_provider,
|
||||
meter_provider=meter_provider,
|
||||
request_hook=request_hook,
|
||||
response_hook=response_hook,
|
||||
)
|
||||
|
|
@ -1127,6 +1296,11 @@ class TestSyncIntegration(BaseTestCases.BaseManualTest):
|
|||
return self.client.request(method, url, headers=headers)
|
||||
return client.request(method, url, headers=headers)
|
||||
|
||||
def test_basic(self):
|
||||
self.perform_request(self.URL)
|
||||
self.assert_span(num_spans=1)
|
||||
self.assert_metrics(num_metrics=1)
|
||||
|
||||
def test_credential_removal(self):
|
||||
new_url = "http://username:password@mock/status/200"
|
||||
self.perform_request(new_url)
|
||||
|
|
@ -1148,6 +1322,7 @@ class TestAsyncIntegration(BaseTestCases.BaseManualTest):
|
|||
def create_transport(
|
||||
self,
|
||||
tracer_provider: typing.Optional["TracerProvider"] = None,
|
||||
meter_provider: typing.Optional["MeterProvider"] = None,
|
||||
request_hook: typing.Optional["AsyncRequestHook"] = None,
|
||||
response_hook: typing.Optional["AsyncResponseHook"] = None,
|
||||
**kwargs,
|
||||
|
|
@ -1156,6 +1331,7 @@ class TestAsyncIntegration(BaseTestCases.BaseManualTest):
|
|||
telemetry_transport = AsyncOpenTelemetryTransport(
|
||||
transport,
|
||||
tracer_provider=tracer_provider,
|
||||
meter_provider=meter_provider,
|
||||
request_hook=request_hook,
|
||||
response_hook=response_hook,
|
||||
)
|
||||
|
|
@ -1195,6 +1371,7 @@ class TestAsyncIntegration(BaseTestCases.BaseManualTest):
|
|||
self.URL, client=self.create_client(self.transport)
|
||||
)
|
||||
self.assert_span(num_spans=2)
|
||||
self.assert_metrics(num_metrics=1)
|
||||
|
||||
def test_credential_removal(self):
|
||||
new_url = "http://username:password@mock/status/200"
|
||||
|
|
@ -1291,6 +1468,7 @@ class TestAsyncInstrumentationIntegration(BaseTestCases.BaseInstrumentorTest):
|
|||
self.perform_request(self.URL, client=self.client)
|
||||
self.perform_request(self.URL, client=self.client2)
|
||||
self.assert_span(num_spans=2)
|
||||
self.assert_metrics(num_metrics=1)
|
||||
|
||||
def test_async_response_hook_does_nothing_if_not_coroutine(self):
|
||||
HTTPXClientInstrumentor().instrument(
|
||||
|
|
|
|||
Loading…
Reference in New Issue