Skip to content

Commit ddcc93c

Browse files
Update Starlette transaction naming hierarchy.
1 parent 930d55c commit ddcc93c

File tree

5 files changed

+92
-18
lines changed

5 files changed

+92
-18
lines changed

newrelic/core/config.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ def create_settings(nested):
9090
return type('Settings', (Settings,), {'nested': nested})()
9191

9292

93+
class TopLevelSettings(Settings):
94+
_host = None
95+
96+
@property
97+
def host(self):
98+
if self._host:
99+
return self._host
100+
else:
101+
return default_host(self.license_key)
102+
103+
@host.setter
104+
def host(self, value):
105+
self._host = value
106+
107+
93108
class AttributesSettings(Settings):
94109
pass
95110

@@ -303,7 +318,7 @@ class EventHarvestConfigHarvestLimitSettings(Settings):
303318
nested = True
304319

305320

306-
_settings = Settings()
321+
_settings = TopLevelSettings()
307322
_settings.attributes = AttributesSettings()
308323
_settings.thread_profiler = ThreadProfilerSettings()
309324
_settings.transaction_tracer = TransactionTracerSettings()
@@ -502,8 +517,7 @@ def default_host(license_key):
502517

503518
_settings.ssl = _environ_as_bool('NEW_RELIC_SSL', True)
504519

505-
_settings.host = os.environ.get('NEW_RELIC_HOST',
506-
default_host(_settings.license_key))
520+
_settings.host = os.environ.get('NEW_RELIC_HOST')
507521
_settings.port = int(os.environ.get('NEW_RELIC_PORT', '0'))
508522

509523
_settings.agent_run_id = None

newrelic/core/environment.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,14 @@ def environment_settings():
185185

186186
plugins = []
187187

188-
# Using six to create create a snapshot of sys.modules can occassionally
188+
# Using any iterable to create a snapshot of sys.modules can occassionally
189189
# fail in a rare case when modules are imported in parallel by different
190-
# threads. This is because list(six.iteritems(sys.modules)) results in
191-
# list(iter(sys.modules.iteritems())), which means sys.modules could change
192-
# between the time when the iterable is handed over from the iter() to
193-
# list().
190+
# threads.
194191
#
195-
# TL;DR: Do NOT use six module for the following iteration.
192+
# TL;DR: Do NOT use an iterable on the original sys.modules to generate the
193+
# list
196194

197-
for name, module in list(sys.modules.items()):
195+
for name, module in sys.modules.copy().items():
198196
# If the module isn't actually loaded (such as failed relative imports
199197
# in Python 2.7), the module will be None and should not be reported.
200198
if module is None:

newrelic/hooks/framework_starlette.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def route_naming_wrapper(wrapped, instance, args, kwargs):
5151
with RequestContext(bind_request(*args, **kwargs)):
5252
transaction = current_transaction()
5353
if transaction:
54-
transaction.set_transaction_name(callable_name(wrapped), priority=3)
54+
transaction.set_transaction_name(callable_name(wrapped), priority=2)
5555
return wrapped(*args, **kwargs)
5656

5757

@@ -83,14 +83,21 @@ def wrap_background_method(wrapped, instance, args, kwargs):
8383
return wrapped(*args, **kwargs)
8484

8585

86-
@function_wrapper
87-
def wrap_middleware(wrapped, instance, args, kwargs):
88-
result = wrapped(*args, **kwargs)
86+
async def middleware_wrapper(wrapped, instance, args, kwargs):
87+
transaction = current_transaction()
88+
if transaction:
89+
transaction.set_transaction_name(callable_name(wrapped), priority=1)
8990

90-
dispatch_func = getattr(result, "dispatch_func", None)
91+
dispatch_func = getattr(wrapped, "dispatch_func", None)
9192
name = dispatch_func and callable_name(dispatch_func)
9293

93-
return FunctionTraceWrapper(result, name=name)
94+
return await FunctionTraceWrapper(wrapped, name=name)(*args, **kwargs)
95+
96+
97+
@function_wrapper
98+
def wrap_middleware(wrapped, instance, args, kwargs):
99+
result = wrapped(*args, **kwargs)
100+
return FunctionWrapper(result, middleware_wrapper)
94101

