Skip to content

opentelemetry-instrumentation: don't fail if an instrumentor raises ImportError #2923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Deprecation of pkg_resource in favor of importlib.metadata
([#2871](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2871))
- `opentelemetry-instrumentation` Don't fail distro loading if instrumentor raises ImportError, instead skip them
([#2923](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2923))

## Version 1.27.0/0.48b0 (2024-08-28)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ def _load_instrumentors(distro):
# tell instrumentation to not run dep checks again as we already did it above
distro.load_instrumentor(entry_point, skip_dep_check=True)
_logger.debug("Instrumented %s", entry_point.name)
except ImportError:
# in scenarios using the kubernetes operator to do autoinstrumentation some
# instrumentors (usually requiring binary extensions) may fail to load
# because the injected autoinstrumentation code does not match the application
# environment regarding python version, libc, etc... In this case it's better
# to skip the single instrumentation rather than failing to load everything
# so treat differently ImportError than the rest of exceptions
_logger.exception(
"Importing of %s failed, skipping it", entry_point.name
)
continue
except Exception as exc: # pylint: disable=broad-except
_logger.exception("Instrumenting of %s failed", entry_point.name)
raise exc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,70 @@ def test_load_instrumentors_dep_conflict(self, iter_mock, dep_mock): # pylint:
)
distro_mock.load_instrumentor.assert_called_once()

@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.get_dist_dependency_conflicts"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_instrumentors_import_error_does_not_stop_everything(
self, iter_mock, dep_mock
):
ep_mock1 = Mock(name="instr1")
ep_mock2 = Mock(name="instr2")

distro_mock = Mock()
distro_mock.load_instrumentor.side_effect = [ImportError, None]

# Mock entry points in order
iter_mock.side_effect = [
(),
(ep_mock1, ep_mock2),
(),
]
dep_mock.return_value = None

_load._load_instrumentors(distro_mock)

distro_mock.load_instrumentor.assert_has_calls(
[
call(ep_mock1, skip_dep_check=True),
call(ep_mock2, skip_dep_check=True),
]
)
self.assertEqual(distro_mock.load_instrumentor.call_count, 2)

@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.get_dist_dependency_conflicts"
)
@patch(
"opentelemetry.instrumentation.auto_instrumentation._load.entry_points"
)
def test_load_instrumentors_raises_exception(self, iter_mock, dep_mock):
ep_mock1 = Mock(name="instr1")
ep_mock2 = Mock(name="instr2")

distro_mock = Mock()
distro_mock.load_instrumentor.side_effect = [ValueError, None]

# Mock entry points in order
iter_mock.side_effect = [
(),
(ep_mock1, ep_mock2),
(),
]
dep_mock.return_value = None

with self.assertRaises(ValueError):
_load._load_instrumentors(distro_mock)

distro_mock.load_instrumentor.assert_has_calls(
[
call(ep_mock1, skip_dep_check=True),
]
)
self.assertEqual(distro_mock.load_instrumentor.call_count, 1)

def test_load_instrumentors_no_entry_point_mocks(self):
distro_mock = Mock()
_load._load_instrumentors(distro_mock)
Expand Down