diff --git a/newrelic/config.py b/newrelic/config.py index 5c4c52464..ee6809cb9 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -557,6 +557,7 @@ def _process_configuration(section): _process_setting(section, "machine_learning.enabled", "getboolean", None) _process_setting(section, "machine_learning.inference_events_value.enabled", "getboolean", None) + _process_setting(section, "package_reporting.enabled", "getboolean", None) # Loading of configuration from specified file and for specified diff --git a/newrelic/core/config.py b/newrelic/core/config.py index 483e23df8..9bb0ca774 100644 --- a/newrelic/core/config.py +++ b/newrelic/core/config.py @@ -143,6 +143,10 @@ class MachineLearningInferenceEventsValueSettings(Settings): pass +class PackageReportingSettings(Settings): + pass + + class CodeLevelMetricsSettings(Settings): pass @@ -398,6 +402,7 @@ class EventHarvestConfigHarvestLimitSettings(Settings): _settings.application_logging.metrics = ApplicationLoggingMetricsSettings() _settings.machine_learning = MachineLearningSettings() _settings.machine_learning.inference_events_value = MachineLearningInferenceEventsValueSettings() +_settings.package_reporting = PackageReportingSettings() _settings.attributes = AttributesSettings() _settings.browser_monitoring = BrowserMonitorSettings() _settings.browser_monitoring.attributes = BrowserMonitorAttributesSettings() @@ -898,6 +903,7 @@ def default_otlp_host(host): _settings.machine_learning.inference_events_value.enabled = _environ_as_bool( "NEW_RELIC_MACHINE_LEARNING_INFERENCE_EVENT_VALUE_ENABLED", default=False ) +_settings.package_reporting.enabled = _environ_as_bool("NEW_RELIC_PACKAGE_REPORTING_ENABLED", default=True) def global_settings(): diff --git a/newrelic/core/environment.py b/newrelic/core/environment.py index 9bca085a3..6d24eced5 100644 --- a/newrelic/core/environment.py +++ b/newrelic/core/environment.py @@ -29,6 +29,7 @@ physical_processor_count, total_physical_memory, ) +from newrelic.core.config import global_settings from newrelic.packages.isort import stdlibs as isort_stdlibs try: @@ -202,44 +203,46 @@ def environment_settings(): plugins = [] - # Using any iterable to create a snapshot of sys.modules can occassionally - # fail in a rare case when modules are imported in parallel by different - # threads. - # - # TL;DR: Do NOT use an iterable on the original sys.modules to generate the - # list - for name, module in sys.modules.copy().items(): - # Exclude lib.sub_paths as independent modules except for newrelic.hooks. - nr_hook = name.startswith("newrelic.hooks.") - if "." in name and not nr_hook or name.startswith("_"): - continue - - # If the module isn't actually loaded (such as failed relative imports - # in Python 2.7), the module will be None and should not be reported. - try: - if not module: + settings = global_settings() + if settings and settings.package_reporting.enabled: + # Using any iterable to create a snapshot of sys.modules can occassionally + # fail in a rare case when modules are imported in parallel by different + # threads. + # + # TL;DR: Do NOT use an iterable on the original sys.modules to generate the + # list + for name, module in sys.modules.copy().items(): + # Exclude lib.sub_paths as independent modules except for newrelic.hooks. + nr_hook = name.startswith("newrelic.hooks.") + if "." in name and not nr_hook or name.startswith("_"): continue - except Exception: - # if the application uses generalimport to manage optional depedencies, - # it's possible that generalimport.MissingOptionalDependency is raised. - # In this case, we should not report the module as it is not actually loaded and - # is not a runtime dependency of the application. - # - continue - - # Exclude standard library/built-in modules. - if name in stdlib_builtin_module_names: - continue - - try: - version = get_package_version(name) - except Exception: - version = None - - # If it has no version it's likely not a real package so don't report it unless - # it's a new relic hook. - if version or nr_hook: - plugins.append("%s (%s)" % (name, version)) + + # If the module isn't actually loaded (such as failed relative imports + # in Python 2.7), the module will be None and should not be reported. + try: + if not module: + continue + except Exception: + # if the application uses generalimport to manage optional depedencies, + # it's possible that generalimport.MissingOptionalDependency is raised. + # In this case, we should not report the module as it is not actually loaded and + # is not a runtime dependency of the application. + # + continue + + # Exclude standard library/built-in modules. + if name in stdlib_builtin_module_names: + continue + + try: + version = get_package_version(name) + except Exception: + version = None + + # If it has no version it's likely not a real package so don't report it unless + # it's a new relic hook. + if version or nr_hook: + plugins.append("%s (%s)" % (name, version)) env.append(("Plugin List", plugins)) diff --git a/tests/agent_unittests/test_environment.py b/tests/agent_unittests/test_environment.py index b2c639adc..84dd753a9 100644 --- a/tests/agent_unittests/test_environment.py +++ b/tests/agent_unittests/test_environment.py @@ -15,9 +15,13 @@ import sys import pytest +from testing_support.fixtures import override_generic_settings +from newrelic.core.config import global_settings from newrelic.core.environment import environment_settings +settings = global_settings() + def module(version): class Module(object): @@ -47,6 +51,23 @@ def test_plugin_list(): assert "pytest (%s)" % (pytest.__version__) in plugin_list +@override_generic_settings(settings, {"package_reporting.enabled": False}) +def test_plugin_list_when_package_reporting_disabled(): + # Let's pretend we fired an import hook + import newrelic.hooks.adapter_gunicorn # noqa: F401 + + environment_info = environment_settings() + + for key, plugin_list in environment_info: + if key == "Plugin List": + break + else: + assert False, "'Plugin List' not found" + + # Check that bogus plugins don't get reported + assert plugin_list == [] + + class NoIteratorDict(object): def __init__(self, d): self.d = d