diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e8a85c26..ab9b88a4ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#2461](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2461)) - Remove SDK dependency from opentelemetry-instrumentation-grpc ([#2474](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2474)) +- `opentelemetry-instrumentation-asyncio` Check for __name__ attribute in the coroutine + ([#2521](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2521)) ## Version 1.24.0/0.45b0 (2024-03-28) diff --git a/instrumentation/opentelemetry-instrumentation-asyncio/src/opentelemetry/instrumentation/asyncio/__init__.py b/instrumentation/opentelemetry-instrumentation-asyncio/src/opentelemetry/instrumentation/asyncio/__init__.py index 72aa5fd2aa..aea7c585fc 100644 --- a/instrumentation/opentelemetry-instrumentation-asyncio/src/opentelemetry/instrumentation/asyncio/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asyncio/src/opentelemetry/instrumentation/asyncio/__init__.py @@ -254,7 +254,9 @@ def trace_item(self, coro_or_future): # Task is already traced, return it if isinstance(coro_or_future, asyncio.Task): return coro_or_future - if asyncio.iscoroutine(coro_or_future): + if asyncio.iscoroutine(coro_or_future) and hasattr( + coro_or_future, "__name__" + ): return self.trace_coroutine(coro_or_future) if futures.isfuture(coro_or_future): return self.trace_future(coro_or_future) diff --git a/instrumentation/opentelemetry-instrumentation-asyncio/tests/test_asyncio_anext.py b/instrumentation/opentelemetry-instrumentation-asyncio/tests/test_asyncio_anext.py new file mode 100644 index 0000000000..9ce3fc4b33 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-asyncio/tests/test_asyncio_anext.py @@ -0,0 +1,56 @@ +# 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 asyncio +from unittest.mock import patch + +# pylint: disable=no-name-in-module +from opentelemetry.instrumentation.asyncio import AsyncioInstrumentor +from opentelemetry.instrumentation.asyncio.environment_variables import ( + OTEL_PYTHON_ASYNCIO_COROUTINE_NAMES_TO_TRACE, +) +from opentelemetry.test.test_base import TestBase +from opentelemetry.trace import get_tracer + + +class TestAsyncioAnext(TestBase): + @patch.dict( + "os.environ", + {OTEL_PYTHON_ASYNCIO_COROUTINE_NAMES_TO_TRACE: "async_func"}, + ) + def setUp(self): + super().setUp() + AsyncioInstrumentor().instrument() + self._tracer = get_tracer( + __name__, + ) + + def tearDown(self): + super().tearDown() + AsyncioInstrumentor().uninstrument() + + # Asyncio anext() does not have __name__ attribute, which is used to determine if the coroutine should be traced. + # This test is to ensure that the instrumentation does not break when the coroutine does not have __name__ attribute. + def test_asyncio_anext(self): + async def main(): + async def async_gen(): + for it in range(2): + yield it + + async_gen_instance = async_gen() + agen = anext(async_gen_instance) + await asyncio.create_task(agen) + + asyncio.run(main()) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0)