diff --git a/newrelic/core/context.py b/newrelic/core/context.py index 4e438de04..e7cbcb5cf 100644 --- a/newrelic/core/context.py +++ b/newrelic/core/context.py @@ -16,41 +16,80 @@ This module implements utilities for context propagation for tracing across threads. """ +import logging + from newrelic.common.object_wrapper import function_wrapper from newrelic.core.trace_cache import trace_cache +_logger = logging.getLogger(__name__) + + class ContextOf(object): - def __init__(self, trace_cache_id): + def __init__(self, trace=None, request=None, trace_cache_id=None): + self.trace = None self.trace_cache = trace_cache() - self.trace = self.trace_cache._cache.get(trace_cache_id) self.thread_id = None self.restore = None + self.should_restore = False + + # Extract trace if possible, else leave as None for safety + if trace is None and request is None and trace_cache_id is None: + _logger.error( + "Runtime instrumentation error. Request context propagation failed. No trace or request provided. Report this issue to New Relic support.", + ) + elif trace is not None: + self.trace = trace + elif trace_cache_id is not None: + self.trace = self.trace_cache._cache.get(trace_cache_id, None) + if self.trace is None: + _logger.error( + "Runtime instrumentation error. Request context propagation failed. No trace with id %s. Report this issue to New Relic support.", + trace_cache_id, + ) + elif hasattr(request, "_nr_trace") and request._nr_trace is not None: + # Unpack traces from objects patched with them + self.trace = request._nr_trace + else: + _logger.error( + "Runtime instrumentation error. Request context propagation failed. No context attached to request. Report this issue to New Relic support.", + ) def __enter__(self): if self.trace: self.thread_id = self.trace_cache.current_thread_id() - self.restore = self.trace_cache._cache.get(self.thread_id) + + # Save previous cache contents + self.restore = self.trace_cache._cache.get(self.thread_id, None) + self.should_restore = True + + # Set context in trace cache self.trace_cache._cache[self.thread_id] = self.trace + return self def __exit__(self, exc, value, tb): - if self.restore: - self.trace_cache._cache[self.thread_id] = self.restore + if self.should_restore: + if self.restore is not None: + # Restore previous contents + self.trace_cache._cache[self.thread_id] = self.restore + else: + # Remove entry from cache + self.trace_cache._cache.pop(self.thread_id) -async def context_wrapper_async(awaitable, trace_cache_id): - with ContextOf(trace_cache_id): - return await awaitable - - -def context_wrapper(func, trace_cache_id): +def context_wrapper(func, trace=None, request=None, trace_cache_id=None): @function_wrapper def _context_wrapper(wrapped, instance, args, kwargs): - with ContextOf(trace_cache_id): + with ContextOf(trace=trace, request=request, trace_cache_id=trace_cache_id): return wrapped(*args, **kwargs) return _context_wrapper(func) +async def context_wrapper_async(awaitable, trace=None, request=None, trace_cache_id=None): + with ContextOf(trace=trace, request=request, trace_cache_id=trace_cache_id): + return await awaitable + + def current_thread_id(): return trace_cache().current_thread_id() diff --git a/newrelic/core/trace_cache.py b/newrelic/core/trace_cache.py index db38c7ee7..4a087c4fb 100644 --- a/newrelic/core/trace_cache.py +++ b/newrelic/core/trace_cache.py @@ -275,25 +275,6 @@ def save_trace(self, trace): task = current_task(self.asyncio) trace._task = task - def thread_start(self, trace): - current_thread_id = self.current_thread_id() - if current_thread_id not in self._cache: - self._cache[current_thread_id] = trace - else: - _logger.error( - "Runtime instrumentation error. An active " - "trace already exists in the cache on thread_id %s. Report " - "this issue to New Relic support.\n ", - current_thread_id, - ) - return None - - return current_thread_id - - def thread_stop(self, thread_id): - if thread_id: - self._cache.pop(thread_id, None) - def pop_current(self, trace): """Restore the trace's parent under the thread ID of the current executing thread.""" diff --git a/newrelic/hooks/adapter_asgiref.py b/newrelic/hooks/adapter_asgiref.py index 9ab3902e5..a8516eb1e 100644 --- a/newrelic/hooks/adapter_asgiref.py +++ b/newrelic/hooks/adapter_asgiref.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from newrelic.api.time_trace import current_trace from newrelic.common.object_wrapper import wrap_function_wrapper -from newrelic.core.trace_cache import trace_cache -from newrelic.core.context import context_wrapper_async, ContextOf +from newrelic.core.context import ContextOf, context_wrapper_async def _bind_thread_handler(loop, source_task, *args, **kwargs): @@ -23,17 +23,15 @@ def _bind_thread_handler(loop, source_task, *args, **kwargs): def thread_handler_wrapper(wrapped, instance, args, kwargs): task = _bind_thread_handler(*args, **kwargs) - with ContextOf(id(task)): + with ContextOf(trace_cache_id=id(task)): return wrapped(*args, **kwargs) def main_wrap_wrapper(wrapped, instance, args, kwargs): awaitable = wrapped(*args, **kwargs) - return context_wrapper_async(awaitable, trace_cache().current_thread_id()) + return context_wrapper_async(awaitable, current_trace()) def instrument_asgiref_sync(module): - wrap_function_wrapper(module, 'SyncToAsync.thread_handler', - thread_handler_wrapper) - wrap_function_wrapper(module, 'AsyncToSync.main_wrap', - main_wrap_wrapper) + wrap_function_wrapper(module, "SyncToAsync.thread_handler", thread_handler_wrapper) + wrap_function_wrapper(module, "AsyncToSync.main_wrap", main_wrap_wrapper) diff --git a/newrelic/hooks/framework_fastapi.py b/newrelic/hooks/framework_fastapi.py index 97b36a815..408ee2a62 100644 --- a/newrelic/hooks/framework_fastapi.py +++ b/newrelic/hooks/framework_fastapi.py @@ -13,25 +13,11 @@ # limitations under the License. from copy import copy -from newrelic.api.time_trace import current_trace + from newrelic.api.function_trace import FunctionTraceWrapper -from newrelic.common.object_wrapper import wrap_function_wrapper, function_wrapper +from newrelic.api.time_trace import current_trace from newrelic.common.object_names import callable_name -from newrelic.core.trace_cache import trace_cache - - -def use_context(trace): - - @function_wrapper - def context_wrapper(wrapped, instance, args, kwargs): - cache = trace_cache() - thread_id = cache.thread_start(trace) - try: - return wrapped(*args, **kwargs) - finally: - cache.thread_stop(thread_id) - - return context_wrapper +from newrelic.common.object_wrapper import wrap_function_wrapper def wrap_run_endpoint_function(wrapped, instance, args, kwargs): @@ -41,12 +27,9 @@ def wrap_run_endpoint_function(wrapped, instance, args, kwargs): name = callable_name(dependant.call) trace.transaction.set_transaction_name(name) - if not kwargs["is_coroutine"]: - dependant = kwargs["dependant"] = copy(dependant) - dependant.call = use_context(trace)(FunctionTraceWrapper(dependant.call)) - return wrapped(*args, **kwargs) - else: - return FunctionTraceWrapper(wrapped, name=name)(*args, **kwargs) + dependant = kwargs["dependant"] = copy(dependant) + dependant.call = FunctionTraceWrapper(dependant.call) + return wrapped(*args, **kwargs) return wrapped(*args, **kwargs) diff --git a/newrelic/hooks/framework_starlette.py b/newrelic/hooks/framework_starlette.py index 47a2128dc..b48851624 100644 --- a/newrelic/hooks/framework_starlette.py +++ b/newrelic/hooks/framework_starlette.py @@ -25,8 +25,7 @@ wrap_function_wrapper, ) from newrelic.core.config import should_ignore_error -from newrelic.core.context import context_wrapper, current_thread_id -from newrelic.core.trace_cache import trace_cache +from newrelic.core.context import ContextOf, context_wrapper def framework_details(): @@ -43,30 +42,10 @@ def bind_exc(request, exc, *args, **kwargs): return exc -class RequestContext(object): - def __init__(self, request): - self.request = request - self.force_propagate = False - self.thread_id = None - - def __enter__(self): - trace = getattr(self.request, "_nr_trace", None) - self.force_propagate = trace and current_trace() is None - - # Propagate trace context onto the current task - if self.force_propagate: - self.thread_id = trace_cache().thread_start(trace) - - def __exit__(self, exc, value, tb): - # Remove any context from the current thread as it was force propagated above - if self.force_propagate: - trace_cache().thread_stop(self.thread_id) - - @function_wrapper def route_naming_wrapper(wrapped, instance, args, kwargs): - with RequestContext(bind_request(*args, **kwargs)): + with ContextOf(request=bind_request(*args, **kwargs)): transaction = current_transaction() if transaction: transaction.set_transaction_name(callable_name(wrapped), priority=2) @@ -136,16 +115,14 @@ def wrap_add_middleware(wrapped, instance, args, kwargs): return wrapped(wrap_middleware(middleware), *args, **kwargs) -def bind_middleware_starlette( - debug=False, routes=None, middleware=None, *args, **kwargs -): +def bind_middleware_starlette(debug=False, routes=None, middleware=None, *args, **kwargs): # pylint: disable=W1113 return middleware def wrap_starlette(wrapped, instance, args, kwargs): middlewares = bind_middleware_starlette(*args, **kwargs) if middlewares: - for middleware in middlewares: + for middleware in middlewares: # pylint: disable=E1133 cls = getattr(middleware, "cls", None) if cls and not hasattr(cls, "__wrapped__"): middleware.cls = wrap_middleware(cls) @@ -171,11 +148,9 @@ async def wrap_exception_handler_async(coro, exc): def wrap_exception_handler(wrapped, instance, args, kwargs): if is_coroutine_function(wrapped): - return wrap_exception_handler_async( - FunctionTraceWrapper(wrapped)(*args, **kwargs), bind_exc(*args, **kwargs) - ) + return wrap_exception_handler_async(FunctionTraceWrapper(wrapped)(*args, **kwargs), bind_exc(*args, **kwargs)) else: - with RequestContext(bind_request(*args, **kwargs)): + with ContextOf(request=bind_request(*args, **kwargs)): response = FunctionTraceWrapper(wrapped)(*args, **kwargs) record_response_error(response, bind_exc(*args, **kwargs)) return response @@ -190,9 +165,7 @@ def wrap_server_error_handler(wrapped, instance, args, kwargs): def wrap_add_exception_handler(wrapped, instance, args, kwargs): - exc_class_or_status_code, handler, args, kwargs = bind_add_exception_handler( - *args, **kwargs - ) + exc_class_or_status_code, handler, args, kwargs = bind_add_exception_handler(*args, **kwargs) handler = FunctionWrapper(handler, wrap_exception_handler) return wrapped(exc_class_or_status_code, handler, *args, **kwargs) @@ -217,7 +190,7 @@ async def wrap_run_in_threadpool(wrapped, instance, args, kwargs): return await wrapped(*args, **kwargs) func, args, kwargs = bind_run_in_threadpool(*args, **kwargs) - func = context_wrapper(func, current_thread_id()) + func = context_wrapper(func, trace) return await wrapped(func, *args, **kwargs) @@ -241,35 +214,21 @@ def instrument_starlette_requests(module): def instrument_starlette_middleware_errors(module): - wrap_function_wrapper( - module, "ServerErrorMiddleware.__call__", error_middleware_wrapper - ) + wrap_function_wrapper(module, "ServerErrorMiddleware.__call__", error_middleware_wrapper) - wrap_function_wrapper( - module, "ServerErrorMiddleware.__init__", wrap_server_error_handler - ) + wrap_function_wrapper(module, "ServerErrorMiddleware.__init__", wrap_server_error_handler) - wrap_function_wrapper( - module, "ServerErrorMiddleware.error_response", wrap_exception_handler - ) + wrap_function_wrapper(module, "ServerErrorMiddleware.error_response", wrap_exception_handler) - wrap_function_wrapper( - module, "ServerErrorMiddleware.debug_response", wrap_exception_handler - ) + wrap_function_wrapper(module, "ServerErrorMiddleware.debug_response", wrap_exception_handler) def instrument_starlette_exceptions(module): - wrap_function_wrapper( - module, "ExceptionMiddleware.__call__", error_middleware_wrapper - ) + wrap_function_wrapper(module, "ExceptionMiddleware.__call__", error_middleware_wrapper) - wrap_function_wrapper( - module, "ExceptionMiddleware.http_exception", wrap_exception_handler - ) + wrap_function_wrapper(module, "ExceptionMiddleware.http_exception", wrap_exception_handler) - wrap_function_wrapper( - module, "ExceptionMiddleware.add_exception_handler", wrap_add_exception_handler - ) + wrap_function_wrapper(module, "ExceptionMiddleware.add_exception_handler", wrap_add_exception_handler) def instrument_starlette_background_task(module): diff --git a/setup.py b/setup.py index 667536f5a..c489221d4 100644 --- a/setup.py +++ b/setup.py @@ -14,26 +14,32 @@ from __future__ import print_function -import sys import os +import sys python_version = sys.version_info[:2] -assert python_version in ((2, 7),) or python_version >= (3, 6), \ - 'The New Relic Python agent only supports Python 2.7 and 3.6+.' +assert python_version in ((2, 7),) or python_version >= ( + 3, + 6, +), "The New Relic Python agent only supports Python 2.7 and 3.6+." with_setuptools = False try: from setuptools import setup + with_setuptools = True except ImportError: from distutils.core import setup -from distutils.core import Extension -from distutils.command.build_ext import build_ext -from distutils.errors import (CCompilerError, DistutilsExecError, - DistutilsPlatformError) +from distutils.command.build_ext import build_ext # noqa +from distutils.core import Extension # noqa +from distutils.errors import ( # noqa + CCompilerError, + DistutilsExecError, + DistutilsPlatformError, +) def newrelic_agent_guess_next_version(tag_version): @@ -53,27 +59,25 @@ def newrelic_agent_next_version(version): if version.exact: return version.format_with("{tag}") else: - return version.format_next_version( - newrelic_agent_guess_next_version, fmt="{guessed}" - ) + return version.format_next_version(newrelic_agent_guess_next_version, fmt="{guessed}") script_directory = os.path.dirname(__file__) if not script_directory: script_directory = os.getcwd() -readme_file = os.path.join(script_directory, 'README.rst') +readme_file = os.path.join(script_directory, "README.rst") -if sys.platform == 'win32' and python_version > (2, 6): - build_ext_errors = (CCompilerError, DistutilsExecError, - DistutilsPlatformError, IOError) +if sys.platform == "win32" and python_version > (2, 6): + build_ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) else: - build_ext_errors = (CCompilerError, DistutilsExecError, - DistutilsPlatformError) + build_ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) + class BuildExtFailed(Exception): pass + class optional_build_ext(build_ext): def run(self): try: @@ -87,88 +91,90 @@ def build_extension(self, ext): except build_ext_errors: raise BuildExtFailed() + packages = [ - "newrelic", - "newrelic.admin", - "newrelic.api", - "newrelic.bootstrap", - "newrelic.common", - "newrelic.core", - "newrelic.extras", - "newrelic.extras.framework_django", - "newrelic.extras.framework_django.templatetags", - "newrelic.hooks", - "newrelic.network", - "newrelic/packages", - "newrelic/packages/urllib3", - "newrelic/packages/urllib3/util", - "newrelic/packages/urllib3/contrib", - "newrelic/packages/urllib3/contrib/_securetransport", - "newrelic/packages/urllib3/packages", - "newrelic/packages/urllib3/packages/backports", - "newrelic/packages/urllib3/packages/ssl_match_hostname", - "newrelic/packages/wrapt", - "newrelic.samplers", + "newrelic", + "newrelic.admin", + "newrelic.api", + "newrelic.bootstrap", + "newrelic.common", + "newrelic.core", + "newrelic.extras", + "newrelic.extras.framework_django", + "newrelic.extras.framework_django.templatetags", + "newrelic.hooks", + "newrelic.network", + "newrelic/packages", + "newrelic/packages/urllib3", + "newrelic/packages/urllib3/util", + "newrelic/packages/urllib3/contrib", + "newrelic/packages/urllib3/contrib/_securetransport", + "newrelic/packages/urllib3/packages", + "newrelic/packages/urllib3/packages/backports", + "newrelic/packages/urllib3/packages/ssl_match_hostname", + "newrelic/packages/wrapt", + "newrelic.samplers", ] classifiers = [ - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: System :: Monitoring", + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: System :: Monitoring", ] kwargs = dict( - name = "newrelic", - use_scm_version={ - "version_scheme": newrelic_agent_next_version, - "local_scheme": "no-local-version", - "git_describe_command": "git describe --dirty --tags --long --match *.*.*.*", - "write_to": "newrelic/version.txt", - }, - setup_requires=["setuptools_scm>=3.2,<4"], - description = "New Relic Python Agent", - long_description = open(readme_file).read(), - url = "https://newrelic.com/docs/python/new-relic-for-python", - project_urls = {"Source": "https://github.com/newrelic/newrelic-python-agent"}, - author = "New Relic", - author_email = "support@newrelic.com", - maintainer = 'New Relic', - maintainer_email = 'support@newrelic.com', - license = 'Apache-2.0', - zip_safe = False, - classifiers = classifiers, - packages = packages, - python_requires = '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*', - package_data = {'newrelic': ['newrelic.ini', - 'version.txt', - 'packages/urllib3/LICENSE.txt', - 'common/cacert.pem'], - }, - scripts = [ 'scripts/newrelic-admin' ], - extras_require = {'infinite-tracing': ['grpcio<2', 'protobuf<4']}, + name="newrelic", + use_scm_version={ + "version_scheme": newrelic_agent_next_version, + "local_scheme": "no-local-version", + "git_describe_command": "git describe --dirty --tags --long --match *.*.*.*", + "write_to": "newrelic/version.txt", + }, + setup_requires=["setuptools_scm>=3.2,<7"], + description="New Relic Python Agent", + long_description=open(readme_file).read(), + url="https://newrelic.com/docs/python/new-relic-for-python", + project_urls={"Source": "https://github.com/newrelic/newrelic-python-agent"}, + author="New Relic", + author_email="support@newrelic.com", + maintainer="New Relic", + maintainer_email="support@newrelic.com", + license="Apache-2.0", + zip_safe=False, + classifiers=classifiers, + packages=packages, + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*", + package_data={ + "newrelic": ["newrelic.ini", "version.txt", "packages/urllib3/LICENSE.txt", "common/cacert.pem"], + }, + scripts=["scripts/newrelic-admin"], + extras_require={"infinite-tracing": ["grpcio<2", "protobuf<4"]}, ) if with_setuptools: - kwargs['entry_points'] = { - 'console_scripts': ['newrelic-admin = newrelic.admin:main'], - } + kwargs["entry_points"] = { + "console_scripts": ["newrelic-admin = newrelic.admin:main"], + } + def with_librt(): try: - if sys.platform.startswith('linux'): + if sys.platform.startswith("linux"): import ctypes.util - return ctypes.util.find_library('rt') + + return ctypes.util.find_library("rt") except Exception: pass + def run_setup(with_extensions): def _run_setup(): @@ -180,35 +186,34 @@ def _run_setup(): if with_extensions: monotonic_libraries = [] if with_librt(): - monotonic_libraries = ['rt'] - - kwargs_tmp['ext_modules'] = [ - Extension("newrelic.packages.wrapt._wrappers", - ["newrelic/packages/wrapt/_wrappers.c"]), - Extension("newrelic.common._monotonic", - ["newrelic/common/_monotonic.c"], - libraries=monotonic_libraries), - Extension("newrelic.core._thread_utilization", - ["newrelic/core/_thread_utilization.c"]), - ] - kwargs_tmp['cmdclass'] = dict(build_ext=optional_build_ext) + monotonic_libraries = ["rt"] + + kwargs_tmp["ext_modules"] = [ + Extension("newrelic.packages.wrapt._wrappers", ["newrelic/packages/wrapt/_wrappers.c"]), + Extension( + "newrelic.common._monotonic", ["newrelic/common/_monotonic.c"], libraries=monotonic_libraries + ), + Extension("newrelic.core._thread_utilization", ["newrelic/core/_thread_utilization.c"]), + ] + kwargs_tmp["cmdclass"] = dict(build_ext=optional_build_ext) setup(**kwargs_tmp) - if os.environ.get('TDDIUM') is not None: + if os.environ.get("TDDIUM") is not None: try: - print('INFO: Running under tddium. Use lock.') + print("INFO: Running under tddium. Use lock.") from lock_file import LockFile except ImportError: - print('ERROR: Cannot import locking mechanism.') + print("ERROR: Cannot import locking mechanism.") _run_setup() else: - print('INFO: Attempting to create lock file.') - with LockFile('setup.lock', wait=True): + print("INFO: Attempting to create lock file.") + with LockFile("setup.lock", wait=True): _run_setup() else: _run_setup() + WARNING = """ WARNING: The optional C extension components of the Python agent could not be compiled. This can occur where a compiler is not present on the @@ -221,16 +226,16 @@ def _run_setup(): optimised C versions, will also be used resulting in additional overheads. """ -with_extensions = os.environ.get('NEW_RELIC_EXTENSIONS', None) +with_extensions = os.environ.get("NEW_RELIC_EXTENSIONS", None) if with_extensions: - if with_extensions.lower() == 'true': + if with_extensions.lower() == "true": with_extensions = True - elif with_extensions.lower() == 'false': + elif with_extensions.lower() == "false": with_extensions = False else: with_extensions = None -if hasattr(sys, 'pypy_version_info'): +if hasattr(sys, "pypy_version_info"): with_extensions = False if with_extensions is not None: @@ -242,20 +247,20 @@ def _run_setup(): except BuildExtFailed: - print(75 * '*') + print(75 * "*") print(WARNING) print("INFO: Trying to build without extensions.") print() - print(75 * '*') + print(75 * "*") run_setup(with_extensions=False) - print(75 * '*') + print(75 * "*") print(WARNING) print("INFO: Only pure Python agent was installed.") print() - print(75 * '*') + print(75 * "*") diff --git a/tests/framework_fastapi/conftest.py b/tests/framework_fastapi/conftest.py index ded9f2e8f..e976b05ed 100644 --- a/tests/framework_fastapi/conftest.py +++ b/tests/framework_fastapi/conftest.py @@ -13,28 +13,33 @@ # limitations under the License. import pytest - -from testing_support.fixtures import (code_coverage_fixture, - collector_agent_registration_fixture, collector_available_fixture) +from testing_support.fixtures import ( # noqa: F401; pylint: disable=W0611 + code_coverage_fixture, + collector_agent_registration_fixture, + collector_available_fixture, +) +from testing_support.fixtures import ( # noqa: F401; pylint: disable=W0611 + newrelic_caplog as caplog, +) _coverage_source = [ - 'newrelic.hooks.framework_fastapi', + "newrelic.hooks.framework_fastapi", ] code_coverage = code_coverage_fixture(source=_coverage_source) _default_settings = { - 'transaction_tracer.explain_threshold': 0.0, - 'transaction_tracer.transaction_threshold': 0.0, - 'transaction_tracer.stack_trace_threshold': 0.0, - 'debug.log_data_collector_payloads': True, - 'debug.record_transaction_failure': True, - 'debug.log_autorum_middleware': True, + "transaction_tracer.explain_threshold": 0.0, + "transaction_tracer.transaction_threshold": 0.0, + "transaction_tracer.stack_trace_threshold": 0.0, + "debug.log_data_collector_payloads": True, + "debug.record_transaction_failure": True, + "debug.log_autorum_middleware": True, } collector_agent_registration = collector_agent_registration_fixture( - app_name='Python Agent Test (framework_fastapi)', - default_settings=_default_settings) + app_name="Python Agent Test (framework_fastapi)", default_settings=_default_settings +) @pytest.fixture(scope="session") diff --git a/tests/framework_fastapi/test_application.py b/tests/framework_fastapi/test_application.py index 41860409a..451cddc8e 100644 --- a/tests/framework_fastapi/test_application.py +++ b/tests/framework_fastapi/test_application.py @@ -12,19 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging + import pytest from testing_support.fixtures import validate_transaction_metrics -@pytest.mark.parametrize("endpoint,transaction_name", ( - ("/sync", "_target_application:sync"), - ("/async", "_target_application:non_sync"), -)) -def test_application(app, endpoint, transaction_name): - @validate_transaction_metrics(transaction_name, - scoped_metrics=[("Function/" + transaction_name, 1)]) +@pytest.mark.parametrize( + "endpoint,transaction_name", + ( + ("/sync", "_target_application:sync"), + ("/async", "_target_application:non_sync"), + ), +) +def test_application(caplog, app, endpoint, transaction_name): + caplog.set_level(logging.ERROR) + + @validate_transaction_metrics(transaction_name, scoped_metrics=[("Function/" + transaction_name, 1)]) def _test(): response = app.get(endpoint) assert response.status == 200 + # Catch context propagation error messages + assert not caplog.records + _test() diff --git a/tests/framework_flask/test_views.py b/tests/framework_flask/test_views.py index 5f491dfb9..d4dd8178f 100644 --- a/tests/framework_flask/test_views.py +++ b/tests/framework_flask/test_views.py @@ -12,23 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest - -from testing_support.fixtures import (validate_transaction_metrics, - validate_transaction_errors, override_application_settings) - -from conftest import async_handler_support, skip_if_not_async_handler_support - +from conftest import ( # pylint: disable=E0611 + async_handler_support, + skip_if_not_async_handler_support, +) +from testing_support.fixtures import ( + validate_transaction_errors, + validate_transaction_metrics, +) scoped_metrics = [ - ('Function/flask.app:Flask.wsgi_app', 1), - ('Python/WSGI/Application', 1), - ('Python/WSGI/Response', 1), - ('Python/WSGI/Finalize', 1), - ('Function/flask.app:Flask.preprocess_request', 1), - ('Function/flask.app:Flask.process_response', 1), - ('Function/flask.app:Flask.do_teardown_request', 1), - ('Function/werkzeug.wsgi:ClosingIterator.close', 1), + ("Function/flask.app:Flask.wsgi_app", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/flask.app:Flask.preprocess_request", 1), + ("Function/flask.app:Flask.process_response", 1), + ("Function/flask.app:Flask.do_teardown_request", 1), + ("Function/werkzeug.wsgi:ClosingIterator.close", 1), ] @@ -48,46 +49,44 @@ def target_application(): from _test_views_async import _test_application return _test_application + @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_views:test_view', - scoped_metrics=scoped_metrics) +@validate_transaction_metrics("_test_views:test_view", scoped_metrics=scoped_metrics) def test_class_based_view(): application = target_application() - response = application.get('/view') - response.mustcontain('VIEW RESPONSE') + response = application.get("/view") + response.mustcontain("VIEW RESPONSE") + -@pytest.mark.xfail(reason="Currently broken in flask.") @skip_if_not_async_handler_support @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_views_async:test_async_view', - scoped_metrics=scoped_metrics) +@validate_transaction_metrics("_test_views_async:test_async_view", scoped_metrics=scoped_metrics) def test_class_based_async_view(): application = target_application() - response = application.get('/async_view') - response.mustcontain('ASYNC VIEW RESPONSE') + response = application.get("/async_view") + response.mustcontain("ASYNC VIEW RESPONSE") + @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_views:test_methodview', - scoped_metrics=scoped_metrics) +@validate_transaction_metrics("_test_views:test_methodview", scoped_metrics=scoped_metrics) def test_get_method_view(): application = target_application() - response = application.get('/methodview') - response.mustcontain('METHODVIEW GET RESPONSE') + response = application.get("/methodview") + response.mustcontain("METHODVIEW GET RESPONSE") + @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_views:test_methodview', - scoped_metrics=scoped_metrics) +@validate_transaction_metrics("_test_views:test_methodview", scoped_metrics=scoped_metrics) def test_post_method_view(): application = target_application() - response = application.post('/methodview') - response.mustcontain('METHODVIEW POST RESPONSE') + response = application.post("/methodview") + response.mustcontain("METHODVIEW POST RESPONSE") + -@pytest.mark.xfail(reason="Currently broken in flask.") @skip_if_not_async_handler_support @validate_transaction_errors(errors=[]) -@validate_transaction_metrics('_test_views_async:test_async_methodview', - scoped_metrics=scoped_metrics) +@validate_transaction_metrics("_test_views_async:test_async_methodview", scoped_metrics=scoped_metrics) def test_get_method_async_view(): application = target_application() - response = application.get('/async_methodview') - response.mustcontain('ASYNC METHODVIEW GET RESPONSE') + response = application.get("/async_methodview") + response.mustcontain("ASYNC METHODVIEW GET RESPONSE")