Skip to content

Commit 5a14c7d

Browse files
dmanchondecko
authored andcommitted
aiohttp server
1 parent 8d0c504 commit 5a14c7d

File tree

6 files changed

+156
-15
lines changed

6 files changed

+156
-15
lines changed

instrumentation/opentelemetry-instrumentation-aiohttp-server/setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ test =
5555

5656
[options.entry_points]
5757
opentelemetry_instrumentor =
58-
aiohttp-server = opentelemetry.instrumentation.aiohttp_server:AioHttpInstrumentor
58+
aiohttp-server = opentelemetry.instrumentation.aiohttp_server:AioHttpServerInstrumentor

instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import urllib
22
from aiohttp import web
3-
from guillotina.utils import get_dotted_name
43
from multidict import CIMultiDictProxy
54
from opentelemetry import context, trace
65
from opentelemetry.instrumentation.aiohttp_server.package import _instruments
@@ -19,7 +18,7 @@
1918
_SUPPRESS_HTTP_INSTRUMENTATION_KEY = "suppress_http_instrumentation"
2019

2120
tracer = trace.get_tracer(__name__)
22-
_excluded_urls = get_excluded_urls("FLASK")
21+
_excluded_urls = get_excluded_urls("AIOHTTP_SERVER")
2322

2423

2524
def get_default_span_details(request: web.Request) -> Tuple[str, dict]:
@@ -34,9 +33,9 @@ def get_default_span_details(request: web.Request) -> Tuple[str, dict]:
3433

3534

3635
def _get_view_func(request) -> str:
37-
"""TODO: is this only working for guillotina?"""
36+
"""TODO: is this useful??"""
3837
try:
39-
return get_dotted_name(request.found_view)
38+
return request.match_info.handler.__name__
4039
except AttributeError:
4140
return "unknown"
4241

@@ -139,7 +138,7 @@ async def middleware(request, handler):
139138
if (
140139
context.get_value("suppress_instrumentation")
141140
or context.get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY)
142-
or not _excluded_urls.url_disabled(request.url)
141+
or _excluded_urls.url_disabled(request.url)
143142
):
144143
return await handler(request)
145144

@@ -173,7 +172,7 @@ def __init__(self, *args, **kwargs):
173172
super().__init__(*args, **kwargs)
174173

175174

