Skip to content

Commit 9c969f3

Browse files
authored
autoinstrumentation: catch ModuleNotFoundError when the library is not installed (#3423)
* catch ModuleNotFoundError when the library is not installed and prevent exception from bubbling up Signed-off-by: emdneto <[email protected]> * cleanup Signed-off-by: emdneto <[email protected]> * remove dup test Signed-off-by: emdneto <[email protected]> * Update CHANGELOG.md --------- Signed-off-by: emdneto <[email protected]>
1 parent 7562ff0 commit 9c969f3

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
1212
## Unreleased
1313

14+
### Added
15+
16+
### Fixed
17+
18+
- `opentelemetry-instrumentation` Catch `ModuleNotFoundError` when the library is not installed
19+
and log as debug instead of exception
20+
([#3423](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3423))
21+
1422
## Version 1.32.0/0.53b0 (2025-04-10)
1523

1624
### Added

opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/_load.py

+8
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ def _load_instrumentors(distro):
8282
exc.conflict,
8383
)
8484
continue
85+
except ModuleNotFoundError as exc:
86+
# ModuleNotFoundError is raised when the library is not installed
87+
# and the instrumentation is not required to be loaded.
88+
# See https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3421
89+
_logger.debug(
90+
"Skipping instrumentation %s: %s", entry_point.name, exc.msg
91+
)
92+
continue
8593
except ImportError:
8694
# in scenarios using the kubernetes operator to do autoinstrumentation some
8795
# instrumentors (usually requiring binary extensions) may fail to load

opentelemetry-instrumentation/tests/auto_instrumentation/test_load.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,12 @@ def test_load_instrumentors_dep_conflict(self, iter_mock, mock_logger): # pylin
326326
]
327327
)
328328

329+
@patch("opentelemetry.instrumentation.auto_instrumentation._load._logger")
329330
@patch(
330331
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
331332
)
332333
def test_load_instrumentors_import_error_does_not_stop_everything(
333-
self, iter_mock
334+
self, iter_mock, mock_logger
334335
):
335336
ep_mock1 = Mock(name="instr1")
336337
ep_mock2 = Mock(name="instr2")
@@ -354,6 +355,12 @@ def test_load_instrumentors_import_error_does_not_stop_everything(
354355
]
355356
)
356357
self.assertEqual(distro_mock.load_instrumentor.call_count, 2)
358+
mock_logger.exception.assert_any_call(
359+
"Importing of %s failed, skipping it",
360+
ep_mock1.name,
361+
)
362+
363+
mock_logger.debug.assert_any_call("Instrumented %s", ep_mock2.name)
357364

358365
@patch(
359366
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
@@ -382,6 +389,46 @@ def test_load_instrumentors_raises_exception(self, iter_mock):
382389
)
383390
self.assertEqual(distro_mock.load_instrumentor.call_count, 1)
384391

392+
@patch("opentelemetry.instrumentation.auto_instrumentation._load._logger")
393+
@patch(
394+
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
395+
)
396+
def test_load_instrumentors_module_not_found_error(
397+
self, iter_mock, mock_logger
398+
):
399+
ep_mock1 = Mock()
400+
ep_mock1.name = "instr1"
401+
402+
ep_mock2 = Mock()
403+
ep_mock2.name = "instr2"
404+
405+
distro_mock = Mock()
406+
407+
distro_mock.load_instrumentor.side_effect = [
408+
ModuleNotFoundError("No module named 'fake_module'"),
409+
None,
410+
]
411+
412+
iter_mock.side_effect = [(), (ep_mock1, ep_mock2), ()]
413+
414+
_load._load_instrumentors(distro_mock)
415+
416+
distro_mock.load_instrumentor.assert_has_calls(
417+
[
418+
call(ep_mock1, raise_exception_on_conflict=True),
419+
call(ep_mock2, raise_exception_on_conflict=True),
420+
]
421+
)
422+
self.assertEqual(distro_mock.load_instrumentor.call_count, 2)
423+
424+
mock_logger.debug.assert_any_call(
425+
"Skipping instrumentation %s: %s",
426+
"instr1",
427+
"No module named 'fake_module'",
428+
)
429+
430+
mock_logger.debug.assert_any_call("Instrumented %s", ep_mock2.name)
431+
385432
def test_load_instrumentors_no_entry_point_mocks(self):
386433
distro_mock = Mock()
387434
_load._load_instrumentors(distro_mock)

0 commit comments

Comments
 (0)