Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2a97615

Browse files
committedJun 1, 2023
Refactored sitecustomize and added tests
1 parent edafb0a commit 2a97615

File tree

4 files changed

+426
-110
lines changed

4 files changed

+426
-110
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from logging import getLogger
16+
from os import environ
17+
18+
from pkg_resources import iter_entry_points
19+
20+
from opentelemetry.instrumentation.dependencies import (
21+
get_dist_dependency_conflicts,
22+
)
23+
from opentelemetry.instrumentation.distro import BaseDistro, DefaultDistro
24+
from opentelemetry.instrumentation.environment_variables import (
25+
OTEL_PYTHON_CONFIGURATOR,
26+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
27+
OTEL_PYTHON_DISTRO,
28+
)
29+
from opentelemetry.instrumentation.version import __version__
30+
31+
logger = getLogger(__name__)
32+
33+
34+
def _load_distros() -> BaseDistro:
35+
distro_name = environ.get(OTEL_PYTHON_DISTRO, None)
36+
for entry_point in iter_entry_points("opentelemetry_distro"):
37+
try:
38+
if distro_name is None or distro_name == entry_point.name:
39+
distro = entry_point.load()()
40+
if not isinstance(distro, BaseDistro):
41+
logger.debug(
42+
"%s is not an OpenTelemetry Distro. Skipping",
43+
entry_point.name,
44+
)
45+
continue
46+
logger.debug(
47+
"Distribution %s will be configured", entry_point.name
48+
)
49+
return distro
50+
except Exception as exc: # pylint: disable=broad-except
51+
logger.exception(
52+
"Distribution %s configuration failed", entry_point.name
53+
)
54+
raise exc
55+
return DefaultDistro()
56+
57+
58+
def _load_instrumentors(distro):
59+
package_to_exclude = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, [])
60+
if isinstance(package_to_exclude, str):
61+
package_to_exclude = package_to_exclude.split(",")
62+
# to handle users entering "requests , flask" or "requests, flask" with spaces
63+
package_to_exclude = [x.strip() for x in package_to_exclude]
64+
65+
for entry_point in iter_entry_points("opentelemetry_pre_instrument"):
66+
entry_point.load()()
67+
68+
for entry_point in iter_entry_points("opentelemetry_instrumentor"):
69+
if entry_point.name in package_to_exclude:
70+
logger.debug(
71+
"Instrumentation skipped for library %s", entry_point.name
72+
)
73+
continue
74+
75+
try:
76+
conflict = get_dist_dependency_conflicts(entry_point.dist)
77+
if conflict:
78+
logger.debug(
79+
"Skipping instrumentation %s: %s",
80+
entry_point.name,
81+
conflict,
82+
)
83+
continue
84+
85+
# tell instrumentation to not run dep checks again as we already did it above
86+
distro.load_instrumentor(entry_point, skip_dep_check=True)
87+
logger.debug("Instrumented %s", entry_point.name)
88+
except Exception as exc: # pylint: disable=broad-except
89+
logger.exception("Instrumenting of %s failed", entry_point.name)
90+
raise exc
91+
92+
for entry_point in iter_entry_points("opentelemetry_post_instrument"):
93+
entry_point.load()()
94+
95+
96+
def _load_configurators():
97+
configurator_name = environ.get(OTEL_PYTHON_CONFIGURATOR, None)
98+
configured = None
99+
for entry_point in iter_entry_points("opentelemetry_configurator"):
100+
if configured is not None:
101+
logger.warning(
102+
"Configuration of %s not loaded, %s already loaded",
103+
entry_point.name,
104+
configured,
105+
)
106+
continue
107+
try:
108+
if (
109+
configurator_name is None
110+
or configurator_name == entry_point.name
111+
):
112+
entry_point.load()().configure(auto_instrumentation_version=__version__) # type: ignore
113+
configured = entry_point.name
114+
else:
115+
logger.warning(
116+
"Configuration of %s not loaded because %s is set by %s",
117+
entry_point.name,
118+
configurator_name,
119+
OTEL_PYTHON_CONFIGURATOR,
120+
)
121+
except Exception as exc: # pylint: disable=broad-except
122+
logger.exception("Configuration of %s failed", entry_point.name)
123+
raise exc

