diff --git a/solarwinds_apm/apm_config.py b/solarwinds_apm/apm_config.py index 97dea229..98c1e5f5 100644 --- a/solarwinds_apm/apm_config.py +++ b/solarwinds_apm/apm_config.py @@ -4,6 +4,8 @@ # # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +# pylint: disable=too-many-lines + import json import logging import os @@ -169,10 +171,6 @@ def __init__( # (Re-)Calculate config if AppOptics self.metric_format = self._calculate_metric_format() self.certificates = self._calculate_certificates() - self.__config["export_logs_enabled"] = self._calculate_logs_enabled() - self.__config["export_metrics_enabled"] = ( - self._calculate_metrics_enabled() - ) logger.debug("Set ApmConfig as: %s", self) @@ -279,6 +277,58 @@ def calculate_is_lambda(cls) -> bool: return True return False + # TODO Make cnf_dict required + @classmethod + def calculate_logs_enabled( + cls, + cnf_dict: dict = None, + ) -> bool: + """Return if export of instrumentor logs telemetry enabled. + Invalid boolean values are ignored. + Order of precedence: Environment Variable > config file > default True. + Optional cnf_dict is presumably already from a config file, else a call + to get_cnf_dict() is made for a fresh read.""" + logs_enabled = True + if cnf_dict is None: + cnf_dict = cls.get_cnf_dict() + if cnf_dict: + cnf_enabled = cls.convert_to_bool( + cnf_dict.get("export_logs_enabled") + ) + logs_enabled = ( + cnf_enabled if cnf_enabled is not None else logs_enabled + ) + env_enabled = cls.convert_to_bool( + os.environ.get("SW_APM_EXPORT_LOGS_ENABLED") + ) + return env_enabled if env_enabled is not None else logs_enabled + + # TODO Make cnf_dict required + @classmethod + def calculate_metrics_enabled( + cls, + cnf_dict: dict = None, + ) -> bool: + """Return if export of instrumentor metrics telemetry enabled. + Invalid boolean values are ignored. + Order of precedence: Environment Variable > config file > default True. + Optional cnf_dict is presumably already from a config file, else a call + to get_cnf_dict() is made for a fresh read.""" + metrics_enabled = True + if cnf_dict is None: + cnf_dict = cls.get_cnf_dict() + if cnf_dict: + cnf_enabled = cls.convert_to_bool( + cnf_dict.get("export_metrics_enabled") + ) + metrics_enabled = ( + cnf_enabled if cnf_enabled is not None else metrics_enabled + ) + env_enabled = cls.convert_to_bool( + os.environ.get("SW_APM_EXPORT_metrics_ENABLED") + ) + return env_enabled if env_enabled is not None else metrics_enabled + def _calculate_agent_enabled_platform(self) -> bool: """Checks if agent is enabled/disabled based on platform""" if not sys.platform.startswith("linux"): @@ -649,36 +699,6 @@ def _calculate_certificates(self) -> str: ) return certs - def _calculate_logs_enabled(self) -> bool: - """Return if export of logs telemetry enabled, based on collector. - Always False if AO collector, else use current config.""" - host = self.get("collector") - if host: - if ( - INTL_SWO_AO_COLLECTOR in host - or INTL_SWO_AO_STG_COLLECTOR in host - ): - logger.warning( - "AO collector detected. Defaulting to disabled logs export." - ) - return False - return self.get("export_logs_enabled") - - def _calculate_metrics_enabled(self) -> bool: - """Return if export of metrics telemetry enabled, based on collector. - Always False if AO collector, else use current config.""" - host = self.get("collector") - if host: - if ( - INTL_SWO_AO_COLLECTOR in host - or INTL_SWO_AO_STG_COLLECTOR in host - ): - logger.warning( - "AO collector detected. Defaulting to disabled OTLP metrics export." - ) - return False - return self.get("export_metrics_enabled") - def mask_service_key(self) -> str: """Return masked service key except first 4 and last 4 chars""" service_key = self.__config.get("service_key") diff --git a/solarwinds_apm/distro.py b/solarwinds_apm/distro.py index 11eabf63..c44ce207 100644 --- a/solarwinds_apm/distro.py +++ b/solarwinds_apm/distro.py @@ -12,6 +12,7 @@ from os import environ from typing import Any +from opentelemetry._logs import NoOpLoggerProvider from opentelemetry.environment_variables import ( OTEL_PROPAGATORS, OTEL_TRACES_EXPORTER, @@ -22,6 +23,7 @@ OTEL_PYTHON_LOG_FORMAT, ) from opentelemetry.instrumentation.version import __version__ as inst_version +from opentelemetry.metrics import NoOpMeterProvider from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, OTEL_EXPORTER_OTLP_PROTOCOL, @@ -283,6 +285,19 @@ def load_instrumentor(self, entry_point: EntryPoint, **kwargs): kwargs, ) + # If SW_APM_EXPORT_(METRICS|LOGS)_ENABLED is set to false, + # then we load the instrumentor with NoOp providers to disable + # Otel instrumentor-based metrics/logs export if supported. + # Assumes kwargs are ignored if not implemented + # for the current instrumentation library. + if ( + SolarWindsApmConfig.calculate_metrics_enabled(self._cnf_dict) + is False + ): + kwargs["meter_provider"] = NoOpMeterProvider() + if SolarWindsApmConfig.calculate_logs_enabled(self._cnf_dict) is False: + kwargs["logger_provider"] = NoOpLoggerProvider() + try: instrumentor: BaseInstrumentor = entry_point.load() except Exception as ex: # pylint: disable=broad-except diff --git a/tests/unit/test_apm_config/test_apm_config.py b/tests/unit/test_apm_config/test_apm_config.py index 658ef9b9..1fb04429 100644 --- a/tests/unit/test_apm_config/test_apm_config.py +++ b/tests/unit/test_apm_config/test_apm_config.py @@ -379,14 +379,9 @@ def test__init_ao_settings_helpers_called(self, mocker): mock_certs = mocker.patch( "solarwinds_apm.apm_config.SolarWindsApmConfig._calculate_certificates" ) - mock_logs_enabled = mocker.patch( - "solarwinds_apm.apm_config.SolarWindsApmConfig._calculate_logs_enabled" - ) - apm_config.SolarWindsApmConfig() mock_metric_format.assert_called_once() mock_certs.assert_called_once() - mock_logs_enabled.assert_called_once() def test_calculate_metric_format_no_collector(self, mocker): assert apm_config.SolarWindsApmConfig()._calculate_metric_format() == 2 @@ -539,104 +534,6 @@ def test_calculate_certificates_ao_prod_trustedpath_file_present(self, mocker): mock_get_public_cert.configure_mock(return_value="foo") assert apm_config.SolarWindsApmConfig()._calculate_certificates() == "bar" - def test_calculate_logs_enabled_no_collector_enabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "", - "SW_APM_EXPORT_LOGS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_logs_enabled() == True - - def test_calculate_logs_enabled_no_collector_disabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "", - "SW_APM_EXPORT_LOGS_ENABLED": "false", - }) - assert apm_config.SolarWindsApmConfig()._calculate_logs_enabled() == False - - def test_calculate_logs_enabled_not_ao_enabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "some-other-collector", - "SW_APM_EXPORT_LOGS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_logs_enabled() == True - - def test_calculate_logs_enabled_not_ao_disabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "some-other-collector", - "SW_APM_EXPORT_LOGS_ENABLED": "false", - }) - assert apm_config.SolarWindsApmConfig()._calculate_logs_enabled() == False - - def test_calculate_logs_enabled_ao_prod(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": INTL_SWO_AO_COLLECTOR, - "SW_APM_EXPORT_LOGS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_logs_enabled() == False - - def test_calculate_logs_enabled_ao_staging(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": INTL_SWO_AO_STG_COLLECTOR, - "SW_APM_EXPORT_LOGS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_logs_enabled() == False - - def test_calculate_logs_enabled_ao_prod_with_port(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "{}:123".format(INTL_SWO_AO_COLLECTOR), - "SW_APM_EXPORT_LOGS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_logs_enabled() == False - - def test_calculate_metrics_enabled_no_collector_enabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "", - "SW_APM_EXPORT_METRICS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_metrics_enabled() == True - - def test_calculate_metrics_enabled_no_collector_disabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "", - "SW_APM_EXPORT_METRICS_ENABLED": "false", - }) - assert apm_config.SolarWindsApmConfig()._calculate_metrics_enabled() == False - - def test_calculate_metrics_enabled_not_ao_enabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "some-other-collector", - "SW_APM_EXPORT_METRICS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_metrics_enabled() == True - - def test_calculate_metrics_enabled_not_ao_disabled(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "some-other-collector", - "SW_APM_EXPORT_METRICS_ENABLED": "false", - }) - assert apm_config.SolarWindsApmConfig()._calculate_metrics_enabled() == False - - def test_calculate_metrics_enabled_ao_prod(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": INTL_SWO_AO_COLLECTOR, - "SW_APM_EXPORT_METRICS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_metrics_enabled() == False - - def test_calculate_metrics_enabled_ao_staging(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": INTL_SWO_AO_STG_COLLECTOR, - "SW_APM_EXPORT_METRICS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_metrics_enabled() == False - - def test_calculate_metrics_enabled_ao_prod_with_port(self, mocker): - mocker.patch.dict(os.environ, { - "SW_APM_COLLECTOR": "{}:123".format(INTL_SWO_AO_COLLECTOR), - "SW_APM_EXPORT_METRICS_ENABLED": "true", - }) - assert apm_config.SolarWindsApmConfig()._calculate_metrics_enabled() == False - def test_mask_service_key_no_key_empty_default(self, mocker): mock_entry_points = mocker.patch( "solarwinds_apm.apm_config.entry_points"