Skip to content

Commit f9b310b

Browse files
committed
Make auto instrumentation use the same dependency resolver as manual instrumentation does
1 parent 139d787 commit f9b310b

File tree

10 files changed

+418
-198
lines changed

10 files changed

+418
-198
lines changed

CHANGELOG.md

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

14+
### Added
15+
16+
- `opentelemetry-instrumentation` Make auto instrumentation use the same dependency resolver as manual instrumentation does
17+
([#3202](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3202))
18+
1419
### Fixed
1520

1621
- `opentelemetry-instrumentation` Fix client address is set to server address in new semconv

instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py

+54-39
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import unittest
1818
from timeit import default_timer
19-
from unittest.mock import Mock, patch
19+
from unittest.mock import Mock, call, patch
2020

2121
import fastapi
2222
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
@@ -37,6 +37,10 @@
3737
from opentelemetry.instrumentation.auto_instrumentation._load import (
3838
_load_instrumentors,
3939
)
40+
from opentelemetry.instrumentation.dependencies import (
41+
DependencyConflict,
42+
DependencyConflictError,
43+
)
4044
from opentelemetry.sdk.metrics.export import (
4145
HistogramDataPoint,
4246
NumberDataPoint,
@@ -54,10 +58,7 @@
5458
from opentelemetry.semconv.trace import SpanAttributes
5559
from opentelemetry.test.globals_test import reset_trace_globals
5660
from opentelemetry.test.test_base import TestBase
57-
from opentelemetry.util._importlib_metadata import (
58-
PackageNotFoundError,
59-
entry_points,
60-
)
61+
from opentelemetry.util._importlib_metadata import entry_points
6162
from opentelemetry.util.http import (
6263
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
6364
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
@@ -1031,26 +1032,6 @@ def client_response_hook(send_span, scope, message):
10311032
)
10321033

10331034

1034-
def mock_version_with_fastapi(*args, **kwargs):
1035-
req_name = args[0]
1036-
if req_name == "fastapi":
1037-
# TODO: Value now matters
1038-
return "0.58"
1039-
raise PackageNotFoundError()
1040-
1041-
1042-
def mock_version_with_old_fastapi(*args, **kwargs):
1043-
req_name = args[0]
1044-
if req_name == "fastapi":
1045-
# TODO: Value now matters
1046-
return "0.57"
1047-
raise PackageNotFoundError()
1048-
1049-
1050-
def mock_version_without_fastapi(*args, **kwargs):
1051-
raise PackageNotFoundError()
1052-
1053-
10541035
class TestAutoInstrumentation(TestBaseAutoFastAPI):
10551036
"""Test the auto-instrumented variant
10561037
@@ -1062,31 +1043,65 @@ def test_entry_point_exists(self):
10621043
(ep,) = entry_points(group="opentelemetry_instrumentor")
10631044
self.assertEqual(ep.name, "fastapi")
10641045

1065-
@patch("opentelemetry.instrumentation.dependencies.version")
1066-
def test_instruments_with_fastapi_installed(self, mock_version):
1067-
mock_version.side_effect = mock_version_with_fastapi
1046+
@staticmethod
1047+
def _instrumentation_loaded_successfully_call():
1048+
return call("Instrumented %s", "fastapi")
1049+
1050+
@staticmethod
1051+
def _instrumentation_failed_to_load_call(dependency_conflict):
1052+
return call(
1053+
"Skipping instrumentation %s: %s", "fastapi", dependency_conflict
1054+
)
1055+
1056+
@patch("opentelemetry.instrumentation.auto_instrumentation._load._logger")
1057+
def test_instruments_with_fastapi_installed(self, mock_logger):
10681058
mock_distro = Mock()
1059+
mock_distro.load_instrumentor.return_value = None
10691060
_load_instrumentors(mock_distro)
1070-
mock_version.assert_called_once_with("fastapi")
10711061
self.assertEqual(len(mock_distro.load_instrumentor.call_args_list), 1)
10721062
(ep,) = mock_distro.load_instrumentor.call_args.args
10731063
self.assertEqual(ep.name, "fastapi")
1064+
mock_logger.debug.assert_has_calls(
1065+
[self._instrumentation_loaded_successfully_call()]
1066+
)
10741067

1075-
@patch("opentelemetry.instrumentation.dependencies.version")
1076-
def test_instruments_with_old_fastapi_installed(self, mock_version): # pylint: disable=no-self-use
1077-
mock_version.side_effect = mock_version_with_old_fastapi
1068+
@patch("opentelemetry.instrumentation.auto_instrumentation._load._logger")
1069+
def test_instruments_with_old_fastapi_installed(self, mock_logger): # pylint: disable=no-self-use
1070+
dependency_conflict = DependencyConflict("0.58", "0.57")
10781071
mock_distro = Mock()
1072+
mock_distro.load_instrumentor.side_effect = DependencyConflictError(
1073+
dependency_conflict
1074+
)
10791075
_load_instrumentors(mock_distro)
1080-
mock_version.assert_called_once_with("fastapi")
1081-
mock_distro.load_instrumentor.assert_not_called()
1076+
self.assertEqual(len(mock_distro.load_instrumentor.call_args_list), 1)
1077+
(ep,) = mock_distro.load_instrumentor.call_args.args
1078+
self.assertEqual(ep.name, "fastapi")
1079+
assert (
1080+
self._instrumentation_loaded_successfully_call()
1081+
not in mock_logger.debug.call_args_list
1082+
)
1083+
mock_logger.debug.assert_has_calls(
1084+
[self._instrumentation_failed_to_load_call(dependency_conflict)]
1085+
)
10821086

1083-
@patch("opentelemetry.instrumentation.dependencies.version")
1084-
def test_instruments_without_fastapi_installed(self, mock_version): # pylint: disable=no-self-use
1085-
mock_version.side_effect = mock_version_without_fastapi
1087+
@patch("opentelemetry.instrumentation.auto_instrumentation._load._logger")
1088+
def test_instruments_without_fastapi_installed(self, mock_logger): # pylint: disable=no-self-use
1089+
dependency_conflict = DependencyConflict("0.58", None)
10861090
mock_distro = Mock()
1091+
mock_distro.load_instrumentor.side_effect = DependencyConflictError(
1092+
dependency_conflict
1093+
)
10871094
_load_instrumentors(mock_distro)
1088-
mock_version.assert_called_once_with("fastapi")
1089-
mock_distro.load_instrumentor.assert_not_called()
1095+
self.assertEqual(len(mock_distro.load_instrumentor.call_args_list), 1)
1096+
(ep,) = mock_distro.load_instrumentor.call_args.args
1097+
self.assertEqual(ep.name, "fastapi")
1098+
assert (
1099+
self._instrumentation_loaded_successfully_call()
1100+
not in mock_logger.debug.call_args_list
1101+
)
1102+
mock_logger.debug.assert_has_calls(
1103+
[self._instrumentation_failed_to_load_call(dependency_conflict)]
1104+
)
10901105

10911106
def _create_app(self):
10921107
# instrumentation is handled by the instrument call

instrumentation/opentelemetry-instrumentation-kafka-python/tests/test_instrumentation.py

+102
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
from importlib.metadata import PackageNotFoundError
1415
from unittest import TestCase
16+
from unittest.mock import call, patch
1517

1618
from kafka import KafkaConsumer, KafkaProducer
1719
from wrapt import BoundFunctionWrapper
1820

1921
from opentelemetry.instrumentation.kafka import KafkaInstrumentor
22+
from opentelemetry.instrumentation.kafka.package import (
23+
_instruments,
24+
_instruments_kafka_python,
25+
_instruments_kafka_python_ng,
26+
)
2027

2128

2229
class TestKafka(TestCase):
@@ -34,3 +41,98 @@ def test_instrument_api(self) -> None:
3441
self.assertFalse(
3542
isinstance(KafkaConsumer.__next__, BoundFunctionWrapper)
3643
)
44+
45+
@patch("opentelemetry.instrumentation.kafka.distribution")
46+
def test_instrumentation_dependencies_kafka_python_installed(
47+
self, mock_distribution
48+
) -> None:
49+
instrumentation = KafkaInstrumentor()
50+
51+
def _distribution(name):
52+
if name == "kafka-python":
53+
return None
54+
raise PackageNotFoundError
55+
56+
mock_distribution.side_effect = _distribution
57+
package_to_instrument = instrumentation.instrumentation_dependencies()
58+
59+
self.assertEqual(mock_distribution.call_count, 2)
60+
self.assertEqual(
61+
mock_distribution.mock_calls,
62+
[
63+
call("kafka-python-ng"),
64+
call("kafka-python"),
65+
],
66+
)
67+
self.assertEqual(package_to_instrument, (_instruments_kafka_python,))
68+
69+
@patch("opentelemetry.instrumentation.kafka.distribution")
70+
def test_instrumentation_dependencies_kafka_python_ng_installed(
71+
self, mock_distribution
72+
) -> None:
73+
instrumentation = KafkaInstrumentor()
74+
75+
def _distribution(name):
76+
if name == "kafka-python-ng":
77+
return None
78+
raise PackageNotFoundError
79+
80+
mock_distribution.side_effect = _distribution
81+
package_to_instrument = instrumentation.instrumentation_dependencies()
82+
83+
self.assertEqual(mock_distribution.call_count, 1)
84+
self.assertEqual(
85+
mock_distribution.mock_calls, [call("kafka-python-ng")]
86+
)
87+
self.assertEqual(
88+
package_to_instrument, (_instruments_kafka_python_ng,)
89+
)
90+
91+
@patch("opentelemetry.instrumentation.kafka.distribution")
92+
def test_instrumentation_dependencies_both_installed(
93+
self, mock_distribution
94+
) -> None:
95+
instrumentation = KafkaInstrumentor()
96+
97+
def _distribution(name):
98+
# Function raises PackageNotFoundError
99+
# if name is not in the list. We will
100+
# not raise it for both names
101+
return None
102+
103+
mock_distribution.side_effect = _distribution
104+
package_to_instrument = instrumentation.instrumentation_dependencies()
105+
106+
self.assertEqual(mock_distribution.call_count, 1)
107+
self.assertEqual(
108+
mock_distribution.mock_calls, [call("kafka-python-ng")]
109+
)
110+
self.assertEqual(
111+
package_to_instrument, (_instruments_kafka_python_ng,)
112+
)
113+
114+
@patch("opentelemetry.instrumentation.kafka.distribution")
115+
def test_instrumentation_dependencies_none_installed(
116+
self, mock_distribution
117+
) -> None:
118+
instrumentation = KafkaInstrumentor()
119+
120+
def _distribution(name):
121+
# Function raises PackageNotFoundError
122+
# if name is not in the list. We will
123+
# raise it for both names to simulate
124+
# neither being installed
125+
raise PackageNotFoundError
126+
127+
mock_distribution.side_effect = _distribution
128+
package_to_instrument = instrumentation.instrumentation_dependencies()
129+
130+
self.assertEqual(mock_distribution.call_count, 2)
131+
self.assertEqual(
132+
mock_distribution.mock_calls,
133+
[
134+
call("kafka-python-ng"),
135+
call("kafka-python"),
136+
],
137+
)
138+
self.assertEqual(package_to_instrument, _instruments)

0 commit comments

Comments
 (0)