‎opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py

+4-110
Original file line numberDiff line numberDiff line change
@@ -16,122 +16,16 @@
1616
from os import environ
1717
from os.path import abspath, dirname, pathsep
1818

19-
from pkg_resources import iter_entry_points
20-
21-
from opentelemetry.instrumentation.dependencies import (
22-
get_dist_dependency_conflicts,
23-
)
24-
from opentelemetry.instrumentation.distro import BaseDistro, DefaultDistro
25-
from opentelemetry.instrumentation.environment_variables import (
26-
OTEL_PYTHON_CONFIGURATOR,
27-
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
28-
OTEL_PYTHON_DISTRO,
19+
from opentelemetry.instrumentation.auto_instrumentation._load import (
20+
_load_configurators,
21+
_load_distros,
22+
_load_instrumentors,
2923
)
3024
from opentelemetry.instrumentation.utils import _python_path_without_directory
31-
from opentelemetry.instrumentation.version import __version__
3225

3326
logger = getLogger(__name__)
3427

3528

36-
def _load_distros() -> BaseDistro:
37-
distro_name = environ.get(OTEL_PYTHON_DISTRO, None)
38-
for entry_point in iter_entry_points("opentelemetry_distro"):
39-
try:
40-
if distro_name is None or distro_name == entry_point.name:
41-
distro = entry_point.load()()
42-
if not isinstance(distro, BaseDistro):
43-
logger.debug(
44-
"%s is not an OpenTelemetry Distro. Skipping",
45-
entry_point.name,
46-
)
47-
continue
48-
logger.debug(
49-
"Distribution %s will be configured", entry_point.name
50-
)
51-
return distro
52-
else:
53-
logger.warning(
54-
"%s distro not loaded because %s is set by %s",
55-
entry_point.name,
56-
distro_name,
57-
OTEL_PYTHON_DISTRO,
58-
)
59-
except Exception as exc: # pylint: disable=broad-except
60-
logger.exception(
61-
"Distribution %s configuration failed", entry_point.name
62-
)
63-
raise exc
64-
return DefaultDistro()
65-
66-
67-
def _load_instrumentors(distro):
68-
package_to_exclude = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, [])
69-
if isinstance(package_to_exclude, str):
70-
package_to_exclude = package_to_exclude.split(",")
71-
# to handle users entering "requests , flask" or "requests, flask" with spaces
72-
package_to_exclude = [x.strip() for x in package_to_exclude]
73-
74-
for entry_point in iter_entry_points("opentelemetry_pre_instrument"):
75-
entry_point.load()()
76-
77-
for entry_point in iter_entry_points("opentelemetry_instrumentor"):
78-
if entry_point.name in package_to_exclude:
79-
logger.debug(
80-
"Instrumentation skipped for library %s", entry_point.name
81-
)
82-
continue
83-
84-
try:
85-
conflict = get_dist_dependency_conflicts(entry_point.dist)
86-
if conflict:
87-
logger.debug(
88-
"Skipping instrumentation %s: %s",
89-
entry_point.name,
90-
conflict,
91-
)
92-
continue
93-
94-
# tell instrumentation to not run dep checks again as we already did it above
95-
distro.load_instrumentor(entry_point, skip_dep_check=True)
96-
logger.debug("Instrumented %s", entry_point.name)
97-
except Exception as exc: # pylint: disable=broad-except
98-
logger.exception("Instrumenting of %s failed", entry_point.name)
99-
raise exc
100-
101-
for entry_point in iter_entry_points("opentelemetry_post_instrument"):
102-
entry_point.load()()
103-
104-
105-
def _load_configurators():
106-
configurator_name = environ.get(OTEL_PYTHON_CONFIGURATOR, None)
107-
configured = None
108-
for entry_point in iter_entry_points("opentelemetry_configurator"):
109-
if configured is not None:
110-
logger.warning(
111-
"Configuration of %s not loaded, %s already loaded",
112-
entry_point.name,
113-
configured,
114-
)
115-
continue
116-
try:
117-
if (
118-
configurator_name is None
119-
or configurator_name == entry_point.name
120-
):
121-
entry_point.load()().configure(auto_instrumentation_version=__version__) # type: ignore
122-
configured = entry_point.name
123-
else:
124-
logger.warning(
125-
"Configuration of %s not loaded because %s is set by %s",
126-
entry_point.name,
127-
configurator_name,
128-
OTEL_PYTHON_CONFIGURATOR,
129-
)
130-
except Exception as exc: # pylint: disable=broad-except
131-
logger.exception("Configuration of %s failed", entry_point.name)
132-
raise exc
133-
134-
13529
def initialize():
13630
# prevents auto-instrumentation of subprocesses if code execs another python process
13731
environ["PYTHONPATH"] = _python_path_without_directory(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# type: ignore
15+
16+
from unittest import TestCase
17+
from unittest.mock import Mock, call, patch
18+
19+
from opentelemetry.instrumentation.auto_instrumentation import _load
20+
from opentelemetry.instrumentation.environment_variables import (
21+
OTEL_PYTHON_CONFIGURATOR,
22+
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
23+
OTEL_PYTHON_DISTRO,
24+
)
25+
from opentelemetry.instrumentation.version import __version__
26+
27+
28+
class TestLoad(TestCase):
29+
@patch.dict(
30+
"os.environ", {OTEL_PYTHON_CONFIGURATOR: "custom_configurator2"}
31+
)
32+
@patch(
33+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
34+
)
35+
def test_load_configurators(self, iter_mock):
36+
ep_mock1 = Mock()
37+
ep_mock1.name = "custom_configurator1"
38+
configurator_mock1 = Mock()
39+
ep_mock1.load.return_value = configurator_mock1
40+
ep_mock2 = Mock()
41+
ep_mock2.name = "custom_configurator2"
42+
configurator_mock2 = Mock()
43+
ep_mock2.load.return_value = configurator_mock2
44+
ep_mock3 = Mock()
45+
ep_mock3.name = "custom_configurator3"
46+
configurator_mock3 = Mock()
47+
ep_mock3.load.return_value = configurator_mock3
48+
49+
iter_mock.return_value = (ep_mock1, ep_mock2, ep_mock3)
50+
_load._load_configurators()
51+
configurator_mock1.assert_not_called()
52+
configurator_mock2().configure.assert_called_once_with(
53+
auto_instrumentation_version=__version__
54+
)
55+
configurator_mock3.assert_not_called()
56+
57+
@patch.dict(
58+
"os.environ", {OTEL_PYTHON_CONFIGURATOR: "custom_configurator2"}
59+
)
60+
@patch(
61+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
62+
)
63+
def test_load_configurators_no_ep(
64+
self,
65+
iter_mock,
66+
):
67+
iter_mock.return_value = ()
68+
_load._load_configurators()
69+
70+
@patch.dict(
71+
"os.environ", {OTEL_PYTHON_CONFIGURATOR: "custom_configurator2"}
72+
)
73+
@patch(
74+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
75+
)
76+
def test_load_configurators_error(self, iter_mock):
77+
ep_mock1 = Mock()
78+
ep_mock1.name = "custom_configurator1"
79+
configurator_mock1 = Mock()
80+
ep_mock1.load.return_value = configurator_mock1
81+
ep_mock2 = Mock()
82+
ep_mock2.name = "custom_configurator2"
83+
configurator_mock2 = Mock()
84+
configurator_mock2().configure.side_effect = Exception()
85+
ep_mock2.load.return_value = configurator_mock2
86+
ep_mock3 = Mock()
87+
ep_mock3.name = "custom_configurator3"
88+
configurator_mock3 = Mock()
89+
ep_mock3.load.return_value = configurator_mock3
90+
91+
iter_mock.return_value = (ep_mock1, ep_mock2, ep_mock3)
92+
self.assertRaises(Exception, _load._load_configurators)
93+
94+
@patch.dict("os.environ", {OTEL_PYTHON_DISTRO: "custom_distro2"})
95+
@patch(
96+
"opentelemetry.instrumentation.auto_instrumentation._load.isinstance"
97+
)
98+
@patch(
99+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
100+
)
101+
def test_load_distros(self, iter_mock, isinstance_mock):
102+
ep_mock1 = Mock()
103+
ep_mock1.name = "custom_distro1"
104+
distro_mock1 = Mock()
105+
ep_mock1.load.return_value = distro_mock1
106+
ep_mock2 = Mock()
107+
ep_mock2.name = "custom_distro2"
108+
distro_mock2 = Mock()
109+
ep_mock2.load.return_value = distro_mock2
110+
ep_mock3 = Mock()
111+
ep_mock3.name = "custom_distro3"
112+
distro_mock3 = Mock()
113+
ep_mock3.load.return_value = distro_mock3
114+
115+
iter_mock.return_value = (ep_mock1, ep_mock2, ep_mock3)
116+
isinstance_mock.return_value = True
117+
self.assertEqual(
118+
_load._load_distros(),
119+
distro_mock2(),
120+
)
121+
122+
@patch.dict("os.environ", {OTEL_PYTHON_DISTRO: "custom_distro2"})
123+
@patch(
124+
"opentelemetry.instrumentation.auto_instrumentation._load.isinstance"
125+
)
126+
@patch(
127+
"opentelemetry.instrumentation.auto_instrumentation._load.DefaultDistro"
128+
)
129+
@patch(
130+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
131+
)
132+
def test_load_distros_not_distro(
133+
self, iter_mock, default_distro_mock, isinstance_mock
134+
):
135+
ep_mock1 = Mock()
136+
ep_mock1.name = "custom_distro1"
137+
distro_mock1 = Mock()
138+
ep_mock1.load.return_value = distro_mock1
139+
ep_mock2 = Mock()
140+
ep_mock2.name = "custom_distro2"
141+
distro_mock2 = Mock()
142+
ep_mock2.load.return_value = distro_mock2
143+
ep_mock3 = Mock()
144+
ep_mock3.name = "custom_distro3"
145+
distro_mock3 = Mock()
146+
ep_mock3.load.return_value = distro_mock3
147+
148+
iter_mock.return_value = (ep_mock1, ep_mock2, ep_mock3)
149+
isinstance_mock.return_value = False
150+
self.assertEqual(
151+
_load._load_distros(),
152+
default_distro_mock(),
153+
)
154+
155+
@patch.dict("os.environ", {OTEL_PYTHON_DISTRO: "custom_distro2"})
156+
@patch(
157+
"opentelemetry.instrumentation.auto_instrumentation._load.DefaultDistro"
158+
)
159+
@patch(
160+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
161+
)
162+
def test_load_distros_no_ep(self, iter_mock, default_distro_mock):
163+
iter_mock.return_value = ()
164+
self.assertEqual(
165+
_load._load_distros(),
166+
default_distro_mock(),
167+
)
168+
169+
@patch.dict("os.environ", {OTEL_PYTHON_DISTRO: "custom_distro2"})
170+
@patch(
171+
"opentelemetry.instrumentation.auto_instrumentation._load.isinstance"
172+
)
173+
@patch(
174+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
175+
)
176+
def test_load_distros_error(self, iter_mock, isinstance_mock):
177+
ep_mock1 = Mock()
178+
ep_mock1.name = "custom_distro1"
179+
distro_mock1 = Mock()
180+
ep_mock1.load.return_value = distro_mock1
181+
ep_mock2 = Mock()
182+
ep_mock2.name = "custom_distro2"
183+
distro_mock2 = Mock()
184+
distro_mock2.side_effect = Exception()
185+
ep_mock2.load.return_value = distro_mock2
186+
ep_mock3 = Mock()
187+
ep_mock3.name = "custom_distro3"
188+
distro_mock3 = Mock()
189+
ep_mock3.load.return_value = distro_mock3
190+
191+
iter_mock.return_value = (ep_mock1, ep_mock2, ep_mock3)
192+
isinstance_mock.return_value = True
193+
self.assertRaises(Exception, _load._load_distros)
194+
195+
@patch.dict(
196+
"os.environ",
197+
{OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: " instr1 , instr3 "},
198+
)
199+
@patch(
200+
"opentelemetry.instrumentation.auto_instrumentation._load.get_dist_dependency_conflicts"
201+
)
202+
@patch(
203+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
204+
)
205+
def test_load_instrumentors(self, iter_mock, dep_mock):
206+
pre_ep_mock1 = Mock()
207+
pre_ep_mock1.name = "pre1"
208+
pre_mock1 = Mock()
209+
pre_ep_mock1.load.return_value = pre_mock1
210+
211+
pre_ep_mock2 = Mock()
212+
pre_ep_mock2.name = "pre2"
213+
pre_mock2 = Mock()
214+
pre_ep_mock2.load.return_value = pre_mock2
215+
216+
ep_mock3 = Mock()
217+
ep_mock3.name = "instr3"
218+
219+
ep_mock4 = Mock()
220+
ep_mock4.name = "instr4"
221+
222+
ep_mock1 = Mock()
223+
ep_mock1.name = "instr1"
224+
225+
ep_mock2 = Mock()
226+
ep_mock2.name = "instr2"
227+
228+
ep_mock3 = Mock()
229+
ep_mock3.name = "instr3"
230+
231+
ep_mock4 = Mock()
232+
ep_mock4.name = "instr4"
233+
234+
post_ep_mock1 = Mock()
235+
post_ep_mock1.name = "post1"
236+
post_mock1 = Mock()
237+
post_ep_mock1.load.return_value = post_mock1
238+
239+
post_ep_mock2 = Mock()
240+
post_ep_mock2.name = "post2"
241+
post_mock2 = Mock()
242+
post_ep_mock2.load.return_value = post_mock2
243+
244+
distro_mock = Mock()
245+
246+
iter_mock.side_effect = [
247+
(pre_ep_mock1, pre_ep_mock2),
248+
(ep_mock1, ep_mock2, ep_mock3, ep_mock4),
249+
(post_ep_mock1, post_ep_mock2),
250+
]
251+
dep_mock.return_value = None
252+
_load._load_instrumentors(distro_mock)
253+
pre_mock1.assert_called_once()
254+
pre_mock2.assert_called_once()
255+
self.assertEqual(iter_mock.call_count, 3)
256+
distro_mock.load_instrumentor.assert_has_calls(
257+
[
258+
call(ep_mock2, skip_dep_check=True),
259+
call(ep_mock4, skip_dep_check=True),
260+
]
261+
)
262+
self.assertEqual(distro_mock.load_instrumentor.call_count, 2)
263+
post_mock1.assert_called_once()
264+
post_mock2.assert_called_once()
265+
266+
@patch.dict(
267+
"os.environ",
268+
{OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: " instr1 , instr3 "},
269+
)
270+
@patch(
271+
"opentelemetry.instrumentation.auto_instrumentation._load.get_dist_dependency_conflicts"
272+
)
273+
@patch(
274+
"opentelemetry.instrumentation.auto_instrumentation._load.iter_entry_points"
275+
)
276+
def test_load_instrumentors_dep_conflict(self, iter_mock, dep_mock):
277+
ep_mock1 = Mock()
278+
ep_mock1.name = "instr1"
279+
280+
ep_mock2 = Mock()
281+
ep_mock2.name = "instr2"
282+
283+
ep_mock3 = Mock()
284+
ep_mock3.name = "instr3"
285+
286+
ep_mock4 = Mock()
287+
ep_mock4.name = "instr4"
288+
289+
distro_mock = Mock()
290+
291+
iter_mock.return_value = (ep_mock1, ep_mock2, ep_mock3, ep_mock4)
292+
dep_mock.side_effect = [None, "DependencyConflict"]
293+
_load._load_instrumentors(distro_mock)
294+
distro_mock.load_instrumentor.assert_has_calls(
295+
[
296+
call(ep_mock2, skip_dep_check=True),
297+
]
298+
)
299+
distro_mock.load_instrumentor.assert_called_once()

0 commit comments

Comments
 (0)
Please sign in to comment.