Skip to content

Commit

Permalink
Add package_capturing.enabled setting (#982)
Browse files Browse the repository at this point in the history
* Add capture_dependencies.enabled setting

* Change setting name

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
hmstepanek and mergify[bot] committed Nov 16, 2023
1 parent 5eb1095 commit fdaa4be
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 37 deletions.
1 change: 1 addition & 0 deletions newrelic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions newrelic/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ class MachineLearningInferenceEventsValueSettings(Settings):
pass


class PackageReportingSettings(Settings):
pass


class CodeLevelMetricsSettings(Settings):
pass

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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():
Expand Down
77 changes: 40 additions & 37 deletions newrelic/core/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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))

Expand Down
21 changes: 21 additions & 0 deletions tests/agent_unittests/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit fdaa4be

Please sign in to comment.