diff --git a/docs/examples/error_hander/README.rst b/docs/examples/error_hander/README.rst new file mode 100644 index 00000000000..45b3d0bdacd --- /dev/null +++ b/docs/examples/error_hander/README.rst @@ -0,0 +1,153 @@ +Global Error Handler +==================== + +Overview +-------- + +This example shows how to use the global error handler. + + +Preparation +----------- + +This example will be executed in a separate virtual environment: + +.. code:: sh + + $ mkdir global_error_handler + $ virtualenv global_error_handler + $ source global_error_handler/bin/activate + +Installation +------------ + +Here we install first ``opentelemetry-sdk``, the only dependency. Afterwards, 2 +error handlers are installed: ``error_handler_0`` will handle +``ZeroDivisionError`` exceptions, ``error_handler_1`` will handle +``IndexError`` and ``KeyError`` exceptions. + +.. code:: sh + + $ pip install opentelemetry-sdk + $ git clone https://github.com/open-telemetry/opentelemetry-python.git + $ pip install -e opentelemetry-python/docs/examples/error_handler/error_handler_0 + $ pip install -e opentelemetry-python/docs/examples/error_handler/error_handler_1 + +Execution +--------- + +An example is provided in the +``opentelemetry-python/docs/examples/error_handler/example.py``. + +You can just run it, you should get output similar to this one: + +.. code:: pytb + + ErrorHandler0 handling a ZeroDivisionError + Traceback (most recent call last): + File "test.py", line 5, in + 1 / 0 + ZeroDivisionError: division by zero + + ErrorHandler1 handling an IndexError + Traceback (most recent call last): + File "test.py", line 11, in + [1][2] + IndexError: list index out of range + + ErrorHandler1 handling a KeyError + Traceback (most recent call last): + File "test.py", line 17, in + {1: 2}[2] + KeyError: 2 + + Error handled by default error handler: + Traceback (most recent call last): + File "test.py", line 23, in + assert False + AssertionError + + No error raised + +The ``opentelemetry-sdk.error_handler`` module includes documentation that +explains how this works. We recommend you read it also, here is just a small +summary. + +In ``example.py`` we use ``GlobalErrorHandler`` as a context manager in several +places, for example: + + +.. code:: python + + with GlobalErrorHandler(): + {1: 2}[2] + +Running that code will raise a ``KeyError`` exception. +``GlobalErrorHandler`` will "capture" that exception and pass it down to the +registered error handlers. If there is one that handles ``KeyError`` exceptions +then it will handle it. That can be seen in the result of the execution of +``example.py``: + +.. code:: + + ErrorHandler1 handling a KeyError + Traceback (most recent call last): + File "test.py", line 17, in + {1: 2}[2] + KeyError: 2 + +There is no registered error handler that can handle ``AssertionError`` +exceptions so this kind of errors are handled by the default error handler +which just logs the exception to standard logging, as seen here: + +.. code:: + + Error handled by default error handler: + Traceback (most recent call last): + File "test.py", line 23, in + assert False + AssertionError + +When no exception is raised, the code inside the scope of +``GlobalErrorHandler`` is exectued normally: + +.. code:: + + No error raised + +Users can create Python packages that provide their own custom error handlers +and install them in their virtual environments before running their code which +instantiates ``GlobalErrorHandler`` context managers. ``error_handler_0`` and +``error_handler_1`` can be used as examples to create these custom error +handlers. + +In order for the error handlers to be registered, they need to create a class +that inherits from ``opentelemetry.sdk.error_handler.ErrorHandler`` and at +least one ``Exception``-type class. For example, this is an error handler that +handles ``ZeroDivisionError`` exceptions: + +.. code:: python + + from opentelemetry.sdk.error_handler import ErrorHandler + from logging import getLogger + + logger = getLogger(__name__) + + + class ErrorHandler0(ErrorHandler, ZeroDivisionError): + + def handle(self, error: Exception, *args, **kwargs): + + logger.exception("ErrorHandler0 handling a ZeroDivisionError") + +To register this error handler, use the ``opentelemetry_error_handler`` entry +point in the setup of the error handler package: + +.. code:: + + [options.entry_points] + opentelemetry_error_handler = + error_handler_0 = error_handler_0:ErrorHandler0 + +This entry point should point to the error handler class, ``ErrorHandler0`` in +this case. diff --git a/docs/examples/error_hander/error_handler_0/README.rst b/docs/examples/error_hander/error_handler_0/README.rst new file mode 100644 index 00000000000..0c86902e4ca --- /dev/null +++ b/docs/examples/error_hander/error_handler_0/README.rst @@ -0,0 +1,4 @@ +Error Handler 0 +=============== + +This is just an error handler for this example. diff --git a/docs/examples/error_hander/error_handler_0/setup.cfg b/docs/examples/error_hander/error_handler_0/setup.cfg new file mode 100644 index 00000000000..61760f5dea1 --- /dev/null +++ b/docs/examples/error_hander/error_handler_0/setup.cfg @@ -0,0 +1,46 @@ +# 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. +# +[metadata] +name = error-handler-0 +description = This is just an error handler example package +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.5 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-sdk == 0.14.dev0 + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_error_handler = + error_handler_0 = error_handler_0:ErrorHandler0 diff --git a/docs/examples/error_hander/error_handler_0/setup.py b/docs/examples/error_hander/error_handler_0/setup.py new file mode 100644 index 00000000000..9e174aa7bbe --- /dev/null +++ b/docs/examples/error_hander/error_handler_0/setup.py @@ -0,0 +1,26 @@ +# 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. +import os + +import setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "error_handler_0", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/docs/examples/error_hander/error_handler_0/src/error_handler_0/__init__.py b/docs/examples/error_hander/error_handler_0/src/error_handler_0/__init__.py new file mode 100644 index 00000000000..8b42b7c70eb --- /dev/null +++ b/docs/examples/error_hander/error_handler_0/src/error_handler_0/__init__.py @@ -0,0 +1,25 @@ +# 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. + +from logging import getLogger + +from opentelemetry.sdk.error_handler import ErrorHandler + +logger = getLogger(__name__) + + +class ErrorHandler0(ErrorHandler, ZeroDivisionError): + def _handle(self, error: Exception, *args, **kwargs): + + logger.exception("ErrorHandler0 handling a ZeroDivisionError") diff --git a/docs/examples/error_hander/error_handler_0/src/error_handler_0/version.py b/docs/examples/error_hander/error_handler_0/src/error_handler_0/version.py new file mode 100644 index 00000000000..0f990278982 --- /dev/null +++ b/docs/examples/error_hander/error_handler_0/src/error_handler_0/version.py @@ -0,0 +1,15 @@ +# 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. + +__version__ = "0.14.dev0" diff --git a/docs/examples/error_hander/error_handler_1/README.rst b/docs/examples/error_hander/error_handler_1/README.rst new file mode 100644 index 00000000000..029b95f5c0f --- /dev/null +++ b/docs/examples/error_hander/error_handler_1/README.rst @@ -0,0 +1,4 @@ +Error Handler 1 +=============== + +This is just an error handler for this example. diff --git a/docs/examples/error_hander/error_handler_1/setup.cfg b/docs/examples/error_hander/error_handler_1/setup.cfg new file mode 100644 index 00000000000..0237f3692e1 --- /dev/null +++ b/docs/examples/error_hander/error_handler_1/setup.cfg @@ -0,0 +1,46 @@ +# 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. +# +[metadata] +name = error_handler_1 +description = This is just an error handler example package +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.5 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-sdk == 0.14.dev0 + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_error_handler = + error_handler_1 = error_handler_1:ErrorHandler1 diff --git a/docs/examples/error_hander/error_handler_1/setup.py b/docs/examples/error_hander/error_handler_1/setup.py new file mode 100644 index 00000000000..ccb282dbb23 --- /dev/null +++ b/docs/examples/error_hander/error_handler_1/setup.py @@ -0,0 +1,26 @@ +# 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. +import os + +import setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "error_handler_1", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/docs/examples/error_hander/error_handler_1/src/error_handler_1/__init__.py b/docs/examples/error_hander/error_handler_1/src/error_handler_1/__init__.py new file mode 100644 index 00000000000..cc63465617f --- /dev/null +++ b/docs/examples/error_hander/error_handler_1/src/error_handler_1/__init__.py @@ -0,0 +1,30 @@ +# 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. + +from logging import getLogger + +from opentelemetry.sdk.error_handler import ErrorHandler + +logger = getLogger(__name__) + + +# pylint: disable=too-many-ancestors +class ErrorHandler1(ErrorHandler, IndexError, KeyError): + def _handle(self, error: Exception, *args, **kwargs): + + if isinstance(error, IndexError): + logger.exception("ErrorHandler1 handling an IndexError") + + elif isinstance(error, KeyError): + logger.exception("ErrorHandler1 handling a KeyError") diff --git a/docs/examples/error_hander/error_handler_1/src/error_handler_1/version.py b/docs/examples/error_hander/error_handler_1/src/error_handler_1/version.py new file mode 100644 index 00000000000..0f990278982 --- /dev/null +++ b/docs/examples/error_hander/error_handler_1/src/error_handler_1/version.py @@ -0,0 +1,15 @@ +# 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. + +__version__ = "0.14.dev0" diff --git a/docs/examples/error_hander/example.py b/docs/examples/error_hander/example.py new file mode 100644 index 00000000000..372c39c16fd --- /dev/null +++ b/docs/examples/error_hander/example.py @@ -0,0 +1,29 @@ +from opentelemetry.sdk.error_handler import GlobalErrorHandler + +# ZeroDivisionError to be handled by ErrorHandler0 +with GlobalErrorHandler(): + 1 / 0 + +print() + +# IndexError to be handled by ErrorHandler1 +with GlobalErrorHandler(): + [1][2] + +print() + +# KeyError to be handled by ErrorHandler1 +with GlobalErrorHandler(): + {1: 2}[2] + +print() + +# AssertionError to be handled by DefaultErrorHandler +with GlobalErrorHandler(): + assert False + +print() + +# No error raised +with GlobalErrorHandler(): + print("No error raised") diff --git a/docs/sdk/error_handler.rst b/docs/sdk/error_handler.rst new file mode 100644 index 00000000000..49962bf769c --- /dev/null +++ b/docs/sdk/error_handler.rst @@ -0,0 +1,7 @@ +opentelemetry.sdk.error_handler package +======================================= + +.. automodule:: opentelemetry.sdk.error_handler + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/sdk/sdk.rst b/docs/sdk/sdk.rst index b1dae535e21..e777aebac65 100644 --- a/docs/sdk/sdk.rst +++ b/docs/sdk/sdk.rst @@ -9,3 +9,4 @@ OpenTelemetry Python SDK metrics resources trace + error_handler diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index c9b4c3538db..1688de79b20 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add Global Error Handler + ([#1080](https://github.com/open-telemetry/opentelemetry-python/pull/1080)) - Update sampling result names ([#1128](https://github.com/open-telemetry/opentelemetry-python/pull/1128)) - Add support for `OTEL_BSP_MAX_QUEUE_SIZE`, `OTEL_BSP_SCHEDULE_DELAY_MILLIS`, `OTEL_BSP_MAX_EXPORT_BATCH_SIZE` and `OTEL_BSP_EXPORT_TIMEOUT_MILLIS` environment variables diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/error_handler/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/error_handler/__init__.py new file mode 100644 index 00000000000..6afbd7c2f3b --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/error_handler/__init__.py @@ -0,0 +1,149 @@ +# 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. + +""" +Global Error Handler + +This module provides a global error handler and an interface that allows +error handlers to be registered with the global error handler via entry points. +A default error handler is also provided. + +To use this feature, users can create an error handler that is registered +using the ``opentelemetry_error_handler`` entry point. A class is to be +registered in this entry point, this class must inherit from the +``opentelemetry.sdk.error_handler.ErrorHandler`` class and implement the +corresponding ``handle`` method. This method will receive the exception object +that is to be handled. The error handler class should also inherit from the +exception classes it wants to handle. For example, this would be an error +handler that handles ``ZeroDivisionError``: + +.. code:: python + + from opentelemetry.sdk.error_handler import ErrorHandler + from logging import getLogger + + logger = getLogger(__name__) + + + class ErrorHandler0(ErrorHandler, ZeroDivisionError): + + def _handle(self, error: Exception, *args, **kwargs): + + logger.exception("ErrorHandler0 handling a ZeroDivisionError") + +To use the global error handler, just instantiate it as a context manager where +you want exceptions to be handled: + + +.. code:: python + + from opentelemetry.sdk.error_handler import GlobalErrorHandler + + with GlobalErrorHandler(): + 1 / 0 + +If the class of the exception raised in the scope of the ``GlobalErrorHandler`` +object is not parent of any registered error handler, then the default error +handler will handle the exception. This default error handler will only log the +exception to standard logging, the exception won't be raised any further. +""" + +from abc import ABC, abstractmethod +from logging import getLogger + +from pkg_resources import iter_entry_points + +logger = getLogger(__name__) + + +class ErrorHandler(ABC): + @abstractmethod + def _handle(self, error: Exception, *args, **kwargs): + """ + Handle an exception + """ + + +class DefaultErrorHandler(ErrorHandler): + """ + Default error handler + + This error handler just logs the exception using standard logging. + """ + + # pylint: disable=useless-return + def _handle(self, error: Exception, *args, **kwargs): + + logger.exception("Error handled by default error handler: ") + return None + + +class GlobalErrorHandler: + """ + Global error handler + + This is a singleton class that can be instantiated anywhere to get the + global error handler. This object provides a handle method that receives + an exception object that will be handled by the registered error handlers. + """ + + _instance = None + + def __new__(cls) -> "GlobalErrorHandler": + if cls._instance is None: + cls._instance = super().__new__(cls) + + return cls._instance + + def __enter__(self): + pass + + # pylint: disable=no-self-use + def __exit__(self, exc_type, exc_value, traceback): + + if exc_value is None: + + return None + + plugin_handled = False + + for error_handler_entry_point in iter_entry_points( + "opentelemetry_error_handler" + ): + + error_handler_class = error_handler_entry_point.load() + + if issubclass(error_handler_class, exc_value.__class__): + + try: + + error_handler_class()._handle(exc_value) + plugin_handled = True + + # pylint: disable=broad-except + except Exception as error_handling_error: + + logger.exception( + "%s error while handling error" + " %s by error handler %s", + error_handling_error.__class__.__name__, + exc_value.__class__.__name__, + error_handler_class.__name__, + ) + + if not plugin_handled: + + DefaultErrorHandler()._handle(exc_value) + + return True diff --git a/opentelemetry-sdk/tests/error_handler/__init__.py b/opentelemetry-sdk/tests/error_handler/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-sdk/tests/error_handler/test_error_handler.py b/opentelemetry-sdk/tests/error_handler/test_error_handler.py new file mode 100644 index 00000000000..7ec572d9379 --- /dev/null +++ b/opentelemetry-sdk/tests/error_handler/test_error_handler.py @@ -0,0 +1,133 @@ +# 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. +# pylint: disable=broad-except + +from logging import ERROR +from unittest import TestCase +from unittest.mock import Mock, patch + +from opentelemetry.sdk.error_handler import ( + ErrorHandler, + GlobalErrorHandler, + logger, +) + + +class TestErrorHandler(TestCase): + def test_default_error_handler(self): + + with self.assertLogs(logger, ERROR): + with GlobalErrorHandler(): + raise Exception("some exception") + + # pylint: disable=no-self-use + @patch("opentelemetry.sdk.error_handler.iter_entry_points") + def test_plugin_error_handler(self, mock_iter_entry_points): + class ZeroDivisionErrorHandler(ErrorHandler, ZeroDivisionError): + # pylint: disable=arguments-differ + + _handle = Mock() + + class AssertionErrorHandler(ErrorHandler, AssertionError): + # pylint: disable=arguments-differ + + _handle = Mock() + + mock_entry_point_zero_division_error_handler = Mock() + mock_entry_point_zero_division_error_handler.configure_mock( + **{"load.return_value": ZeroDivisionErrorHandler} + ) + mock_entry_point_assertion_error_handler = Mock() + mock_entry_point_assertion_error_handler.configure_mock( + **{"load.return_value": AssertionErrorHandler} + ) + + mock_iter_entry_points.configure_mock( + **{ + "return_value": [ + mock_entry_point_zero_division_error_handler, + mock_entry_point_assertion_error_handler, + ] + } + ) + + error = ZeroDivisionError() + + with GlobalErrorHandler(): + raise error + + # pylint: disable=protected-access + ZeroDivisionErrorHandler._handle.assert_called_with(error) + + error = AssertionError() + + with GlobalErrorHandler(): + raise error + + AssertionErrorHandler._handle.assert_called_with(error) + + @patch("opentelemetry.sdk.error_handler.iter_entry_points") + def test_error_in_handler(self, mock_iter_entry_points): + class ErrorErrorHandler(ErrorHandler, ZeroDivisionError): + # pylint: disable=arguments-differ + + def _handle(self, error: Exception): + assert False + + mock_entry_point_error_error_handler = Mock() + mock_entry_point_error_error_handler.configure_mock( + **{"load.return_value": ErrorErrorHandler} + ) + + mock_iter_entry_points.configure_mock( + **{"return_value": [mock_entry_point_error_error_handler]} + ) + + error = ZeroDivisionError() + + with self.assertLogs(logger, ERROR): + with GlobalErrorHandler(): + raise error + + # pylint: disable=no-self-use + @patch("opentelemetry.sdk.error_handler.iter_entry_points") + def test_plugin_error_handler_context_manager( + self, mock_iter_entry_points + ): + + mock_error_handler_instance = Mock() + + class MockErrorHandlerClass(IndexError): + def __new__(cls): + return mock_error_handler_instance + + mock_entry_point_error_handler = Mock() + mock_entry_point_error_handler.configure_mock( + **{"load.return_value": MockErrorHandlerClass} + ) + + mock_iter_entry_points.configure_mock( + **{"return_value": [mock_entry_point_error_handler]} + ) + + error = IndexError() + + with GlobalErrorHandler(): + raise error + + with GlobalErrorHandler(): + pass + + # pylint: disable=protected-access + mock_error_handler_instance._handle.assert_called_once_with(error) diff --git a/scripts/check_for_valid_readme.py b/scripts/check_for_valid_readme.py index d555b4fa2f9..7eff0ae582e 100644 --- a/scripts/check_for_valid_readme.py +++ b/scripts/check_for_valid_readme.py @@ -29,6 +29,7 @@ def main(): error = False for path in map(Path, args.paths): + readme = path / "README.rst" try: if not is_valid_rst(readme):