Skip to content

Commit c86dae6

Browse files
committed
Load instrumentors via Distro
This commit makes the following changes: - Introduces a new `load_instrumentor(EntryPoint) -> None:` with a default implementation method to the `BaseDistro` class. - The default implementation loads the insrumentor from the provided entry point and calls applies it without any arguments. (same as before) - sitecustomize now calls Distro's `load_instrumentor` method to load and activate an instrumentor instead of doing it directly. - Added a new `DefaultDistro` implementation which is used if not distro is found by entry points.
1 parent cb35cc4 commit c86dae6

File tree

4 files changed

+108
-13
lines changed

4 files changed

+108
-13
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
([#472](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/472))
1313
- Set the `traced_request_attrs` of FalconInstrumentor by an argument correctly.
1414
([#473](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/473))
15+
- Distros can now implement `load_instrumentor(EntryPoint)` method to customize instrumentor
16+
loading behaviour.
17+
([#480](https://github.com/open-telemetry/opentelemetry-python/pull/480))
1518

1619
### Added
1720
- Move `opentelemetry-instrumentation` from core repository

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

+25-12
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,49 @@
2323
from opentelemetry.environment_variables import (
2424
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
2525
)
26+
from opentelemetry.instrumentation.distro import BaseDistro, DefaultDistro
2627

2728
logger = getLogger(__file__)
2829

2930

30-
def _load_distros():
31+
def _load_distros() -> BaseDistro:
3132
for entry_point in iter_entry_points("opentelemetry_distro"):
3233
try:
33-
entry_point.load()().configure() # type: ignore
34-
logger.debug("Distribution %s configured", entry_point.name)
34+
distro = entry_point.load()()
35+
if not isinstance(distro, BaseDistro):
36+
logger.debug(
37+
"%s is not an OpenTelemetry Distro. Skipping",
38+
entry_point.name,
39+
)
40+
continue
41+
logger.debug(
42+
"Distribution %s will be configured", entry_point.name
43+
)
44+
return distro
3545
except Exception as exc: # pylint: disable=broad-except
3646
logger.exception(
3747
"Distribution %s configuration failed", entry_point.name
3848
)
3949
raise exc
50+
return DefaultDistro()
4051

4152

42-
def _load_instrumentors():
53+
def _load_instrumentors(distro):
4354
package_to_exclude = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, [])
4455
if isinstance(package_to_exclude, str):
4556
package_to_exclude = package_to_exclude.split(",")
4657
# to handle users entering "requests , flask" or "requests, flask" with spaces
4758
package_to_exclude = [x.strip() for x in package_to_exclude]
4859

4960
for entry_point in iter_entry_points("opentelemetry_instrumentor"):
61+
if entry_point.name in package_to_exclude:
62+
logger.debug(
63+
"Instrumentation skipped for library %s", entry_point.name
64+
)
65+
continue
66+
5067
try:
51-
if entry_point.name in package_to_exclude:
52-
logger.debug(
53-
"Instrumentation skipped for library %s", entry_point.name
54-
)
55-
continue
56-
entry_point.load()().instrument() # type: ignore
68+
distro.load_instrumentor(entry_point)
5769
logger.debug("Instrumented %s", entry_point.name)
5870
except Exception as exc: # pylint: disable=broad-except
5971
logger.exception("Instrumenting of %s failed", entry_point.name)
@@ -80,9 +92,10 @@ def _load_configurators():
8092

8193
def initialize():
8294
try:
83-
_load_distros()
95+
distro = _load_distros()
96+
distro.configure()
8497
_load_configurators()
85-
_load_instrumentors()
98+
_load_instrumentors(distro)
8699
except Exception: # pylint: disable=broad-except
87100
logger.exception("Failed to auto initialize opentelemetry")
88101
finally:

opentelemetry-instrumentation/src/opentelemetry/instrumentation/distro.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
from abc import ABC, abstractmethod
2121
from logging import getLogger
2222

23+
from pkg_resources import EntryPoint
24+
25+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
26+
2327
_LOG = getLogger(__name__)
2428

2529

@@ -43,5 +47,25 @@ def configure(self, **kwargs):
4347
"""Configure the distribution"""
4448
self._configure(**kwargs)
4549

50+
def load_instrumentor( # pylint: disable=no-self-use
51+
self, entry_point: EntryPoint
52+
):
53+
"""Takes a collection of instrumentation entry points
54+
and activates them by instantiating and calling instrument()
55+
on each one.
56+
57+
Distros can override this method to customize the behavior by
58+
inspecting each entry point and configuring them in special ways,
59+
passing additional arguments, load a replacement/fork instead,
60+
skip loading entirely, etc.
61+
"""
62+
instrumentor: BaseInstrumentor = entry_point.load()
63+
instrumentor().instrument()
64+
65+
66+
class DefaultDistro(BaseDistro):
67+
def _configure(self, **kwargs):
68+
pass
69+
4670

47-
__all__ = ["BaseDistro"]
71+
__all__ = ["BaseDistro", "DefaultDistro"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
18+
from pkg_resources import EntryPoint
19+
20+
from opentelemetry.instrumentation.distro import BaseDistro
21+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
22+
23+
24+
class MockInstrumetor(BaseInstrumentor):
25+
def _instrument(self, **kwargs):
26+
pass
27+
28+
def _uninstrument(self, **kwargs):
29+
pass
30+
31+
32+
class MockEntryPoint(EntryPoint):
33+
def __init__(self, obj): # pylint: disable=super-init-not-called
34+
self._obj = obj
35+
36+
def load(self, *args, **kwargs): # pylint: disable=signature-differs
37+
return self._obj
38+
39+
40+
class MockDistro(BaseDistro):
41+
def _configure(self, **kwargs):
42+
pass
43+
44+
45+
class TestDistro(TestCase):
46+
def test_load_instrumentor(self):
47+
# pylint: disable=protected-access
48+
distro = MockDistro()
49+
50+
instrumentor = MockInstrumetor()
51+
entry_point = MockEntryPoint(MockInstrumetor)
52+
53+
self.assertFalse(instrumentor._is_instrumented)
54+
distro.load_instrumentor(entry_point)
55+
self.assertTrue(instrumentor._is_instrumented)

0 commit comments

Comments
 (0)