From 87befcbef6e1add38d2692d3b38c1e435f674592 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 17 Feb 2025 10:33:18 +0100 Subject: [PATCH 01/10] opentelemetry-instrumentation: expose a way to init autoinstrumentation --- opentelemetry-instrumentation/README.rst | 14 +++++ .../auto_instrumentation/__init__.py | 22 +++++++ .../auto_instrumentation/sitecustomize.py | 29 +-------- .../auto_instrumentation/test_initialize.py | 62 +++++++++++++++++++ .../test_sitecustomize.py | 27 ++++++++ 5 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py create mode 100644 opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index 3ed88c213f..2b62594738 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -130,6 +130,20 @@ start celery with the rest of the arguments. The above command will configure the global trace provider to use the Random IDs Generator, and then pass ``--port=3000`` to ``flask run``. +auto-instrumentation +-------------------- + +:: + + from opentelemetry.instrumentation import autoinstrumentation + autoinstrumenttion.initialize() + + +If you are in an environment where you cannot use opentelemetry-instrument to inject auto-instrumentation you can do so manually with +the code above. + + + References ---------- diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py index 963b3a6956..28a948d71d 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -19,6 +19,12 @@ from re import sub from shutil import which +from opentelemetry.instrumentation.auto_instrumentation._load import ( + _load_configurators, + _load_distro, + _load_instrumentors, +) +from opentelemetry.instrumentation.utils import _python_path_without_directory from opentelemetry.instrumentation.version import __version__ from opentelemetry.util._importlib_metadata import entry_points @@ -110,3 +116,19 @@ def run() -> None: executable = which(args.command) execl(executable, executable, *args.command_args) + + +def initialize(): + """Setup auto-instrumentation, called by the sitecustomize module""" + # prevents auto-instrumentation of subprocesses if code execs another python process + environ["PYTHONPATH"] = _python_path_without_directory( + environ.get("PYTHONPATH", ""), dirname(abspath(__file__)), pathsep + ) + + try: + distro = _load_distro() + distro.configure() + _load_configurators() + _load_instrumentors(distro) + except Exception: # pylint: disable=broad-except + _logger.exception("Failed to auto initialize OpenTelemetry") diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py index 912675f1b7..c126b87372 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py @@ -12,33 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from logging import getLogger -from os import environ -from os.path import abspath, dirname, pathsep - -from opentelemetry.instrumentation.auto_instrumentation._load import ( - _load_configurators, - _load_distro, - _load_instrumentors, -) -from opentelemetry.instrumentation.utils import _python_path_without_directory - -logger = getLogger(__name__) - - -def initialize(): - # prevents auto-instrumentation of subprocesses if code execs another python process - environ["PYTHONPATH"] = _python_path_without_directory( - environ["PYTHONPATH"], dirname(abspath(__file__)), pathsep - ) - - try: - distro = _load_distro() - distro.configure() - _load_configurators() - _load_instrumentors(distro) - except Exception: # pylint: disable=broad-except - logger.exception("Failed to auto initialize opentelemetry") - +from opentelemetry.instrumentation.auto_instrumentation import initialize initialize() diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py new file mode 100644 index 0000000000..f1ebe45a4a --- /dev/null +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py @@ -0,0 +1,62 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# type: ignore + +from os import environ +from os.path import abspath, dirname, pathsep +from unittest import TestCase +from unittest.mock import patch + +from opentelemetry.instrumentation import auto_instrumentation + +# TODO: convert to assertNoLogs instead of mocking logger when 3.10 is baseline + + +class TestInitialize(TestCase): + auto_instrumentation_path = dirname(abspath(auto_instrumentation.__file__)) + + @patch.dict("os.environ", {}, clear=True) + @patch("opentelemetry.instrumentation.auto_instrumentation._logger") + def test_handles_pythonpath_not_set(self, logger_mock): + auto_instrumentation.initialize() + self.assertEqual(environ["PYTHONPATH"], "") + logger_mock.exception.assert_not_called() + + @patch.dict("os.environ", {"PYTHONPATH": "."}) + @patch("opentelemetry.instrumentation.auto_instrumentation._logger") + def test_handles_pythonpath_set(self, logger_mock): + auto_instrumentation.initialize() + self.assertEqual(environ["PYTHONPATH"], ".") + + logger_mock.exception.assert_not_called() + + @patch.dict( + "os.environ", + {"PYTHONPATH": auto_instrumentation_path + pathsep + "foo"}, + ) + @patch("opentelemetry.instrumentation.auto_instrumentation._logger") + def test_clears_auto_instrumentation_path(self, logger_mock): + auto_instrumentation.initialize() + self.assertEqual(environ["PYTHONPATH"], "foo") + + logger_mock.exception.assert_not_called() + + @patch("opentelemetry.instrumentation.auto_instrumentation._logger") + @patch("opentelemetry.instrumentation.auto_instrumentation._load_distro") + def test_handles_exceptions(self, load_distro_mock, logger_mock): + load_distro_mock.side_effect = ValueError + auto_instrumentation.initialize() + logger_mock.exception.assert_called_once_with( + "Failed to auto initialize OpenTelemetry" + ) diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py new file mode 100644 index 0000000000..dc1a41ecc2 --- /dev/null +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py @@ -0,0 +1,27 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# type: ignore + +from unittest import TestCase +from unittest.mock import patch + + +class TestSiteCustomize(TestCase): + @patch("opentelemetry.instrumentation.auto_instrumentation.initialize") + def test_sitecustomize_side_effects(self, initialize_mock): + initialize_mock.assert_not_called() + + import opentelemetry.instrumentation.auto_instrumentation.sitecustomize # NOQA + + initialize_mock.assert_called_once() From 70e3f14654d255297e3e425335b39d8704041a36 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 17 Feb 2025 12:16:38 +0100 Subject: [PATCH 02/10] Please pylint --- .../tests/auto_instrumentation/test_initialize.py | 3 +-- .../tests/auto_instrumentation/test_sitecustomize.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py index f1ebe45a4a..978a830f73 100644 --- a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py @@ -38,7 +38,6 @@ def test_handles_pythonpath_not_set(self, logger_mock): def test_handles_pythonpath_set(self, logger_mock): auto_instrumentation.initialize() self.assertEqual(environ["PYTHONPATH"], ".") - logger_mock.exception.assert_not_called() @patch.dict( @@ -49,12 +48,12 @@ def test_handles_pythonpath_set(self, logger_mock): def test_clears_auto_instrumentation_path(self, logger_mock): auto_instrumentation.initialize() self.assertEqual(environ["PYTHONPATH"], "foo") - logger_mock.exception.assert_not_called() @patch("opentelemetry.instrumentation.auto_instrumentation._logger") @patch("opentelemetry.instrumentation.auto_instrumentation._load_distro") def test_handles_exceptions(self, load_distro_mock, logger_mock): + # pylint:disable=no-self-use load_distro_mock.side_effect = ValueError auto_instrumentation.initialize() logger_mock.exception.assert_called_once_with( diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py index dc1a41ecc2..97b5133c38 100644 --- a/opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_sitecustomize.py @@ -18,6 +18,7 @@ class TestSiteCustomize(TestCase): + # pylint:disable=import-outside-toplevel,unused-import,no-self-use @patch("opentelemetry.instrumentation.auto_instrumentation.initialize") def test_sitecustomize_side_effects(self, initialize_mock): initialize_mock.assert_not_called() From 7b330fb871e3fd122048a1818e94d4d95828d08c Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 17 Feb 2025 12:16:44 +0100 Subject: [PATCH 03/10] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 101cafd361..a18196bbaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3258](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3258)) - `opentelemetry-instrumentation-botocore` Add support for GenAI system events ([#3266](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3266)) +- `opentelemetry-instrumentation` make it simpler to initialize auto-instrumentation manually + ([#3273](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3273)) ### Fixed From 9447b83e4b56cd1cdd4a71a3c3fd6ebea8c47173 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 17 Feb 2025 12:23:46 +0100 Subject: [PATCH 04/10] Fix example --- opentelemetry-instrumentation/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index 2b62594738..fd27bef564 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -135,8 +135,8 @@ auto-instrumentation :: - from opentelemetry.instrumentation import autoinstrumentation - autoinstrumenttion.initialize() + from opentelemetry.instrumentation import auto_instrumentation + auto_instrumentation.initialize() If you are in an environment where you cannot use opentelemetry-instrument to inject auto-instrumentation you can do so manually with From ef518abf0ffe88c4af5a98190118ad3c368897c0 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 18 Feb 2025 09:23:30 +0100 Subject: [PATCH 05/10] Fix whitespace in README --- opentelemetry-instrumentation/README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index fd27bef564..5c7f46d1d9 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -142,8 +142,6 @@ auto-instrumentation If you are in an environment where you cannot use opentelemetry-instrument to inject auto-instrumentation you can do so manually with the code above. - - References ---------- From 2dfdff21bd8134226f4500a24008f897d8a2975e Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 18 Feb 2025 09:51:17 +0100 Subject: [PATCH 06/10] Add a note aboout ordering of initialization vs imports --- opentelemetry-instrumentation/README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index 5c7f46d1d9..10d4a9fe7c 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -140,7 +140,8 @@ auto-instrumentation If you are in an environment where you cannot use opentelemetry-instrument to inject auto-instrumentation you can do so manually with -the code above. +the code above. Please note that some instrumentations may require the ``initialize()`` method to be called before the library they +instrument is imported. References ---------- From 39f81ced5ad70bcfa7225ab58426856662e0836f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 18 Feb 2025 09:53:11 +0100 Subject: [PATCH 07/10] Don't touch PYTHONPATH if not set --- .../instrumentation/auto_instrumentation/__init__.py | 7 ++++--- .../tests/auto_instrumentation/test_initialize.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py index 28a948d71d..69af0b4cea 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -121,9 +121,10 @@ def run() -> None: def initialize(): """Setup auto-instrumentation, called by the sitecustomize module""" # prevents auto-instrumentation of subprocesses if code execs another python process - environ["PYTHONPATH"] = _python_path_without_directory( - environ.get("PYTHONPATH", ""), dirname(abspath(__file__)), pathsep - ) + if "PYTHONPATH" in environ: + environ["PYTHONPATH"] = _python_path_without_directory( + environ["PYTHONPATH"], dirname(abspath(__file__)), pathsep + ) try: distro = _load_distro() diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py index 978a830f73..6d05a69c8e 100644 --- a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py @@ -30,7 +30,7 @@ class TestInitialize(TestCase): @patch("opentelemetry.instrumentation.auto_instrumentation._logger") def test_handles_pythonpath_not_set(self, logger_mock): auto_instrumentation.initialize() - self.assertEqual(environ["PYTHONPATH"], "") + self.assertNotIn("PYTHONPATH", environ) logger_mock.exception.assert_not_called() @patch.dict("os.environ", {"PYTHONPATH": "."}) From e421f346f19e5641ab548d65a4248b95354d61eb Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 18 Feb 2025 14:05:13 +0100 Subject: [PATCH 08/10] Update opentelemetry-instrumentation/README.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com> --- opentelemetry-instrumentation/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index 10d4a9fe7c..dd61e43c3c 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -139,7 +139,7 @@ auto-instrumentation auto_instrumentation.initialize() -If you are in an environment where you cannot use opentelemetry-instrument to inject auto-instrumentation you can do so manually with +If you are in an environment where you cannot use opentelemetry-instrument to inject auto-instrumentation you can do so programmatically with the code above. Please note that some instrumentations may require the ``initialize()`` method to be called before the library they instrument is imported. From c6e67dd2570f15da74087c6af63746ccdb007c6a Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 18 Feb 2025 17:57:40 +0100 Subject: [PATCH 09/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a18196bbaa..aa661040d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3258](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3258)) - `opentelemetry-instrumentation-botocore` Add support for GenAI system events ([#3266](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3266)) -- `opentelemetry-instrumentation` make it simpler to initialize auto-instrumentation manually +- `opentelemetry-instrumentation` make it simpler to initialize auto-instrumentation programmatically ([#3273](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3273)) ### Fixed From 760eb49e8a79ec844bd923977b461eaa5f226300 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 24 Feb 2025 10:12:50 +0100 Subject: [PATCH 10/10] Update opentelemetry-instrumentation/README.rst Co-authored-by: Leighton Chen --- opentelemetry-instrumentation/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index dd61e43c3c..ce17dbbe0c 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -130,7 +130,7 @@ start celery with the rest of the arguments. The above command will configure the global trace provider to use the Random IDs Generator, and then pass ``--port=3000`` to ``flask run``. -auto-instrumentation +Programmatic Auto-instrumentation -------------------- ::