95102

96103
def bind_middleware(middleware_class, *args, **kwargs):
@@ -159,6 +166,14 @@ def wrap_add_exception_handler(wrapped, instance, args, kwargs):
159166
return wrapped(exc_class_or_status_code, handler, *args, **kwargs)
160167

161168

169+
def error_middleware_wrapper(wrapped, instance, args, kwargs):
170+
transaction = current_transaction()
171+
if transaction:
172+
transaction.set_transaction_name(callable_name(wrapped), priority=1)
173+
174+
return FunctionTraceWrapper(wrapped)(*args, **kwargs)
175+
176+
162177
def instrument_starlette_applications(module):
163178
framework = framework_details()
164179
version_info = tuple(int(v) for v in framework[1].split(".", 3)[:3])
@@ -178,7 +193,7 @@ def instrument_starlette_requests(module):
178193

179194

180195
def instrument_starlette_middleware_errors(module):
181-
wrap_function_trace(module, "ServerErrorMiddleware.__call__")
196+
wrap_function_wrapper(module, "ServerErrorMiddleware.__call__", error_middleware_wrapper)
182197

183198
wrap_function_wrapper(module, "ServerErrorMiddleware.__init__", wrap_server_error_handler)
184199

@@ -188,7 +203,7 @@ def instrument_starlette_middleware_errors(module):
188203

189204

190205
def instrument_starlette_exceptions(module):
191-
wrap_function_trace(module, "ExceptionMiddleware.__call__")
206+
wrap_function_wrapper(module, "ExceptionMiddleware.__call__", error_middleware_wrapper)
192207

193208
wrap_function_wrapper(module, "ExceptionMiddleware.http_exception",
194209
wrap_exception_handler)

tests/agent_unittests/test_environment.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,37 @@ def test_plugin_list():
4343
assert "newrelic.hooks.newrelic" not in plugin_list
4444

4545

46+
class NoIteratorDict(object):
47+
def __init__(self, d):
48+
self.d = d
49+
50+
def copy(self):
51+
return self.d.copy()
52+
53+
def get(self, *args, **kwargs):
54+
return self.d.get(*args, **kwargs)
55+
56+
def __getitem__(self, *args, **kwargs):
57+
return self.d.__getitem__(*args, **kwargs)
58+
59+
def __contains__(self, *args, **kwargs):
60+
return self.d.__contains__(*args, **kwargs)
61+
62+
63+
def test_plugin_list_uses_no_sys_modules_iterator(monkeypatch):
64+
modules = NoIteratorDict(sys.modules)
65+
monkeypatch.setattr(sys, 'modules', modules)
66+
67+
# If environment_settings iterates over sys.modules, an attribute error will be generated
68+
environment_info = environment_settings()
69+
70+
for key, plugin_list in environment_info:
71+
if key == "Plugin List":
72+
break
73+
else:
74+
assert False, "'Plugin List' not found"
75+
76+
4677
@pytest.mark.parametrize(
4778
"loaded_modules,dispatcher,dispatcher_version,worker_version",
4879
(

tests/agent_unittests/test_region_aware_settings.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,19 @@ def test_region_aware_license_keys(ini, env, expected_host,
8181
settings = global_settings()
8282
assert settings.host == expected_host
8383
assert settings.license_key == expected_license_key
84+
85+
86+
@pytest.mark.parametrize("ini,license_key,host_override,expected_host", (
87+
(INI_FILE_WITHOUT_LICENSE_KEY, NO_REGION_KEY, None, "collector.newrelic.com"),
88+
(INI_FILE_WITHOUT_LICENSE_KEY, EU01_KEY, None, "collector.eu01.nr-data.net"),
89+
(INI_FILE_WITHOUT_LICENSE_KEY, NO_REGION_KEY, "foo.newrelic.com", "foo.newrelic.com"),
90+
))
91+
def test_region_aware_global_settings(ini, license_key, host_override,
92+
expected_host, global_settings):
93+
settings = global_settings()
94+
95+
if host_override:
96+
settings.host = host_override
97+
98+
settings.license_key = license_key
99+
assert settings.host == expected_host

0 commit comments

Comments
 (0)