opentelemetry-instrumentation-aiohttp-server 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_server-0.47b0/.gitignore +63 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/PKG-INFO +51 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/README.rst +24 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/pyproject.toml +56 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/src/opentelemetry/instrumentation/aiohttp_server/__init__.py +267 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/src/opentelemetry/instrumentation/aiohttp_server/package.py +16 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/src/opentelemetry/instrumentation/aiohttp_server/version.py +15 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/tests/__init__.py +0 -0
- opentelemetry_instrumentation_aiohttp_server-0.47b0/tests/test_aiohttp_server_integration.py +155 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
*.py[cod]
|
|
2
|
+
*.sw[op]
|
|
3
|
+
|
|
4
|
+
# C extensions
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# Packages
|
|
8
|
+
*.egg
|
|
9
|
+
*.egg-info
|
|
10
|
+
dist
|
|
11
|
+
dist-info
|
|
12
|
+
build
|
|
13
|
+
eggs
|
|
14
|
+
parts
|
|
15
|
+
bin
|
|
16
|
+
var
|
|
17
|
+
sdist
|
|
18
|
+
develop-eggs
|
|
19
|
+
.installed.cfg
|
|
20
|
+
pyvenv.cfg
|
|
21
|
+
lib
|
|
22
|
+
lib64
|
|
23
|
+
__pycache__
|
|
24
|
+
venv*/
|
|
25
|
+
.venv*/
|
|
26
|
+
|
|
27
|
+
# Installer logs
|
|
28
|
+
pip-log.txt
|
|
29
|
+
|
|
30
|
+
# Unit test / coverage reports
|
|
31
|
+
coverage.xml
|
|
32
|
+
.coverage
|
|
33
|
+
.nox
|
|
34
|
+
.tox
|
|
35
|
+
.cache
|
|
36
|
+
htmlcov
|
|
37
|
+
|
|
38
|
+
# Translations
|
|
39
|
+
*.mo
|
|
40
|
+
|
|
41
|
+
# Mac
|
|
42
|
+
.DS_Store
|
|
43
|
+
|
|
44
|
+
# Mr Developer
|
|
45
|
+
.mr.developer.cfg
|
|
46
|
+
.project
|
|
47
|
+
.pydevproject
|
|
48
|
+
|
|
49
|
+
# JetBrains
|
|
50
|
+
.idea
|
|
51
|
+
|
|
52
|
+
# VSCode
|
|
53
|
+
.vscode
|
|
54
|
+
|
|
55
|
+
# Sphinx
|
|
56
|
+
_build/
|
|
57
|
+
|
|
58
|
+
# mypy
|
|
59
|
+
.mypy_cache/
|
|
60
|
+
target
|
|
61
|
+
|
|
62
|
+
# Benchmark result files
|
|
63
|
+
*-benchmark.json
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: opentelemetry-instrumentation-aiohttp-server
|
|
3
|
+
Version: 0.47b0
|
|
4
|
+
Summary: Aiohttp server instrumentation for OpenTelemetry
|
|
5
|
+
Project-URL: Homepage, https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-aiohttp-server
|
|
6
|
+
Author-email: OpenTelemetry Authors <cncf-opentelemetry-contributors@lists.cncf.io>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Requires-Dist: opentelemetry-api~=1.12
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation==0.47b0
|
|
21
|
+
Requires-Dist: opentelemetry-semantic-conventions==0.47b0
|
|
22
|
+
Requires-Dist: opentelemetry-util-http==0.47b0
|
|
23
|
+
Requires-Dist: wrapt<2.0.0,>=1.0.0
|
|
24
|
+
Provides-Extra: instruments
|
|
25
|
+
Requires-Dist: aiohttp~=3.0; extra == 'instruments'
|
|
26
|
+
Description-Content-Type: text/x-rst
|
|
27
|
+
|
|
28
|
+
OpenTelemetry aiohttp server Integration
|
|
29
|
+
========================================
|
|
30
|
+
|
|
31
|
+
|pypi|
|
|
32
|
+
|
|
33
|
+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-aiohttp-client.svg
|
|
34
|
+
:target: https://pypi.org/project/opentelemetry-instrumentation-aiohttp-client/
|
|
35
|
+
|
|
36
|
+
This library allows tracing HTTP requests made by the
|
|
37
|
+
`aiohttp server <https://docs.aiohttp.org/en/stable/server.html>`_ library.
|
|
38
|
+
|
|
39
|
+
Installation
|
|
40
|
+
------------
|
|
41
|
+
|
|
42
|
+
::
|
|
43
|
+
|
|
44
|
+
pip install opentelemetry-instrumentation-aiohttp-server
|
|
45
|
+
|
|
46
|
+
References
|
|
47
|
+
----------
|
|
48
|
+
|
|
49
|
+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
|
|
50
|
+
* `aiohttp client Tracing <https://docs.aiohttp.org/en/stable/tracing_reference.html>`_
|
|
51
|
+
* `OpenTelemetry Python Examples <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples>`_
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
OpenTelemetry aiohttp server Integration
|
|
2
|
+
========================================
|
|
3
|
+
|
|
4
|
+
|pypi|
|
|
5
|
+
|
|
6
|
+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-aiohttp-client.svg
|
|
7
|
+
:target: https://pypi.org/project/opentelemetry-instrumentation-aiohttp-client/
|
|
8
|
+
|
|
9
|
+
This library allows tracing HTTP requests made by the
|
|
10
|
+
`aiohttp server <https://docs.aiohttp.org/en/stable/server.html>`_ library.
|
|
11
|
+
|
|
12
|
+
Installation
|
|
13
|
+
------------
|
|
14
|
+
|
|
15
|
+
::
|
|
16
|
+
|
|
17
|
+
pip install opentelemetry-instrumentation-aiohttp-server
|
|
18
|
+
|
|
19
|
+
References
|
|
20
|
+
----------
|
|
21
|
+
|
|
22
|
+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
|
|
23
|
+
* `aiohttp client Tracing <https://docs.aiohttp.org/en/stable/tracing_reference.html>`_
|
|
24
|
+
* `OpenTelemetry Python Examples <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples>`_
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "opentelemetry-instrumentation-aiohttp-server"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Aiohttp server instrumentation for OpenTelemetry"
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io"}
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: Apache Software License",
|
|
19
|
+
"Programming Language :: Python",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"opentelemetry-api ~= 1.12",
|
|
29
|
+
"opentelemetry-instrumentation == 0.47b0",
|
|
30
|
+
"opentelemetry-semantic-conventions == 0.47b0",
|
|
31
|
+
"opentelemetry-util-http == 0.47b0",
|
|
32
|
+
"wrapt >= 1.0.0, < 2.0.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
instruments = [
|
|
37
|
+
"aiohttp ~= 3.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.entry-points.opentelemetry_instrumentor]
|
|
41
|
+
aiohttp-server = "opentelemetry.instrumentation.aiohttp_server:AioHttpServerInstrumentor"
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-aiohttp-server"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.version]
|
|
47
|
+
path = "src/opentelemetry/instrumentation/aiohttp_server/version.py"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.sdist]
|
|
50
|
+
include = [
|
|
51
|
+
"/src",
|
|
52
|
+
"/tests",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/opentelemetry"]
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Copyright 2020, OpenTelemetry Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import urllib
|
|
16
|
+
from timeit import default_timer
|
|
17
|
+
from typing import Dict, List, Tuple, Union
|
|
18
|
+
|
|
19
|
+
from aiohttp import web
|
|
20
|
+
from multidict import CIMultiDictProxy
|
|
21
|
+
|
|
22
|
+
from opentelemetry import metrics, trace
|
|
23
|
+
from opentelemetry.instrumentation.aiohttp_server.package import _instruments
|
|
24
|
+
from opentelemetry.instrumentation.aiohttp_server.version import __version__
|
|
25
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
26
|
+
from opentelemetry.instrumentation.utils import (
|
|
27
|
+
http_status_to_status_code,
|
|
28
|
+
is_http_instrumentation_enabled,
|
|
29
|
+
)
|
|
30
|
+
from opentelemetry.propagate import extract
|
|
31
|
+
from opentelemetry.propagators.textmap import Getter
|
|
32
|
+
from opentelemetry.semconv.metrics import MetricInstruments
|
|
33
|
+
from opentelemetry.semconv.trace import SpanAttributes
|
|
34
|
+
from opentelemetry.trace.status import Status, StatusCode
|
|
35
|
+
from opentelemetry.util.http import get_excluded_urls, remove_url_credentials
|
|
36
|
+
|
|
37
|
+
_duration_attrs = [
|
|
38
|
+
SpanAttributes.HTTP_METHOD,
|
|
39
|
+
SpanAttributes.HTTP_HOST,
|
|
40
|
+
SpanAttributes.HTTP_SCHEME,
|
|
41
|
+
SpanAttributes.HTTP_STATUS_CODE,
|
|
42
|
+
SpanAttributes.HTTP_FLAVOR,
|
|
43
|
+
SpanAttributes.HTTP_SERVER_NAME,
|
|
44
|
+
SpanAttributes.NET_HOST_NAME,
|
|
45
|
+
SpanAttributes.NET_HOST_PORT,
|
|
46
|
+
SpanAttributes.HTTP_ROUTE,
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
_active_requests_count_attrs = [
|
|
50
|
+
SpanAttributes.HTTP_METHOD,
|
|
51
|
+
SpanAttributes.HTTP_HOST,
|
|
52
|
+
SpanAttributes.HTTP_SCHEME,
|
|
53
|
+
SpanAttributes.HTTP_FLAVOR,
|
|
54
|
+
SpanAttributes.HTTP_SERVER_NAME,
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
tracer = trace.get_tracer(__name__)
|
|
58
|
+
meter = metrics.get_meter(__name__, __version__)
|
|
59
|
+
_excluded_urls = get_excluded_urls("AIOHTTP_SERVER")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _parse_duration_attrs(req_attrs):
|
|
63
|
+
duration_attrs = {}
|
|
64
|
+
for attr_key in _duration_attrs:
|
|
65
|
+
if req_attrs.get(attr_key) is not None:
|
|
66
|
+
duration_attrs[attr_key] = req_attrs[attr_key]
|
|
67
|
+
return duration_attrs
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _parse_active_request_count_attrs(req_attrs):
|
|
71
|
+
active_requests_count_attrs = {}
|
|
72
|
+
for attr_key in _active_requests_count_attrs:
|
|
73
|
+
if req_attrs.get(attr_key) is not None:
|
|
74
|
+
active_requests_count_attrs[attr_key] = req_attrs[attr_key]
|
|
75
|
+
return active_requests_count_attrs
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_default_span_details(request: web.Request) -> Tuple[str, dict]:
|
|
79
|
+
"""Default implementation for get_default_span_details
|
|
80
|
+
Args:
|
|
81
|
+
request: the request object itself.
|
|
82
|
+
Returns:
|
|
83
|
+
a tuple of the span name, and any attributes to attach to the span.
|
|
84
|
+
"""
|
|
85
|
+
span_name = request.path.strip() or f"HTTP {request.method}"
|
|
86
|
+
return span_name, {}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _get_view_func(request: web.Request) -> str:
|
|
90
|
+
"""Returns the name of the request handler.
|
|
91
|
+
Args:
|
|
92
|
+
request: the request object itself.
|
|
93
|
+
Returns:
|
|
94
|
+
a string containing the name of the handler function
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
return request.match_info.handler.__name__
|
|
98
|
+
except AttributeError:
|
|
99
|
+
return "unknown"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def collect_request_attributes(request: web.Request) -> Dict:
|
|
103
|
+
"""Collects HTTP request attributes from the ASGI scope and returns a
|
|
104
|
+
dictionary to be used as span creation attributes."""
|
|
105
|
+
|
|
106
|
+
server_host, port, http_url = (
|
|
107
|
+
request.url.host,
|
|
108
|
+
request.url.port,
|
|
109
|
+
str(request.url),
|
|
110
|
+
)
|
|
111
|
+
query_string = request.query_string
|
|
112
|
+
if query_string and http_url:
|
|
113
|
+
if isinstance(query_string, bytes):
|
|
114
|
+
query_string = query_string.decode("utf8")
|
|
115
|
+
http_url += "?" + urllib.parse.unquote(query_string)
|
|
116
|
+
|
|
117
|
+
result = {
|
|
118
|
+
SpanAttributes.HTTP_SCHEME: request.scheme,
|
|
119
|
+
SpanAttributes.HTTP_HOST: server_host,
|
|
120
|
+
SpanAttributes.NET_HOST_PORT: port,
|
|
121
|
+
SpanAttributes.HTTP_ROUTE: _get_view_func(request),
|
|
122
|
+
SpanAttributes.HTTP_FLAVOR: f"{request.version.major}.{request.version.minor}",
|
|
123
|
+
SpanAttributes.HTTP_TARGET: request.path,
|
|
124
|
+
SpanAttributes.HTTP_URL: remove_url_credentials(http_url),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
http_method = request.method
|
|
128
|
+
if http_method:
|
|
129
|
+
result[SpanAttributes.HTTP_METHOD] = http_method
|
|
130
|
+
|
|
131
|
+
http_host_value_list = (
|
|
132
|
+
[request.host] if not isinstance(request.host, list) else request.host
|
|
133
|
+
)
|
|
134
|
+
if http_host_value_list:
|
|
135
|
+
result[SpanAttributes.HTTP_SERVER_NAME] = ",".join(
|
|
136
|
+
http_host_value_list
|
|
137
|
+
)
|
|
138
|
+
http_user_agent = request.headers.get("user-agent")
|
|
139
|
+
if http_user_agent:
|
|
140
|
+
result[SpanAttributes.HTTP_USER_AGENT] = http_user_agent
|
|
141
|
+
|
|
142
|
+
# remove None values
|
|
143
|
+
result = {k: v for k, v in result.items() if v is not None}
|
|
144
|
+
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def set_status_code(span, status_code: int) -> None:
|
|
149
|
+
"""Adds HTTP response attributes to span using the status_code argument."""
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
status_code = int(status_code)
|
|
153
|
+
except ValueError:
|
|
154
|
+
span.set_status(
|
|
155
|
+
Status(
|
|
156
|
+
StatusCode.ERROR,
|
|
157
|
+
"Non-integer HTTP status: " + repr(status_code),
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
|
|
162
|
+
span.set_status(
|
|
163
|
+
Status(http_status_to_status_code(status_code, server_span=True))
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class AiohttpGetter(Getter):
|
|
168
|
+
"""Extract current trace from headers"""
|
|
169
|
+
|
|
170
|
+
def get(self, carrier, key: str) -> Union[List, None]:
|
|
171
|
+
"""Getter implementation to retrieve an HTTP header value from the ASGI
|
|
172
|
+
scope.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
carrier: ASGI scope object
|
|
176
|
+
key: header name in scope
|
|
177
|
+
Returns:
|
|
178
|
+
A list of all header values matching the key, or None if the key
|
|
179
|
+
does not match any header.
|
|
180
|
+
"""
|
|
181
|
+
headers: CIMultiDictProxy = carrier.headers
|
|
182
|
+
if not headers:
|
|
183
|
+
return None
|
|
184
|
+
return headers.getall(key, None)
|
|
185
|
+
|
|
186
|
+
def keys(self, carrier: Dict) -> List:
|
|
187
|
+
return list(carrier.keys())
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
getter = AiohttpGetter()
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@web.middleware
|
|
194
|
+
async def middleware(request, handler):
|
|
195
|
+
"""Middleware for aiohttp implementing tracing logic"""
|
|
196
|
+
if not is_http_instrumentation_enabled() or _excluded_urls.url_disabled(
|
|
197
|
+
request.url.path
|
|
198
|
+
):
|
|
199
|
+
return await handler(request)
|
|
200
|
+
|
|
201
|
+
span_name, additional_attributes = get_default_span_details(request)
|
|
202
|
+
|
|
203
|
+
req_attrs = collect_request_attributes(request)
|
|
204
|
+
duration_attrs = _parse_duration_attrs(req_attrs)
|
|
205
|
+
active_requests_count_attrs = _parse_active_request_count_attrs(req_attrs)
|
|
206
|
+
|
|
207
|
+
duration_histogram = meter.create_histogram(
|
|
208
|
+
name=MetricInstruments.HTTP_SERVER_DURATION,
|
|
209
|
+
unit="ms",
|
|
210
|
+
description="Duration of HTTP server requests.",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
active_requests_counter = meter.create_up_down_counter(
|
|
214
|
+
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
|
|
215
|
+
unit="requests",
|
|
216
|
+
description="measures the number of concurrent HTTP requests those are currently in flight",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
with tracer.start_as_current_span(
|
|
220
|
+
span_name,
|
|
221
|
+
context=extract(request, getter=getter),
|
|
222
|
+
kind=trace.SpanKind.SERVER,
|
|
223
|
+
) as span:
|
|
224
|
+
attributes = collect_request_attributes(request)
|
|
225
|
+
attributes.update(additional_attributes)
|
|
226
|
+
span.set_attributes(attributes)
|
|
227
|
+
start = default_timer()
|
|
228
|
+
active_requests_counter.add(1, active_requests_count_attrs)
|
|
229
|
+
try:
|
|
230
|
+
resp = await handler(request)
|
|
231
|
+
set_status_code(span, resp.status)
|
|
232
|
+
except web.HTTPException as ex:
|
|
233
|
+
set_status_code(span, ex.status_code)
|
|
234
|
+
raise
|
|
235
|
+
finally:
|
|
236
|
+
duration = max((default_timer() - start) * 1000, 0)
|
|
237
|
+
duration_histogram.record(duration, duration_attrs)
|
|
238
|
+
active_requests_counter.add(-1, active_requests_count_attrs)
|
|
239
|
+
return resp
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class _InstrumentedApplication(web.Application):
|
|
243
|
+
"""Insert tracing middleware"""
|
|
244
|
+
|
|
245
|
+
def __init__(self, *args, **kwargs):
|
|
246
|
+
middlewares = kwargs.pop("middlewares", [])
|
|
247
|
+
middlewares.insert(0, middleware)
|
|
248
|
+
kwargs["middlewares"] = middlewares
|
|
249
|
+
super().__init__(*args, **kwargs)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class AioHttpServerInstrumentor(BaseInstrumentor):
|
|
253
|
+
# pylint: disable=protected-access,attribute-defined-outside-init
|
|
254
|
+
"""An instrumentor for aiohttp.web.Application
|
|
255
|
+
|
|
256
|
+
See `BaseInstrumentor`
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
def _instrument(self, **kwargs):
|
|
260
|
+
self._original_app = web.Application
|
|
261
|
+
setattr(web, "Application", _InstrumentedApplication)
|
|
262
|
+
|
|
263
|
+
def _uninstrument(self, **kwargs):
|
|
264
|
+
setattr(web, "Application", self._original_app)
|
|
265
|
+
|
|
266
|
+
def instrumentation_dependencies(self):
|
|
267
|
+
return _instruments
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_instruments = ("aiohttp ~= 3.0",)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
__version__ = "0.47b0"
|
|
File without changes
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Copyright 2020, OpenTelemetry Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from http import HTTPStatus
|
|
17
|
+
|
|
18
|
+
import aiohttp
|
|
19
|
+
import pytest
|
|
20
|
+
import pytest_asyncio
|
|
21
|
+
|
|
22
|
+
from opentelemetry import trace as trace_api
|
|
23
|
+
from opentelemetry.instrumentation.aiohttp_server import (
|
|
24
|
+
AioHttpServerInstrumentor,
|
|
25
|
+
)
|
|
26
|
+
from opentelemetry.instrumentation.utils import suppress_http_instrumentation
|
|
27
|
+
from opentelemetry.semconv.trace import SpanAttributes
|
|
28
|
+
from opentelemetry.test.globals_test import reset_trace_globals
|
|
29
|
+
from opentelemetry.test.test_base import TestBase
|
|
30
|
+
from opentelemetry.util._importlib_metadata import entry_points
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class HTTPMethod(Enum):
|
|
34
|
+
"""HTTP methods and descriptions"""
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
return f"{self.value}"
|
|
38
|
+
|
|
39
|
+
CONNECT = "CONNECT"
|
|
40
|
+
DELETE = "DELETE"
|
|
41
|
+
GET = "GET"
|
|
42
|
+
HEAD = "HEAD"
|
|
43
|
+
OPTIONS = "OPTIONS"
|
|
44
|
+
PATCH = "PATCH"
|
|
45
|
+
POST = "POST"
|
|
46
|
+
PUT = "PUT"
|
|
47
|
+
TRACE = "TRACE"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.fixture(name="tracer", scope="session")
|
|
51
|
+
def fixture_tracer():
|
|
52
|
+
test_base = TestBase()
|
|
53
|
+
|
|
54
|
+
tracer_provider, memory_exporter = test_base.create_tracer_provider()
|
|
55
|
+
|
|
56
|
+
reset_trace_globals()
|
|
57
|
+
trace_api.set_tracer_provider(tracer_provider)
|
|
58
|
+
|
|
59
|
+
yield tracer_provider, memory_exporter
|
|
60
|
+
|
|
61
|
+
reset_trace_globals()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def default_handler(request, status=200):
|
|
65
|
+
return aiohttp.web.Response(status=status)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.fixture(name="suppress")
|
|
69
|
+
def fixture_suppress():
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest_asyncio.fixture(name="server_fixture")
|
|
74
|
+
async def fixture_server_fixture(tracer, aiohttp_server, suppress):
|
|
75
|
+
_, memory_exporter = tracer
|
|
76
|
+
|
|
77
|
+
AioHttpServerInstrumentor().instrument()
|
|
78
|
+
|
|
79
|
+
app = aiohttp.web.Application()
|
|
80
|
+
app.add_routes([aiohttp.web.get("/test-path", default_handler)])
|
|
81
|
+
if suppress:
|
|
82
|
+
with suppress_http_instrumentation():
|
|
83
|
+
server = await aiohttp_server(app)
|
|
84
|
+
else:
|
|
85
|
+
server = await aiohttp_server(app)
|
|
86
|
+
|
|
87
|
+
yield server, app
|
|
88
|
+
|
|
89
|
+
memory_exporter.clear()
|
|
90
|
+
|
|
91
|
+
AioHttpServerInstrumentor().uninstrument()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_checking_instrumentor_pkg_installed():
|
|
95
|
+
|
|
96
|
+
(instrumentor_entrypoint,) = entry_points(
|
|
97
|
+
group="opentelemetry_instrumentor", name="aiohttp-server"
|
|
98
|
+
)
|
|
99
|
+
instrumentor = instrumentor_entrypoint.load()()
|
|
100
|
+
assert isinstance(instrumentor, AioHttpServerInstrumentor)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
@pytest.mark.parametrize(
|
|
105
|
+
"url, expected_method, expected_status_code",
|
|
106
|
+
[
|
|
107
|
+
("/test-path", HTTPMethod.GET, HTTPStatus.OK),
|
|
108
|
+
("/not-found", HTTPMethod.GET, HTTPStatus.NOT_FOUND),
|
|
109
|
+
],
|
|
110
|
+
)
|
|
111
|
+
async def test_status_code_instrumentation(
|
|
112
|
+
tracer,
|
|
113
|
+
server_fixture,
|
|
114
|
+
aiohttp_client,
|
|
115
|
+
url,
|
|
116
|
+
expected_method,
|
|
117
|
+
expected_status_code,
|
|
118
|
+
):
|
|
119
|
+
_, memory_exporter = tracer
|
|
120
|
+
server, _ = server_fixture
|
|
121
|
+
|
|
122
|
+
assert len(memory_exporter.get_finished_spans()) == 0
|
|
123
|
+
|
|
124
|
+
client = await aiohttp_client(server)
|
|
125
|
+
await client.get(url)
|
|
126
|
+
|
|
127
|
+
assert len(memory_exporter.get_finished_spans()) == 1
|
|
128
|
+
|
|
129
|
+
[span] = memory_exporter.get_finished_spans()
|
|
130
|
+
|
|
131
|
+
assert expected_method.value == span.attributes[SpanAttributes.HTTP_METHOD]
|
|
132
|
+
assert (
|
|
133
|
+
expected_status_code
|
|
134
|
+
== span.attributes[SpanAttributes.HTTP_STATUS_CODE]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
assert (
|
|
138
|
+
f"http://{server.host}:{server.port}{url}"
|
|
139
|
+
== span.attributes[SpanAttributes.HTTP_URL]
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.mark.asyncio
|
|
144
|
+
@pytest.mark.parametrize("suppress", [True])
|
|
145
|
+
async def test_suppress_instrumentation(
|
|
146
|
+
tracer, server_fixture, aiohttp_client
|
|
147
|
+
):
|
|
148
|
+
_, memory_exporter = tracer
|
|
149
|
+
server, _ = server_fixture
|
|
150
|
+
assert len(memory_exporter.get_finished_spans()) == 0
|
|
151
|
+
|
|
152
|
+
client = await aiohttp_client(server)
|
|
153
|
+
await client.get("/test-path")
|
|
154
|
+
|
|
155
|
+
assert len(memory_exporter.get_finished_spans()) == 0
|