Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

### Fixed

- `opentelemetry-instrumentation` Catch `ModuleNotFoundError` when the library is not installed
and prevent exception from bubbling up
([#3423](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3423))

## Version 1.32.0/0.53b0 (2025-04-10)

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,12 @@ def test_load_instrumentors_dep_conflict(self, iter_mock, mock_logger): # pylin
]
)

@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")
Expand All @@ -354,6 +355,12 @@ def test_load_instrumentors_import_error_does_not_stop_everything(
]
)
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"
Expand Down Expand Up @@ -382,6 +389,46 @@ def test_load_instrumentors_raises_exception(self, iter_mock):
)
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)
Expand Down