From cb0752f3fc30e8c7f5067170b27d517a5c3aa14f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 15 Apr 2025 15:05:47 +0200 Subject: [PATCH] [release/v1.32.x-0.53bx] autoinstrumentation: catch `ModuleNotFoundError` when the library is not installed (#3425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * autoinstrumentation: catch `ModuleNotFoundError` when the library is not installed Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> * Fixup workflows --------- Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com> --- .github/workflows/lint_0.yml | 2 +- .github/workflows/misc_0.yml | 2 +- .github/workflows/test_0.yml | 2 +- .github/workflows/test_1.yml | 2 +- .github/workflows/test_2.yml | 2 +- CHANGELOG.md | 8 +++ .../auto_instrumentation/_load.py | 8 +++ .../tests/auto_instrumentation/test_load.py | 49 ++++++++++++++++++- 8 files changed, 69 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint_0.yml b/.github/workflows/lint_0.yml index 0f17501b5..a2b69ab12 100644 --- a/.github/workflows/lint_0.yml +++ b/.github/workflows/lint_0.yml @@ -10,7 +10,7 @@ on: pull_request: env: - CORE_REPO_SHA: opentelemetrybot/prepare-release-1.32.0-0.53b0 + CORE_REPO_SHA: release/v1.32.x-0.53bx CONTRIB_REPO_SHA: main PIP_EXISTS_ACTION: w diff --git a/.github/workflows/misc_0.yml b/.github/workflows/misc_0.yml index af0bba0a9..950fec459 100644 --- a/.github/workflows/misc_0.yml +++ b/.github/workflows/misc_0.yml @@ -10,7 +10,7 @@ on: pull_request: env: - CORE_REPO_SHA: opentelemetrybot/prepare-release-1.32.0-0.53b0 + CORE_REPO_SHA: release/v1.32.x-0.53bx CONTRIB_REPO_SHA: main PIP_EXISTS_ACTION: w diff --git a/.github/workflows/test_0.yml b/.github/workflows/test_0.yml index c6da23289..48baba81d 100644 --- a/.github/workflows/test_0.yml +++ b/.github/workflows/test_0.yml @@ -10,7 +10,7 @@ on: pull_request: env: - CORE_REPO_SHA: opentelemetrybot/prepare-release-1.32.0-0.53b0 + CORE_REPO_SHA: release/v1.32.x-0.53bx CONTRIB_REPO_SHA: main PIP_EXISTS_ACTION: w diff --git a/.github/workflows/test_1.yml b/.github/workflows/test_1.yml index d0d0fbc1f..7ef83f32f 100644 --- a/.github/workflows/test_1.yml +++ b/.github/workflows/test_1.yml @@ -10,7 +10,7 @@ on: pull_request: env: - CORE_REPO_SHA: opentelemetrybot/prepare-release-1.32.0-0.53b0 + CORE_REPO_SHA: release/v1.32.x-0.53bx CONTRIB_REPO_SHA: main PIP_EXISTS_ACTION: w diff --git a/.github/workflows/test_2.yml b/.github/workflows/test_2.yml index a10702ee6..5180f2583 100644 --- a/.github/workflows/test_2.yml +++ b/.github/workflows/test_2.yml @@ -10,7 +10,7 @@ on: pull_request: env: - CORE_REPO_SHA: opentelemetrybot/prepare-release-1.32.0-0.53b0 + CORE_REPO_SHA: release/v1.32.x-0.53bx CONTRIB_REPO_SHA: main PIP_EXISTS_ACTION: w diff --git a/CHANGELOG.md b/CHANGELOG.md index d01e135a8..6b68b2514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > The following components are released independently and maintain individual CHANGELOG files. > Use [this search for a list of all CHANGELOG.md files in this repo](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-python-contrib+path%3A**%2FCHANGELOG.md&type=code). +## Unreleased + +### Fixed + +- `opentelemetry-instrumentation` Catch `ModuleNotFoundError` when the library is not installed + and log as debug instead of exception + ([#3425](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3425)) + ## Version 1.32.0/0.53b0 (2025-04-10) ### Added diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/_load.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/_load.py index 5084879fa..4bbd95b41 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/_load.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/_load.py @@ -82,6 +82,14 @@ def _load_instrumentors(distro): exc.conflict, ) continue + except ModuleNotFoundError as exc: + # ModuleNotFoundError is raised when the library is not installed + # and the instrumentation is not required to be loaded. + # See https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3421 + _logger.debug( + "Skipping instrumentation %s: %s", entry_point.name, exc.msg + ) + continue except ImportError: # in scenarios using the kubernetes operator to do autoinstrumentation some # instrumentors (usually requiring binary extensions) may fail to load diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_load.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_load.py index f4d489173..37af27030 100644 --- a/opentelemetry-instrumentation/tests/auto_instrumentation/test_load.py +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_load.py @@ -326,11 +326,12 @@ class TestLoad(TestCase): ] ) + @patch("opentelemetry.instrumentation.auto_instrumentation._load._logger") @patch( "opentelemetry.instrumentation.auto_instrumentation._load.entry_points" ) def test_load_instrumentors_import_error_does_not_stop_everything( - self, iter_mock + self, iter_mock, mock_logger ): ep_mock1 = Mock(name="instr1") ep_mock2 = Mock(name="instr2") @@ -354,6 +355,12 @@ class TestLoad(TestCase): ] ) self.assertEqual(distro_mock.load_instrumentor.call_count, 2) + mock_logger.exception.assert_any_call( + "Importing of %s failed, skipping it", + ep_mock1.name, + ) + + mock_logger.debug.assert_any_call("Instrumented %s", ep_mock2.name) @patch( "opentelemetry.instrumentation.auto_instrumentation._load.entry_points" @@ -382,6 +389,46 @@ class TestLoad(TestCase): ) self.assertEqual(distro_mock.load_instrumentor.call_count, 1) + @patch("opentelemetry.instrumentation.auto_instrumentation._load._logger") + @patch( + "opentelemetry.instrumentation.auto_instrumentation._load.entry_points" + ) + def test_load_instrumentors_module_not_found_error( + self, iter_mock, mock_logger + ): + ep_mock1 = Mock() + ep_mock1.name = "instr1" + + ep_mock2 = Mock() + ep_mock2.name = "instr2" + + distro_mock = Mock() + + distro_mock.load_instrumentor.side_effect = [ + ModuleNotFoundError("No module named 'fake_module'"), + None, + ] + + iter_mock.side_effect = [(), (ep_mock1, ep_mock2), ()] + + _load._load_instrumentors(distro_mock) + + distro_mock.load_instrumentor.assert_has_calls( + [ + call(ep_mock1, raise_exception_on_conflict=True), + call(ep_mock2, raise_exception_on_conflict=True), + ] + ) + self.assertEqual(distro_mock.load_instrumentor.call_count, 2) + + mock_logger.debug.assert_any_call( + "Skipping instrumentation %s: %s", + "instr1", + "No module named 'fake_module'", + ) + + mock_logger.debug.assert_any_call("Instrumented %s", ep_mock2.name) + def test_load_instrumentors_no_entry_point_mocks(self): distro_mock = Mock() _load._load_instrumentors(distro_mock)