Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -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

Expand Down
13 changes: 13 additions & 0 deletions opentelemetry-instrumentation/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ 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 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
the code above. Please note that some instrumentations may require the ``initialize()`` method to be called before the library they
instrument is imported.

References
----------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -110,3 +116,20 @@ 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
if "PYTHONPATH" in environ:
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")
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 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.assertNotIn("PYTHONPATH", environ)
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):
# pylint:disable=no-self-use
load_distro_mock.side_effect = ValueError
auto_instrumentation.initialize()
logger_mock.exception.assert_called_once_with(
"Failed to auto initialize OpenTelemetry"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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):
# 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()

import opentelemetry.instrumentation.auto_instrumentation.sitecustomize # NOQA

initialize_mock.assert_called_once()