Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 log as debug instead of exception
([#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