Skip to content

Commit a912c9e

Browse files
hectorhdzgemdnetoxrmx
authored
feat(instrumentation-aiohttp-client): Add support for HTTP metrics (#3517)
* Add metrics support * Update instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py Co-authored-by: Emídio Neto <[email protected]> * Update instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py Co-authored-by: Emídio Neto <[email protected]> * Update instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py Co-authored-by: Emídio Neto <[email protected]> * Adding tests, updating changelog * Adding server address and port to both spans and metrics --------- Co-authored-by: Emídio Neto <[email protected]> Co-authored-by: Riccardo Magliocchetti <[email protected]>
1 parent 75c73d1 commit a912c9e

File tree

5 files changed

+400
-44
lines changed

5 files changed

+400
-44
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4545
- `opentelemetry-instrumentation-fastapi`: Drop support for FastAPI versions earlier than `0.92`
4646
([#3012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3012))
4747

48+
### Added
49+
- `opentelemetry-instrumentation-aiohttp-client` Add support for HTTP metrics
50+
([#3517](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3517))
51+
4852
### Deprecated
4953

5054
- Drop support for Python 3.8, bump baseline to Python 3.9.

instrumentation/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
| Instrumentation | Supported Packages | Metrics support | Semconv status |
33
| --------------- | ------------------ | --------------- | -------------- |
44
| [opentelemetry-instrumentation-aio-pika](./opentelemetry-instrumentation-aio-pika) | aio_pika >= 7.2.0, < 10.0.0 | No | development
5-
| [opentelemetry-instrumentation-aiohttp-client](./opentelemetry-instrumentation-aiohttp-client) | aiohttp ~= 3.0 | No | migration
5+
| [opentelemetry-instrumentation-aiohttp-client](./opentelemetry-instrumentation-aiohttp-client) | aiohttp ~= 3.0 | Yes | migration
66
| [opentelemetry-instrumentation-aiohttp-server](./opentelemetry-instrumentation-aiohttp-server) | aiohttp ~= 3.0 | Yes | development
77
| [opentelemetry-instrumentation-aiokafka](./opentelemetry-instrumentation-aiokafka) | aiokafka >= 0.8, < 1.0 | No | development
88
| [opentelemetry-instrumentation-aiopg](./opentelemetry-instrumentation-aiopg) | aiopg >= 0.13.0, < 2.0.0 | No | development

instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ def response_hook(span: Span, params: typing.Union[
9090

9191
import types
9292
import typing
93+
from timeit import default_timer
9394
from typing import Collection
95+
from urllib.parse import urlparse
9496

9597
import aiohttp
9698
import wrapt
@@ -99,11 +101,20 @@ def response_hook(span: Span, params: typing.Union[
99101
from opentelemetry import context as context_api
100102
from opentelemetry import trace
101103
from opentelemetry.instrumentation._semconv import (
104+
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
105+
HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
106+
_client_duration_attrs_new,
107+
_client_duration_attrs_old,
108+
_filter_semconv_duration_attrs,
102109
_get_schema_url,
103110
_OpenTelemetrySemanticConventionStability,
104111
_OpenTelemetryStabilitySignalType,
105112
_report_new,
113+
_report_old,
114+
_set_http_host_client,
106115
_set_http_method,
116+
_set_http_net_peer_name_client,
117+
_set_http_peer_port_client,
107118
_set_http_url,
108119
_set_status,
109120
_StabilityMode,
@@ -115,8 +126,13 @@ def response_hook(span: Span, params: typing.Union[
115126
is_instrumentation_enabled,
116127
unwrap,
117128
)
129+
from opentelemetry.metrics import MeterProvider, get_meter
118130
from opentelemetry.propagate import inject
119131
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
132+
from opentelemetry.semconv.metrics import MetricInstruments
133+
from opentelemetry.semconv.metrics.http_metrics import (
134+
HTTP_CLIENT_REQUEST_DURATION,
135+
)
120136
from opentelemetry.trace import Span, SpanKind, TracerProvider, get_tracer
121137
from opentelemetry.trace.status import Status, StatusCode
122138
from opentelemetry.util.http import remove_url_credentials, sanitize_method
@@ -172,11 +188,14 @@ def _set_http_status_code_attribute(
172188
)
173189

174190

191+
# pylint: disable=too-many-locals
192+
# pylint: disable=too-many-statements
175193
def create_trace_config(
176194
url_filter: _UrlFilterT = None,
177195
request_hook: _RequestHookT = None,
178196
response_hook: _ResponseHookT = None,
179197
tracer_provider: TracerProvider = None,
198+
meter_provider: MeterProvider = None,
180199
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
181200
) -> aiohttp.TraceConfig:
182201
"""Create an aiohttp-compatible trace configuration.
@@ -205,6 +224,7 @@ def create_trace_config(
205224
:param Callable request_hook: Optional callback that can modify span name and request params.
206225
:param Callable response_hook: Optional callback that can modify span name and response params.
207226
:param tracer_provider: optional TracerProvider from which to get a Tracer
227+
:param meter_provider: optional Meter provider to use
208228
209229
:return: An object suitable for use with :py:class:`aiohttp.ClientSession`.
210230
:rtype: :py:class:`aiohttp.TraceConfig`
@@ -214,20 +234,70 @@ def create_trace_config(
214234
# Explicitly specify the type for the `request_hook` and `response_hook` param and rtype to work
215235
# around this issue.
216236

237+
schema_url = _get_schema_url(sem_conv_opt_in_mode)
238+
217239
tracer = get_tracer(
218240
__name__,
219241
__version__,
220242
tracer_provider,
221-
schema_url=_get_schema_url(sem_conv_opt_in_mode),
243+
schema_url=schema_url,
244+
)
245+
246+
meter = get_meter(
247+
__name__,
248+
__version__,
249+
meter_provider,
250+
schema_url,
222251
)
223252

224-
# TODO: Use this when we have durations for aiohttp-client
253+
start_time = 0
254+
255+
duration_histogram_old = None
256+
if _report_old(sem_conv_opt_in_mode):
257+
duration_histogram_old = meter.create_histogram(
258+
name=MetricInstruments.HTTP_CLIENT_DURATION,
259+
unit="ms",
260+
description="measures the duration of the outbound HTTP request",
261+
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
262+
)
263+
duration_histogram_new = None
264+
if _report_new(sem_conv_opt_in_mode):
265+
duration_histogram_new = meter.create_histogram(
266+
name=HTTP_CLIENT_REQUEST_DURATION,
267+
unit="s",
268+
description="Duration of HTTP client requests.",
269+
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
270+
)
271+
225272
metric_attributes = {}
226273

227274
def _end_trace(trace_config_ctx: types.SimpleNamespace):
275+
elapsed_time = max(default_timer() - trace_config_ctx.start_time, 0)
228276
context_api.detach(trace_config_ctx.token)
229277
trace_config_ctx.span.end()
230278

279+
if trace_config_ctx.duration_histogram_old is not None:
280+
duration_attrs_old = _filter_semconv_duration_attrs(
281+
metric_attributes,
282+
_client_duration_attrs_old,
283+
_client_duration_attrs_new,
284+
_StabilityMode.DEFAULT,
285+
)
286+
trace_config_ctx.duration_histogram_old.record(
287+
max(round(elapsed_time * 1000), 0),
288+
attributes=duration_attrs_old,
289+
)
290+
if trace_config_ctx.duration_histogram_new is not None:
291+
duration_attrs_new = _filter_semconv_duration_attrs(
292+
metric_attributes,
293+
_client_duration_attrs_old,
294+
_client_duration_attrs_new,
295+
_StabilityMode.HTTP,
296+
)
297+
trace_config_ctx.duration_histogram_new.record(
298+
elapsed_time, attributes=duration_attrs_new
299+
)
300+
231301
async def on_request_start(
232302
unused_session: aiohttp.ClientSession,
233303
trace_config_ctx: types.SimpleNamespace,
@@ -237,6 +307,7 @@ async def on_request_start(
237307
trace_config_ctx.span = None
238308
return
239309

310+
trace_config_ctx.start_time = default_timer()
240311
method = params.method
241312
request_span_name = _get_span_name(method)
242313
request_url = (
@@ -252,8 +323,44 @@ async def on_request_start(
252323
sanitize_method(method),
253324
sem_conv_opt_in_mode,
254325
)
326+
_set_http_method(
327+
metric_attributes,
328+
method,
329+
sanitize_method(method),
330+
sem_conv_opt_in_mode,
331+
)
255332
_set_http_url(span_attributes, request_url, sem_conv_opt_in_mode)
256333

334+
try:
335+
parsed_url = urlparse(request_url)
336+
if parsed_url.hostname:
337+
_set_http_host_client(
338+
metric_attributes,
339+
parsed_url.hostname,
340+
sem_conv_opt_in_mode,
341+
)
342+
_set_http_net_peer_name_client(
343+
metric_attributes,
344+
parsed_url.hostname,
345+
sem_conv_opt_in_mode,
346+
)
347+
if _report_new(sem_conv_opt_in_mode):
348+
_set_http_host_client(
349+
span_attributes,
350+
parsed_url.hostname,
351+
sem_conv_opt_in_mode,
352+
)
353+
if parsed_url.port:
354+
_set_http_peer_port_client(
355+
metric_attributes, parsed_url.port, sem_conv_opt_in_mode
356+
)
357+
if _report_new(sem_conv_opt_in_mode):
358+
_set_http_peer_port_client(
359+
span_attributes, parsed_url.port, sem_conv_opt_in_mode
360+
)
361+
except ValueError:
362+
pass
363+
257364
trace_config_ctx.span = trace_config_ctx.tracer.start_span(
258365
request_span_name, kind=SpanKind.CLIENT, attributes=span_attributes
259366
)
@@ -298,6 +405,7 @@ async def on_request_exception(
298405
exc_type = type(params.exception).__qualname__
299406
if _report_new(sem_conv_opt_in_mode):
300407
trace_config_ctx.span.set_attribute(ERROR_TYPE, exc_type)
408+
metric_attributes[ERROR_TYPE] = exc_type
301409

302410
trace_config_ctx.span.set_status(
303411
Status(StatusCode.ERROR, exc_type)
@@ -312,7 +420,12 @@ async def on_request_exception(
312420
def _trace_config_ctx_factory(**kwargs):
313421
kwargs.setdefault("trace_request_ctx", {})
314422
return types.SimpleNamespace(
315-
tracer=tracer, url_filter=url_filter, **kwargs
423+
tracer=tracer,
424+
url_filter=url_filter,
425+
start_time=start_time,
426+
duration_histogram_old=duration_histogram_old,
427+
duration_histogram_new=duration_histogram_new,
428+
**kwargs,
316429
)
317430

318431
trace_config = aiohttp.TraceConfig(
@@ -328,6 +441,7 @@ def _trace_config_ctx_factory(**kwargs):
328441

329442
def _instrument(
330443
tracer_provider: TracerProvider = None,
444+
meter_provider: MeterProvider = None,
331445
url_filter: _UrlFilterT = None,
332446
request_hook: _RequestHookT = None,
333447
response_hook: _ResponseHookT = None,
@@ -357,6 +471,7 @@ def instrumented_init(wrapped, instance, args, kwargs):
357471
request_hook=request_hook,
358472
response_hook=response_hook,
359473
tracer_provider=tracer_provider,
474+
meter_provider=meter_provider,
360475
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
361476
)
362477
trace_config._is_instrumented_by_opentelemetry = True
@@ -401,6 +516,7 @@ def _instrument(self, **kwargs):
401516
Args:
402517
**kwargs: Optional arguments
403518
``tracer_provider``: a TracerProvider, defaults to global
519+
``meter_provider``: a MeterProvider, defaults to global
404520
``url_filter``: A callback to process the requested URL prior to adding
405521
it as a span attribute. This can be useful to remove sensitive data
406522
such as API keys or user personal information.
@@ -415,6 +531,7 @@ def _instrument(self, **kwargs):
415531
)
416532
_instrument(
417533
tracer_provider=kwargs.get("tracer_provider"),
534+
meter_provider=kwargs.get("meter_provider"),
418535
url_filter=kwargs.get("url_filter"),
419536
request_hook=kwargs.get("request_hook"),
420537
response_hook=kwargs.get("response_hook"),

instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/package.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515

1616
_instruments = ("aiohttp ~= 3.0",)
1717

18-
_supports_metrics = False
18+
_supports_metrics = True
1919

2020
_semconv_status = "migration"

0 commit comments

Comments
 (0)