176-
class AioHttpInstrumentor(BaseInstrumentor):
175+
class AioHttpServerInstrumentor(BaseInstrumentor):
177176
# pylint: disable=protected-access,attribute-defined-outside-init
178177
"""An instrumentor for aiohttp.web.Application
179178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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 asyncio
16+
import contextlib
17+
import typing
18+
import unittest
19+
import urllib.parse
20+
from functools import partial
21+
from http import HTTPStatus
22+
from unittest import mock
23+
24+
import aiohttp
25+
import aiohttp.test_utils
26+
import yarl
27+
from pkg_resources import iter_entry_points
28+
29+
from opentelemetry import context
30+
from opentelemetry.instrumentation import aiohttp_server
31+
from opentelemetry.instrumentation.aiohttp_server import (
32+
AioHttpServerInstrumentor,
33+
)
34+
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
35+
from opentelemetry.semconv.trace import SpanAttributes
36+
from opentelemetry.test.test_base import TestBase
37+
from opentelemetry.trace import Span, StatusCode
38+
39+
40+
def run_with_test_server(
41+
runnable: typing.Callable, url: str, handler: typing.Callable
42+
) -> typing.Tuple[str, int]:
43+
async def do_request():
44+
app = aiohttp.web.Application()
45+
parsed_url = urllib.parse.urlparse(url)
46+
app.add_routes([aiohttp.web.get(parsed_url.path, handler)])
47+
app.add_routes([aiohttp.web.post(parsed_url.path, handler)])
48+
app.add_routes([aiohttp.web.patch(parsed_url.path, handler)])
49+
50+
with contextlib.suppress(aiohttp.ClientError):
51+
async with aiohttp.test_utils.TestServer(app) as server:
52+
netloc = (server.host, server.port)
53+
await server.start_server()
54+
await runnable(server)
55+
return netloc
56+
57+
loop = asyncio.get_event_loop()
58+
return loop.run_until_complete(do_request())
59+
60+
61+
class TestAioHttpServerIntegration(TestBase):
62+
URL = "/test-path"
63+
64+
def setUp(self):
65+
super().setUp()
66+
AioHttpServerInstrumentor().instrument()
67+
68+
def tearDown(self):
69+
super().tearDown()
70+
AioHttpServerInstrumentor().uninstrument()
71+
72+
@staticmethod
73+
# pylint:disable=unused-argument
74+
async def default_handler(request, status=200):
75+
return aiohttp.web.Response(status=status)
76+
77+
def assert_spans(self, num_spans: int):
78+
finished_spans = self.memory_exporter.get_finished_spans()
79+
self.assertEqual(num_spans, len(finished_spans))
80+
if num_spans == 0:
81+
return None
82+
if num_spans == 1:
83+
return finished_spans[0]
84+
return finished_spans
85+
86+
@staticmethod
87+
def get_default_request(url: str = URL):
88+
async def default_request(server: aiohttp.test_utils.TestServer):
89+
async with aiohttp.test_utils.TestClient(server) as session:
90+
await session.get(url)
91+
92+
return default_request
93+
94+
def test_instrument(self):
95+
host, port = run_with_test_server(
96+
self.get_default_request(), self.URL, self.default_handler
97+
)
98+
span = self.assert_spans(1)
99+
self.assertEqual("GET", span.attributes[SpanAttributes.HTTP_METHOD])
100+
self.assertEqual(
101+
f"http://{host}:{port}/test-path",
102+
span.attributes[SpanAttributes.HTTP_URL],
103+
)
104+
self.assertEqual(200, span.attributes[SpanAttributes.HTTP_STATUS_CODE])
105+
106+
def test_status_codes(self):
107+
error_handler = partial(self.default_handler, status=400)
108+
host, port = run_with_test_server(
109+
self.get_default_request(), self.URL, error_handler
110+
)
111+
span = self.assert_spans(1)
112+
self.assertEqual("GET", span.attributes[SpanAttributes.HTTP_METHOD])
113+
self.assertEqual(
114+
f"http://{host}:{port}/test-path",
115+
span.attributes[SpanAttributes.HTTP_URL],
116+
)
117+
self.assertEqual(400, span.attributes[SpanAttributes.HTTP_STATUS_CODE])
118+
119+
def test_not_recording(self):
120+
mock_tracer = mock.Mock()
121+
mock_span = mock.Mock()
122+
mock_span.is_recording.return_value = False
123+
mock_tracer.start_span.return_value = mock_span
124+
with mock.patch("opentelemetry.trace.get_tracer"):
125+
# pylint: disable=W0612
126+
host, port = run_with_test_server(
127+
self.get_default_request(), self.URL, self.default_handler
128+
)
129+
130+
self.assertFalse(mock_span.is_recording())
131+
self.assertTrue(mock_span.is_recording.called)
132+
self.assertFalse(mock_span.set_attribute.called)
133+
self.assertFalse(mock_span.set_status.called)

opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,11 @@ def _is_installed(req):
103103

104104
def _find_installed_libraries():
105105
libs = default_instrumentations[:]
106-
libs.extend(
107-
[
108-
v["instrumentation"]
109-
for _, v in libraries.items()
110-
if _is_installed(v["library"])
111-
]
112-
)
106+
107+
for _, v in libraries.items():
108+
if _is_installed(v["library"]):
109+
libs.extend(v["instrumentation"])
110+
113111
return libs
114112

115113

opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
},
2323
"aiohttp": {
2424
"library": "aiohttp ~= 3.0",
25-
"instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.39b0.dev",
25+
"instrumentation": [
26+
"opentelemetry-instrumentation-aiohttp-client==0.39b0.dev",
27+
"opentelemetry-instrumentation-aiohttp-server==0.39b0.dev",
28+
],
2629
},
2730
"aiopg": {
2831
"library": "aiopg >= 0.13.0, < 2.0.0",

tox.ini

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ envlist =
2525
py3{7,8,9,10,11}-test-instrumentation-aiohttp-client
2626
pypy3-test-instrumentation-aiohttp-client
2727

28+
; opentelemetry-instrumentation-aiohttp-server
29+
py3{6,7,8,9,10}-test-instrumentation-aiohttp-server
30+
pypy3-test-instrumentation-aiohttp-server
31+
2832
; opentelemetry-instrumentation-aiopg
2933
py3{7,8,9,10,11}-test-instrumentation-aiopg
3034
; instrumentation-aiopg intentionally excluded from pypy3
@@ -287,6 +291,7 @@ changedir =
287291
test-opentelemetry-instrumentation: opentelemetry-instrumentation/tests
288292
test-instrumentation-aio-pika: instrumentation/opentelemetry-instrumentation-aio-pika/tests
289293
test-instrumentation-aiohttp-client: instrumentation/opentelemetry-instrumentation-aiohttp-client/tests
294+
test-instrumentation-aiohttp-server: instrumentation/opentelemetry-instrumentation-aiohttp-server/tests
290295
test-instrumentation-aiopg: instrumentation/opentelemetry-instrumentation-aiopg/tests
291296
test-instrumentation-asgi: instrumentation/opentelemetry-instrumentation-asgi/tests
292297
test-instrumentation-asyncpg: instrumentation/opentelemetry-instrumentation-asyncpg/tests
@@ -425,6 +430,8 @@ commands_pre =
425430

426431
aiohttp-client: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-aiohttp-client[test]
427432

433+
aiohttp-server: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-aiohttp-server[test]
434+
428435
aiopg: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-dbapi pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-aiopg[test]
429436

430437
richconsole: pip install flaky {toxinidir}/exporter/opentelemetry-exporter-richconsole[test]
@@ -527,6 +534,7 @@ commands_pre =
527534
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pymemcache[test]
528535
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-psycopg2[test]
529536
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-aiohttp-client[test]
537+
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-aiohttp-server[test]
530538
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-aiopg[test]
531539
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlite3[test]
532540
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pyramid[test]

0 commit comments

Comments
 (0)