Skip to content

Commit cf1918f

Browse files
Add uvicorn instrumentation. Closes #4
1 parent 14cfbcc commit cf1918f

File tree

4 files changed

+134
-2
lines changed

4 files changed

+134
-2
lines changed

newrelic/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2351,6 +2351,10 @@ def _process_module_builtin_defaults():
23512351
'newrelic.hooks.external_urllib3',
23522352
'instrument_urllib3_connection')
23532353

2354+
_process_module_definition('uvicorn.config',
2355+
'newrelic.hooks.adapter_uvicorn',
2356+
'instrument_uvicorn_config')
2357+
23542358
_process_module_definition('sanic.app',
23552359
'newrelic.hooks.framework_sanic',
23562360
'instrument_sanic_app')

newrelic/core/environment.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,25 @@ def environment_settings():
153153
dispatcher.append(('Dispatcher', 'gunicorn (gevent)'))
154154
elif 'gunicorn.workers.geventlet' in sys.modules:
155155
dispatcher.append(('Dispatcher', 'gunicorn (eventlet)'))
156+
elif 'uvicorn.workers' in sys.modules:
157+
dispatcher.append(('Dispatcher', 'gunicorn (uvicorn)'))
158+
uvicorn = sys.modules.get('uvicorn')
159+
if hasattr(uvicorn, '__version__'):
160+
dispatcher.append(('Worker Version', uvicorn.__version__))
156161
else:
157162
dispatcher.append(('Dispatcher', 'gunicorn'))
163+
158164
gunicorn = sys.modules['gunicorn']
159165
if hasattr(gunicorn, '__version__'):
160166
dispatcher.append(('Dispatcher Version', gunicorn.__version__))
161167

168+
if not dispatcher and 'uvicorn' in sys.modules:
169+
dispatcher.append(('Dispatcher', 'uvicorn'))
170+
uvicorn = sys.modules['uvicorn']
171+
172+
if hasattr(uvicorn, '__version__'):
173+
dispatcher.append(('Dispatcher Version', uvicorn.__version__))
174+
162175
if not dispatcher and 'tornado' in sys.modules:
163176
dispatcher.append(('Dispatcher', 'tornado'))
164177
tornado = sys.modules['tornado']

newrelic/hooks/adapter_uvicorn.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2010 New Relic, Inc.
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 newrelic.api.asgi_application import ASGIApplicationWrapper
16+
17+
18+
@property
19+
def loaded_app(self):
20+
return self._nr_loaded_app
21+
22+
23+
@loaded_app.setter
24+
def loaded_app(self, value):
25+
# Wrap only the first loaded app
26+
if (
27+
not getattr(self, "_nr_loaded_app", None)
28+
and value
29+
and getattr(self, "interface", "") != "wsgi"
30+
):
31+
value = ASGIApplicationWrapper(value)
32+
self._nr_loaded_app = value
33+
34+
35+
def instrument_uvicorn_config(module):
36+
module.Config.loaded_app = loaded_app

tests/agent_unittests/test_environment.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,99 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import pytest
16+
import sys
1517
from newrelic.core.environment import environment_settings
1618

1719

20+
def module(version):
21+
class Module(object):
22+
pass
23+
24+
if version:
25+
Module.__version__ = version
26+
27+
return Module
28+
29+
1830
def test_plugin_list():
1931
# Let's pretend we fired an import hook
2032
import newrelic.hooks.adapter_gunicorn
2133

2234
environment_info = environment_settings()
2335

2436
for key, plugin_list in environment_info:
25-
if key == 'Plugin List':
37+
if key == "Plugin List":
2638
break
2739
else:
2840
assert False, "'Plugin List' not found"
2941

3042
# Check that bogus plugins don't get reported
31-
assert 'newrelic.hooks.newrelic' not in plugin_list
43+
assert "newrelic.hooks.newrelic" not in plugin_list
44+
45+
46+
@pytest.mark.parametrize(
47+
"loaded_modules,dispatcher,dispatcher_version,worker_version",
48+
(
49+
({"uvicorn": module("4.5.6")}, "uvicorn", "4.5.6", None),
50+
(
51+
{
52+
"gunicorn": module("1.2.3"),
53+
"uvicorn": module("4.5.6"),
54+
"uvicorn.workers": object(),
55+
},
56+
"gunicorn (uvicorn)",
57+
"1.2.3",
58+
"4.5.6",
59+
),
60+
({"uvicorn": object()}, "uvicorn", None, None),
61+
(
62+
{
63+
"gunicorn": object(),
64+
"uvicorn": module("4.5.6"),
65+
"uvicorn.workers": object(),
66+
},
67+
"gunicorn (uvicorn)",
68+
None,
69+
"4.5.6",
70+
),
71+
(
72+
{"gunicorn": module("1.2.3"), "uvicorn": None, "uvicorn.workers": object()},
73+
"gunicorn (uvicorn)",
74+
"1.2.3",
75+
None,
76+
),
77+
(
78+
{"gunicorn": object(), "uvicorn": object(), "uvicorn.workers": object()},
79+
"gunicorn (uvicorn)",
80+
None,
81+
None,
82+
),
83+
),
84+
)
85+
def test_uvicorn_dispatcher(
86+
monkeypatch, loaded_modules, dispatcher, dispatcher_version, worker_version
87+
):
88+
# Let's pretend we load some modules
89+
for name, module in loaded_modules.items():
90+
monkeypatch.setitem(sys.modules, name, module)
91+
92+
environment_info = environment_settings()
93+
94+
actual_dispatcher = None
95+
actual_dispatcher_version = None
96+
actual_worker_version = None
97+
for key, value in environment_info:
98+
if key == "Dispatcher":
99+
assert actual_dispatcher is None
100+
actual_dispatcher = value
101+
elif key == "Dispatcher Version":
102+
assert actual_dispatcher_version is None
103+
actual_dispatcher_version = value
104+
elif key == "Worker Version":
105+
assert actual_worker_version is None
106+
actual_worker_version = value
107+
108+
assert actual_dispatcher == dispatcher
109+
assert actual_dispatcher_version == dispatcher_version
110+
assert actual_worker_version == worker_version

0 commit comments

Comments
 (0)