opentelemetry-instrumentation-aiohttp-client 0.46b0__tar.gz → 0.47b0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/.gitignore +3 -0
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/PKG-INFO +5 -4
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/pyproject.toml +4 -3
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/src/opentelemetry/instrumentation/aiohttp_client/__init__.py +80 -18
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/src/opentelemetry/instrumentation/aiohttp_client/package.py +4 -0
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/src/opentelemetry/instrumentation/aiohttp_client/version.py +1 -1
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/tests/test_aiohttp_client_integration.py +335 -35
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/LICENSE +0 -0
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/README.rst +0 -0
- {opentelemetry_instrumentation_aiohttp_client-0.46b0 → opentelemetry_instrumentation_aiohttp_client-0.47b0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: opentelemetry-instrumentation-aiohttp-client
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.47b0
|
4
4
|
Summary: OpenTelemetry aiohttp client instrumentation
|
5
5
|
Project-URL: Homepage, https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-aiohttp-client
|
6
6
|
Author-email: OpenTelemetry Authors <cncf-opentelemetry-contributors@lists.cncf.io>
|
@@ -15,11 +15,12 @@ Classifier: Programming Language :: Python :: 3.8
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
18
19
|
Requires-Python: >=3.8
|
19
20
|
Requires-Dist: opentelemetry-api~=1.12
|
20
|
-
Requires-Dist: opentelemetry-instrumentation==0.
|
21
|
-
Requires-Dist: opentelemetry-semantic-conventions==0.
|
22
|
-
Requires-Dist: opentelemetry-util-http==0.
|
21
|
+
Requires-Dist: opentelemetry-instrumentation==0.47b0
|
22
|
+
Requires-Dist: opentelemetry-semantic-conventions==0.47b0
|
23
|
+
Requires-Dist: opentelemetry-util-http==0.47b0
|
23
24
|
Requires-Dist: wrapt<2.0.0,>=1.0.0
|
24
25
|
Provides-Extra: instruments
|
25
26
|
Requires-Dist: aiohttp~=3.0; extra == 'instruments'
|
@@ -22,12 +22,13 @@ classifiers = [
|
|
22
22
|
"Programming Language :: Python :: 3.9",
|
23
23
|
"Programming Language :: Python :: 3.10",
|
24
24
|
"Programming Language :: Python :: 3.11",
|
25
|
+
"Programming Language :: Python :: 3.12",
|
25
26
|
]
|
26
27
|
dependencies = [
|
27
28
|
"opentelemetry-api ~= 1.12",
|
28
|
-
"opentelemetry-instrumentation == 0.
|
29
|
-
"opentelemetry-semantic-conventions == 0.
|
30
|
-
"opentelemetry-util-http == 0.
|
29
|
+
"opentelemetry-instrumentation == 0.47b0",
|
30
|
+
"opentelemetry-semantic-conventions == 0.47b0",
|
31
|
+
"opentelemetry-util-http == 0.47b0",
|
31
32
|
"wrapt >= 1.0.0, < 2.0.0",
|
32
33
|
]
|
33
34
|
|
@@ -90,19 +90,28 @@ import yarl
|
|
90
90
|
|
91
91
|
from opentelemetry import context as context_api
|
92
92
|
from opentelemetry import trace
|
93
|
+
from opentelemetry.instrumentation._semconv import (
|
94
|
+
_get_schema_url,
|
95
|
+
_HTTPStabilityMode,
|
96
|
+
_OpenTelemetrySemanticConventionStability,
|
97
|
+
_OpenTelemetryStabilitySignalType,
|
98
|
+
_report_new,
|
99
|
+
_set_http_method,
|
100
|
+
_set_http_url,
|
101
|
+
_set_status,
|
102
|
+
)
|
93
103
|
from opentelemetry.instrumentation.aiohttp_client.package import _instruments
|
94
104
|
from opentelemetry.instrumentation.aiohttp_client.version import __version__
|
95
105
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
96
106
|
from opentelemetry.instrumentation.utils import (
|
97
|
-
http_status_to_status_code,
|
98
107
|
is_instrumentation_enabled,
|
99
108
|
unwrap,
|
100
109
|
)
|
101
110
|
from opentelemetry.propagate import inject
|
102
|
-
from opentelemetry.semconv.
|
111
|
+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
|
103
112
|
from opentelemetry.trace import Span, SpanKind, TracerProvider, get_tracer
|
104
113
|
from opentelemetry.trace.status import Status, StatusCode
|
105
|
-
from opentelemetry.util.http import remove_url_credentials
|
114
|
+
from opentelemetry.util.http import remove_url_credentials, sanitize_method
|
106
115
|
|
107
116
|
_UrlFilterT = typing.Optional[typing.Callable[[yarl.URL], str]]
|
108
117
|
_RequestHookT = typing.Optional[
|
@@ -122,11 +131,45 @@ _ResponseHookT = typing.Optional[
|
|
122
131
|
]
|
123
132
|
|
124
133
|
|
134
|
+
def _get_span_name(method: str) -> str:
|
135
|
+
method = sanitize_method(method.strip())
|
136
|
+
if method == "_OTHER":
|
137
|
+
method = "HTTP"
|
138
|
+
return method
|
139
|
+
|
140
|
+
|
141
|
+
def _set_http_status_code_attribute(
|
142
|
+
span,
|
143
|
+
status_code,
|
144
|
+
metric_attributes=None,
|
145
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT,
|
146
|
+
):
|
147
|
+
status_code_str = str(status_code)
|
148
|
+
try:
|
149
|
+
status_code = int(status_code)
|
150
|
+
except ValueError:
|
151
|
+
status_code = -1
|
152
|
+
if metric_attributes is None:
|
153
|
+
metric_attributes = {}
|
154
|
+
# When we have durations we should set metrics only once
|
155
|
+
# Also the decision to include status code on a histogram should
|
156
|
+
# not be dependent on tracing decisions.
|
157
|
+
_set_status(
|
158
|
+
span,
|
159
|
+
metric_attributes,
|
160
|
+
status_code,
|
161
|
+
status_code_str,
|
162
|
+
server_span=False,
|
163
|
+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
164
|
+
)
|
165
|
+
|
166
|
+
|
125
167
|
def create_trace_config(
|
126
168
|
url_filter: _UrlFilterT = None,
|
127
169
|
request_hook: _RequestHookT = None,
|
128
170
|
response_hook: _ResponseHookT = None,
|
129
171
|
tracer_provider: TracerProvider = None,
|
172
|
+
sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT,
|
130
173
|
) -> aiohttp.TraceConfig:
|
131
174
|
"""Create an aiohttp-compatible trace configuration.
|
132
175
|
|
@@ -167,9 +210,12 @@ def create_trace_config(
|
|
167
210
|
__name__,
|
168
211
|
__version__,
|
169
212
|
tracer_provider,
|
170
|
-
schema_url=
|
213
|
+
schema_url=_get_schema_url(sem_conv_opt_in_mode),
|
171
214
|
)
|
172
215
|
|
216
|
+
# TODO: Use this when we have durations for aiohttp-client
|
217
|
+
metric_attributes = {}
|
218
|
+
|
173
219
|
def _end_trace(trace_config_ctx: types.SimpleNamespace):
|
174
220
|
context_api.detach(trace_config_ctx.token)
|
175
221
|
trace_config_ctx.span.end()
|
@@ -183,18 +229,22 @@ def create_trace_config(
|
|
183
229
|
trace_config_ctx.span = None
|
184
230
|
return
|
185
231
|
|
186
|
-
|
187
|
-
request_span_name =
|
232
|
+
method = params.method
|
233
|
+
request_span_name = _get_span_name(method)
|
188
234
|
request_url = (
|
189
235
|
remove_url_credentials(trace_config_ctx.url_filter(params.url))
|
190
236
|
if callable(trace_config_ctx.url_filter)
|
191
237
|
else remove_url_credentials(str(params.url))
|
192
238
|
)
|
193
239
|
|
194
|
-
span_attributes = {
|
195
|
-
|
196
|
-
|
197
|
-
|
240
|
+
span_attributes = {}
|
241
|
+
_set_http_method(
|
242
|
+
span_attributes,
|
243
|
+
method,
|
244
|
+
sanitize_method(method),
|
245
|
+
sem_conv_opt_in_mode,
|
246
|
+
)
|
247
|
+
_set_http_url(span_attributes, request_url, sem_conv_opt_in_mode)
|
198
248
|
|
199
249
|
trace_config_ctx.span = trace_config_ctx.tracer.start_span(
|
200
250
|
request_span_name, kind=SpanKind.CLIENT, attributes=span_attributes
|
@@ -219,14 +269,13 @@ def create_trace_config(
|
|
219
269
|
|
220
270
|
if callable(response_hook):
|
221
271
|
response_hook(trace_config_ctx.span, params)
|
272
|
+
_set_http_status_code_attribute(
|
273
|
+
trace_config_ctx.span,
|
274
|
+
params.response.status,
|
275
|
+
metric_attributes,
|
276
|
+
sem_conv_opt_in_mode,
|
277
|
+
)
|
222
278
|
|
223
|
-
if trace_config_ctx.span.is_recording():
|
224
|
-
trace_config_ctx.span.set_status(
|
225
|
-
Status(http_status_to_status_code(int(params.response.status)))
|
226
|
-
)
|
227
|
-
trace_config_ctx.span.set_attribute(
|
228
|
-
SpanAttributes.HTTP_STATUS_CODE, params.response.status
|
229
|
-
)
|
230
279
|
_end_trace(trace_config_ctx)
|
231
280
|
|
232
281
|
async def on_request_exception(
|
@@ -238,7 +287,13 @@ def create_trace_config(
|
|
238
287
|
return
|
239
288
|
|
240
289
|
if trace_config_ctx.span.is_recording() and params.exception:
|
241
|
-
|
290
|
+
exc_type = type(params.exception).__qualname__
|
291
|
+
if _report_new(sem_conv_opt_in_mode):
|
292
|
+
trace_config_ctx.span.set_attribute(ERROR_TYPE, exc_type)
|
293
|
+
|
294
|
+
trace_config_ctx.span.set_status(
|
295
|
+
Status(StatusCode.ERROR, exc_type)
|
296
|
+
)
|
242
297
|
trace_config_ctx.span.record_exception(params.exception)
|
243
298
|
|
244
299
|
if callable(response_hook):
|
@@ -271,6 +326,7 @@ def _instrument(
|
|
271
326
|
trace_configs: typing.Optional[
|
272
327
|
typing.Sequence[aiohttp.TraceConfig]
|
273
328
|
] = None,
|
329
|
+
sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT,
|
274
330
|
):
|
275
331
|
"""Enables tracing of all ClientSessions
|
276
332
|
|
@@ -293,6 +349,7 @@ def _instrument(
|
|
293
349
|
request_hook=request_hook,
|
294
350
|
response_hook=response_hook,
|
295
351
|
tracer_provider=tracer_provider,
|
352
|
+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
296
353
|
)
|
297
354
|
trace_config._is_instrumented_by_opentelemetry = True
|
298
355
|
client_trace_configs.append(trace_config)
|
@@ -344,12 +401,17 @@ class AioHttpClientInstrumentor(BaseInstrumentor):
|
|
344
401
|
``trace_configs``: An optional list of aiohttp.TraceConfig items, allowing customize enrichment of spans
|
345
402
|
based on aiohttp events (see specification: https://docs.aiohttp.org/en/stable/tracing_reference.html)
|
346
403
|
"""
|
404
|
+
_OpenTelemetrySemanticConventionStability._initialize()
|
405
|
+
_sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
406
|
+
_OpenTelemetryStabilitySignalType.HTTP,
|
407
|
+
)
|
347
408
|
_instrument(
|
348
409
|
tracer_provider=kwargs.get("tracer_provider"),
|
349
410
|
url_filter=kwargs.get("url_filter"),
|
350
411
|
request_hook=kwargs.get("request_hook"),
|
351
412
|
response_hook=kwargs.get("response_hook"),
|
352
413
|
trace_configs=kwargs.get("trace_configs"),
|
414
|
+
sem_conv_opt_in_mode=_sem_conv_opt_in_mode,
|
353
415
|
)
|
354
416
|
|
355
417
|
def _uninstrument(self, **kwargs):
|
@@ -14,7 +14,6 @@
|
|
14
14
|
|
15
15
|
import asyncio
|
16
16
|
import contextlib
|
17
|
-
import sys
|
18
17
|
import typing
|
19
18
|
import unittest
|
20
19
|
import urllib.parse
|
@@ -29,10 +28,22 @@ from pkg_resources import iter_entry_points
|
|
29
28
|
|
30
29
|
from opentelemetry import trace as trace_api
|
31
30
|
from opentelemetry.instrumentation import aiohttp_client
|
31
|
+
from opentelemetry.instrumentation._semconv import (
|
32
|
+
OTEL_SEMCONV_STABILITY_OPT_IN,
|
33
|
+
_HTTPStabilityMode,
|
34
|
+
_OpenTelemetrySemanticConventionStability,
|
35
|
+
)
|
32
36
|
from opentelemetry.instrumentation.aiohttp_client import (
|
33
37
|
AioHttpClientInstrumentor,
|
34
38
|
)
|
35
39
|
from opentelemetry.instrumentation.utils import suppress_instrumentation
|
40
|
+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
|
41
|
+
from opentelemetry.semconv.attributes.http_attributes import (
|
42
|
+
HTTP_REQUEST_METHOD,
|
43
|
+
HTTP_REQUEST_METHOD_ORIGINAL,
|
44
|
+
HTTP_RESPONSE_STATUS_CODE,
|
45
|
+
)
|
46
|
+
from opentelemetry.semconv.attributes.url_attributes import URL_FULL
|
36
47
|
from opentelemetry.semconv.trace import SpanAttributes
|
37
48
|
from opentelemetry.test.test_base import TestBase
|
38
49
|
from opentelemetry.trace import Span, StatusCode
|
@@ -60,7 +71,23 @@ def run_with_test_server(
|
|
60
71
|
|
61
72
|
|
62
73
|
class TestAioHttpIntegration(TestBase):
|
63
|
-
|
74
|
+
|
75
|
+
_test_status_codes = (
|
76
|
+
(HTTPStatus.OK, StatusCode.UNSET),
|
77
|
+
(HTTPStatus.TEMPORARY_REDIRECT, StatusCode.UNSET),
|
78
|
+
(HTTPStatus.NOT_FOUND, StatusCode.ERROR),
|
79
|
+
(HTTPStatus.BAD_REQUEST, StatusCode.ERROR),
|
80
|
+
(HTTPStatus.SERVICE_UNAVAILABLE, StatusCode.ERROR),
|
81
|
+
(HTTPStatus.GATEWAY_TIMEOUT, StatusCode.ERROR),
|
82
|
+
)
|
83
|
+
|
84
|
+
def setUp(self):
|
85
|
+
super().setUp()
|
86
|
+
_OpenTelemetrySemanticConventionStability._initialized = False
|
87
|
+
|
88
|
+
def assert_spans(self, spans, num_spans=1):
|
89
|
+
finished_spans = self.memory_exporter.get_finished_spans()
|
90
|
+
self.assertEqual(num_spans, len(finished_spans))
|
64
91
|
self.assertEqual(
|
65
92
|
[
|
66
93
|
(
|
@@ -68,7 +95,7 @@ class TestAioHttpIntegration(TestBase):
|
|
68
95
|
(span.status.status_code, span.status.description),
|
69
96
|
dict(span.attributes),
|
70
97
|
)
|
71
|
-
for span in
|
98
|
+
for span in finished_spans
|
72
99
|
],
|
73
100
|
spans,
|
74
101
|
)
|
@@ -100,43 +127,72 @@ class TestAioHttpIntegration(TestBase):
|
|
100
127
|
return run_with_test_server(client_request, url, handler)
|
101
128
|
|
102
129
|
def test_status_codes(self):
|
103
|
-
for status_code, span_status in
|
104
|
-
(HTTPStatus.OK, StatusCode.UNSET),
|
105
|
-
(HTTPStatus.TEMPORARY_REDIRECT, StatusCode.UNSET),
|
106
|
-
(HTTPStatus.SERVICE_UNAVAILABLE, StatusCode.ERROR),
|
107
|
-
(
|
108
|
-
HTTPStatus.GATEWAY_TIMEOUT,
|
109
|
-
StatusCode.ERROR,
|
110
|
-
),
|
111
|
-
):
|
130
|
+
for status_code, span_status in self._test_status_codes:
|
112
131
|
with self.subTest(status_code=status_code):
|
132
|
+
path = "test-path?query=param#foobar"
|
113
133
|
host, port = self._http_request(
|
114
134
|
trace_config=aiohttp_client.create_trace_config(),
|
115
|
-
url="/
|
135
|
+
url=f"/{path}",
|
116
136
|
status_code=status_code,
|
117
137
|
)
|
138
|
+
url = f"http://{host}:{port}/{path}"
|
139
|
+
attributes = {
|
140
|
+
SpanAttributes.HTTP_METHOD: "GET",
|
141
|
+
SpanAttributes.HTTP_URL: url,
|
142
|
+
SpanAttributes.HTTP_STATUS_CODE: status_code,
|
143
|
+
}
|
144
|
+
spans = [("GET", (span_status, None), attributes)]
|
145
|
+
self.assert_spans(spans)
|
146
|
+
self.memory_exporter.clear()
|
118
147
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
{
|
130
|
-
SpanAttributes.HTTP_METHOD: "GET",
|
131
|
-
SpanAttributes.HTTP_URL: url,
|
132
|
-
SpanAttributes.HTTP_STATUS_CODE: int(
|
133
|
-
status_code
|
134
|
-
),
|
135
|
-
},
|
136
|
-
)
|
137
|
-
]
|
148
|
+
def test_status_codes_new_semconv(self):
|
149
|
+
for status_code, span_status in self._test_status_codes:
|
150
|
+
with self.subTest(status_code=status_code):
|
151
|
+
path = "test-path?query=param#foobar"
|
152
|
+
host, port = self._http_request(
|
153
|
+
trace_config=aiohttp_client.create_trace_config(
|
154
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP
|
155
|
+
),
|
156
|
+
url=f"/{path}",
|
157
|
+
status_code=status_code,
|
138
158
|
)
|
159
|
+
url = f"http://{host}:{port}/{path}"
|
160
|
+
attributes = {
|
161
|
+
HTTP_REQUEST_METHOD: "GET",
|
162
|
+
URL_FULL: url,
|
163
|
+
HTTP_RESPONSE_STATUS_CODE: status_code,
|
164
|
+
}
|
165
|
+
if status_code >= 400:
|
166
|
+
attributes[ERROR_TYPE] = str(status_code.value)
|
167
|
+
spans = [("GET", (span_status, None), attributes)]
|
168
|
+
self.assert_spans(spans)
|
169
|
+
self.memory_exporter.clear()
|
139
170
|
|
171
|
+
def test_status_codes_both_semconv(self):
|
172
|
+
for status_code, span_status in self._test_status_codes:
|
173
|
+
with self.subTest(status_code=status_code):
|
174
|
+
path = "test-path?query=param#foobar"
|
175
|
+
host, port = self._http_request(
|
176
|
+
trace_config=aiohttp_client.create_trace_config(
|
177
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP
|
178
|
+
),
|
179
|
+
url=f"/{path}",
|
180
|
+
status_code=status_code,
|
181
|
+
)
|
182
|
+
url = f"http://{host}:{port}/{path}"
|
183
|
+
attributes = {
|
184
|
+
HTTP_REQUEST_METHOD: "GET",
|
185
|
+
SpanAttributes.HTTP_METHOD: "GET",
|
186
|
+
URL_FULL: url,
|
187
|
+
SpanAttributes.HTTP_URL: url,
|
188
|
+
HTTP_RESPONSE_STATUS_CODE: status_code,
|
189
|
+
SpanAttributes.HTTP_STATUS_CODE: status_code,
|
190
|
+
}
|
191
|
+
if status_code >= 400:
|
192
|
+
attributes[ERROR_TYPE] = str(status_code.value)
|
193
|
+
|
194
|
+
spans = [("GET", (span_status, None), attributes)]
|
195
|
+
self.assert_spans(spans, 1)
|
140
196
|
self.memory_exporter.clear()
|
141
197
|
|
142
198
|
def test_schema_url(self):
|
@@ -154,6 +210,40 @@ class TestAioHttpIntegration(TestBase):
|
|
154
210
|
)
|
155
211
|
self.memory_exporter.clear()
|
156
212
|
|
213
|
+
def test_schema_url_new_semconv(self):
|
214
|
+
with self.subTest(status_code=200):
|
215
|
+
self._http_request(
|
216
|
+
trace_config=aiohttp_client.create_trace_config(
|
217
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP
|
218
|
+
),
|
219
|
+
url="/test-path?query=param#foobar",
|
220
|
+
status_code=200,
|
221
|
+
)
|
222
|
+
|
223
|
+
span = self.memory_exporter.get_finished_spans()[0]
|
224
|
+
self.assertEqual(
|
225
|
+
span.instrumentation_info.schema_url,
|
226
|
+
"https://opentelemetry.io/schemas/1.21.0",
|
227
|
+
)
|
228
|
+
self.memory_exporter.clear()
|
229
|
+
|
230
|
+
def test_schema_url_both_semconv(self):
|
231
|
+
with self.subTest(status_code=200):
|
232
|
+
self._http_request(
|
233
|
+
trace_config=aiohttp_client.create_trace_config(
|
234
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP
|
235
|
+
),
|
236
|
+
url="/test-path?query=param#foobar",
|
237
|
+
status_code=200,
|
238
|
+
)
|
239
|
+
|
240
|
+
span = self.memory_exporter.get_finished_spans()[0]
|
241
|
+
self.assertEqual(
|
242
|
+
span.instrumentation_info.schema_url,
|
243
|
+
"https://opentelemetry.io/schemas/1.21.0",
|
244
|
+
)
|
245
|
+
self.memory_exporter.clear()
|
246
|
+
|
157
247
|
def test_not_recording(self):
|
158
248
|
mock_tracer = mock.Mock()
|
159
249
|
mock_span = mock.Mock()
|
@@ -268,7 +358,7 @@ class TestAioHttpIntegration(TestBase):
|
|
268
358
|
[
|
269
359
|
(
|
270
360
|
"GET",
|
271
|
-
(expected_status,
|
361
|
+
(expected_status, "ClientConnectorError"),
|
272
362
|
{
|
273
363
|
SpanAttributes.HTTP_METHOD: "GET",
|
274
364
|
SpanAttributes.HTTP_URL: url,
|
@@ -278,6 +368,89 @@ class TestAioHttpIntegration(TestBase):
|
|
278
368
|
)
|
279
369
|
self.memory_exporter.clear()
|
280
370
|
|
371
|
+
def test_basic_exception(self):
|
372
|
+
async def request_handler(request):
|
373
|
+
assert "traceparent" in request.headers
|
374
|
+
|
375
|
+
host, port = self._http_request(
|
376
|
+
trace_config=aiohttp_client.create_trace_config(),
|
377
|
+
url="/test",
|
378
|
+
request_handler=request_handler,
|
379
|
+
)
|
380
|
+
span = self.memory_exporter.get_finished_spans()[0]
|
381
|
+
self.assertEqual(len(span.events), 1)
|
382
|
+
self.assertEqual(span.events[0].name, "exception")
|
383
|
+
self.assert_spans(
|
384
|
+
[
|
385
|
+
(
|
386
|
+
"GET",
|
387
|
+
(StatusCode.ERROR, "ServerDisconnectedError"),
|
388
|
+
{
|
389
|
+
SpanAttributes.HTTP_METHOD: "GET",
|
390
|
+
SpanAttributes.HTTP_URL: f"http://{host}:{port}/test",
|
391
|
+
},
|
392
|
+
)
|
393
|
+
]
|
394
|
+
)
|
395
|
+
|
396
|
+
def test_basic_exception_new_semconv(self):
|
397
|
+
async def request_handler(request):
|
398
|
+
assert "traceparent" in request.headers
|
399
|
+
|
400
|
+
host, port = self._http_request(
|
401
|
+
trace_config=aiohttp_client.create_trace_config(
|
402
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP
|
403
|
+
),
|
404
|
+
url="/test",
|
405
|
+
request_handler=request_handler,
|
406
|
+
)
|
407
|
+
span = self.memory_exporter.get_finished_spans()[0]
|
408
|
+
self.assertEqual(len(span.events), 1)
|
409
|
+
self.assertEqual(span.events[0].name, "exception")
|
410
|
+
self.assert_spans(
|
411
|
+
[
|
412
|
+
(
|
413
|
+
"GET",
|
414
|
+
(StatusCode.ERROR, "ServerDisconnectedError"),
|
415
|
+
{
|
416
|
+
HTTP_REQUEST_METHOD: "GET",
|
417
|
+
URL_FULL: f"http://{host}:{port}/test",
|
418
|
+
ERROR_TYPE: "ServerDisconnectedError",
|
419
|
+
},
|
420
|
+
)
|
421
|
+
]
|
422
|
+
)
|
423
|
+
|
424
|
+
def test_basic_exception_both_semconv(self):
|
425
|
+
async def request_handler(request):
|
426
|
+
assert "traceparent" in request.headers
|
427
|
+
|
428
|
+
host, port = self._http_request(
|
429
|
+
trace_config=aiohttp_client.create_trace_config(
|
430
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP
|
431
|
+
),
|
432
|
+
url="/test",
|
433
|
+
request_handler=request_handler,
|
434
|
+
)
|
435
|
+
span = self.memory_exporter.get_finished_spans()[0]
|
436
|
+
self.assertEqual(len(span.events), 1)
|
437
|
+
self.assertEqual(span.events[0].name, "exception")
|
438
|
+
self.assert_spans(
|
439
|
+
[
|
440
|
+
(
|
441
|
+
"GET",
|
442
|
+
(StatusCode.ERROR, "ServerDisconnectedError"),
|
443
|
+
{
|
444
|
+
HTTP_REQUEST_METHOD: "GET",
|
445
|
+
URL_FULL: f"http://{host}:{port}/test",
|
446
|
+
ERROR_TYPE: "ServerDisconnectedError",
|
447
|
+
SpanAttributes.HTTP_METHOD: "GET",
|
448
|
+
SpanAttributes.HTTP_URL: f"http://{host}:{port}/test",
|
449
|
+
},
|
450
|
+
)
|
451
|
+
]
|
452
|
+
)
|
453
|
+
|
281
454
|
def test_timeout(self):
|
282
455
|
async def request_handler(request):
|
283
456
|
await asyncio.sleep(1)
|
@@ -295,7 +468,7 @@ class TestAioHttpIntegration(TestBase):
|
|
295
468
|
[
|
296
469
|
(
|
297
470
|
"GET",
|
298
|
-
(StatusCode.ERROR,
|
471
|
+
(StatusCode.ERROR, "ServerTimeoutError"),
|
299
472
|
{
|
300
473
|
SpanAttributes.HTTP_METHOD: "GET",
|
301
474
|
SpanAttributes.HTTP_URL: f"http://{host}:{port}/test_timeout",
|
@@ -322,7 +495,7 @@ class TestAioHttpIntegration(TestBase):
|
|
322
495
|
[
|
323
496
|
(
|
324
497
|
"GET",
|
325
|
-
(StatusCode.ERROR,
|
498
|
+
(StatusCode.ERROR, "TooManyRedirects"),
|
326
499
|
{
|
327
500
|
SpanAttributes.HTTP_METHOD: "GET",
|
328
501
|
SpanAttributes.HTTP_URL: f"http://{host}:{port}/test_too_many_redirects",
|
@@ -331,6 +504,92 @@ class TestAioHttpIntegration(TestBase):
|
|
331
504
|
]
|
332
505
|
)
|
333
506
|
|
507
|
+
def test_nonstandard_http_method(self):
|
508
|
+
trace_configs = [aiohttp_client.create_trace_config()]
|
509
|
+
app = HttpServerMock("nonstandard_method")
|
510
|
+
|
511
|
+
@app.route("/status/200", methods=["NONSTANDARD"])
|
512
|
+
def index():
|
513
|
+
return ("", 405, {})
|
514
|
+
|
515
|
+
url = "http://localhost:5000/status/200"
|
516
|
+
|
517
|
+
with app.run("localhost", 5000):
|
518
|
+
with self.subTest(url=url):
|
519
|
+
|
520
|
+
async def do_request(url):
|
521
|
+
async with aiohttp.ClientSession(
|
522
|
+
trace_configs=trace_configs,
|
523
|
+
) as session:
|
524
|
+
async with session.request("NONSTANDARD", url):
|
525
|
+
pass
|
526
|
+
|
527
|
+
loop = asyncio.get_event_loop()
|
528
|
+
loop.run_until_complete(do_request(url))
|
529
|
+
|
530
|
+
self.assert_spans(
|
531
|
+
[
|
532
|
+
(
|
533
|
+
"HTTP",
|
534
|
+
(StatusCode.ERROR, None),
|
535
|
+
{
|
536
|
+
SpanAttributes.HTTP_METHOD: "_OTHER",
|
537
|
+
SpanAttributes.HTTP_URL: url,
|
538
|
+
SpanAttributes.HTTP_STATUS_CODE: int(
|
539
|
+
HTTPStatus.METHOD_NOT_ALLOWED
|
540
|
+
),
|
541
|
+
},
|
542
|
+
)
|
543
|
+
]
|
544
|
+
)
|
545
|
+
self.memory_exporter.clear()
|
546
|
+
|
547
|
+
def test_nonstandard_http_method_new_semconv(self):
|
548
|
+
trace_configs = [
|
549
|
+
aiohttp_client.create_trace_config(
|
550
|
+
sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP
|
551
|
+
)
|
552
|
+
]
|
553
|
+
app = HttpServerMock("nonstandard_method")
|
554
|
+
|
555
|
+
@app.route("/status/200", methods=["NONSTANDARD"])
|
556
|
+
def index():
|
557
|
+
return ("", 405, {})
|
558
|
+
|
559
|
+
url = "http://localhost:5000/status/200"
|
560
|
+
|
561
|
+
with app.run("localhost", 5000):
|
562
|
+
with self.subTest(url=url):
|
563
|
+
|
564
|
+
async def do_request(url):
|
565
|
+
async with aiohttp.ClientSession(
|
566
|
+
trace_configs=trace_configs,
|
567
|
+
) as session:
|
568
|
+
async with session.request("NONSTANDARD", url):
|
569
|
+
pass
|
570
|
+
|
571
|
+
loop = asyncio.get_event_loop()
|
572
|
+
loop.run_until_complete(do_request(url))
|
573
|
+
|
574
|
+
self.assert_spans(
|
575
|
+
[
|
576
|
+
(
|
577
|
+
"HTTP",
|
578
|
+
(StatusCode.ERROR, None),
|
579
|
+
{
|
580
|
+
HTTP_REQUEST_METHOD: "_OTHER",
|
581
|
+
URL_FULL: url,
|
582
|
+
HTTP_RESPONSE_STATUS_CODE: int(
|
583
|
+
HTTPStatus.METHOD_NOT_ALLOWED
|
584
|
+
),
|
585
|
+
HTTP_REQUEST_METHOD_ORIGINAL: "NONSTANDARD",
|
586
|
+
ERROR_TYPE: "405",
|
587
|
+
},
|
588
|
+
)
|
589
|
+
]
|
590
|
+
)
|
591
|
+
self.memory_exporter.clear()
|
592
|
+
|
334
593
|
def test_credential_removal(self):
|
335
594
|
trace_configs = [aiohttp_client.create_trace_config()]
|
336
595
|
|
@@ -379,6 +638,7 @@ class TestAioHttpClientInstrumentor(TestBase):
|
|
379
638
|
def setUp(self):
|
380
639
|
super().setUp()
|
381
640
|
AioHttpClientInstrumentor().instrument()
|
641
|
+
_OpenTelemetrySemanticConventionStability._initialized = False
|
382
642
|
|
383
643
|
def tearDown(self):
|
384
644
|
super().tearDown()
|
@@ -419,6 +679,46 @@ class TestAioHttpClientInstrumentor(TestBase):
|
|
419
679
|
)
|
420
680
|
self.assertEqual(200, span.attributes[SpanAttributes.HTTP_STATUS_CODE])
|
421
681
|
|
682
|
+
def test_instrument_new_semconv(self):
|
683
|
+
AioHttpClientInstrumentor().uninstrument()
|
684
|
+
with mock.patch.dict(
|
685
|
+
"os.environ", {OTEL_SEMCONV_STABILITY_OPT_IN: "http"}
|
686
|
+
):
|
687
|
+
AioHttpClientInstrumentor().instrument()
|
688
|
+
host, port = run_with_test_server(
|
689
|
+
self.get_default_request(), self.URL, self.default_handler
|
690
|
+
)
|
691
|
+
span = self.assert_spans(1)
|
692
|
+
self.assertEqual("GET", span.name)
|
693
|
+
self.assertEqual("GET", span.attributes[HTTP_REQUEST_METHOD])
|
694
|
+
self.assertEqual(
|
695
|
+
f"http://{host}:{port}/test-path",
|
696
|
+
span.attributes[URL_FULL],
|
697
|
+
)
|
698
|
+
self.assertEqual(200, span.attributes[HTTP_RESPONSE_STATUS_CODE])
|
699
|
+
|
700
|
+
def test_instrument_both_semconv(self):
|
701
|
+
AioHttpClientInstrumentor().uninstrument()
|
702
|
+
with mock.patch.dict(
|
703
|
+
"os.environ", {OTEL_SEMCONV_STABILITY_OPT_IN: "http/dup"}
|
704
|
+
):
|
705
|
+
AioHttpClientInstrumentor().instrument()
|
706
|
+
host, port = run_with_test_server(
|
707
|
+
self.get_default_request(), self.URL, self.default_handler
|
708
|
+
)
|
709
|
+
url = f"http://{host}:{port}/test-path"
|
710
|
+
attributes = {
|
711
|
+
HTTP_REQUEST_METHOD: "GET",
|
712
|
+
SpanAttributes.HTTP_METHOD: "GET",
|
713
|
+
URL_FULL: url,
|
714
|
+
SpanAttributes.HTTP_URL: url,
|
715
|
+
HTTP_RESPONSE_STATUS_CODE: 200,
|
716
|
+
SpanAttributes.HTTP_STATUS_CODE: 200,
|
717
|
+
}
|
718
|
+
span = self.assert_spans(1)
|
719
|
+
self.assertEqual("GET", span.name)
|
720
|
+
self.assertEqual(span.attributes, attributes)
|
721
|
+
|
422
722
|
def test_instrument_with_custom_trace_config(self):
|
423
723
|
trace_config = aiohttp.TraceConfig()
|
424
724
|
|
File without changes
|
File without changes
|