Skip to content

Commit b422b98

Browse files
committed
fix(asyncclick): disable asyncclick for long running processes like servers
1 parent cb89a34 commit b422b98

File tree

3 files changed

+49
-22
lines changed

3 files changed

+49
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Fixed
1515

16+
- `opentelemetry-instrumentation-asyncclick`: fix issue where servers using asyncclick would not get a separate span per-request
1617
- `opentelemetry-instrumentation-click`: fix issue where starting uvicorn via `python -m` would cause the click instrumentation to give all requests the same trace id
1718
- `opentelemetry-instrumentation-botocore`: migrate off the deprecated events API to use the logs API
1819
([#3624](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3624))

instrumentation/opentelemetry-instrumentation-asyncclick/src/opentelemetry/instrumentation/asyncclick/__init__.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,11 @@ async def hello():
4949
import sys
5050
from functools import partial
5151
from logging import getLogger
52-
from typing import (
53-
TYPE_CHECKING,
54-
Any,
55-
Awaitable,
56-
Callable,
57-
Collection,
58-
TypeVar,
59-
)
52+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Collection, TypeVar
6053

6154
import asyncclick
62-
from typing_extensions import ParamSpec, Unpack
63-
from wrapt import (
64-
wrap_function_wrapper, # type: ignore[reportUnknownVariableType]
65-
)
66-
67-
from opentelemetry import trace
68-
from opentelemetry.instrumentation.asyncclick.package import _instruments
69-
from opentelemetry.instrumentation.asyncclick.version import __version__
7055
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
71-
from opentelemetry.instrumentation.utils import (
72-
unwrap,
73-
)
56+
from opentelemetry.instrumentation.utils import unwrap
7457
from opentelemetry.semconv._incubating.attributes.process_attributes import (
7558
PROCESS_COMMAND_ARGS,
7659
PROCESS_EXECUTABLE_NAME,
@@ -79,6 +62,12 @@ async def hello():
7962
)
8063
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
8164
from opentelemetry.trace.status import StatusCode
65+
from typing_extensions import ParamSpec, Unpack
66+
from wrapt import wrap_function_wrapper # type: ignore[reportUnknownVariableType]
67+
68+
from opentelemetry import trace
69+
from opentelemetry.instrumentation.asyncclick.package import _instruments
70+
from opentelemetry.instrumentation.asyncclick.version import __version__
8271

8372
if TYPE_CHECKING:
8473
from typing import TypedDict
@@ -112,6 +101,14 @@ async def _command_invoke_wrapper(
112101

113102
ctx = args[0]
114103

104+
# we don't want to create a root span for long running processes like servers
105+
# otherwise all requests would have the same trace id
106+
if (
107+
"opentelemetry.instrumentation.asgi" in sys.modules
108+
or "opentelemetry.instrumentation.wsgi" in sys.modules
109+
):
110+
return await wrapped(*args, **kwargs)
111+
115112
span_name = ctx.info_name
116113
span_attributes = {
117114
PROCESS_COMMAND_ARGS: sys.argv,
@@ -132,9 +129,7 @@ async def _command_invoke_wrapper(
132129
span.set_status(StatusCode.ERROR, str(exc))
133130
if span.is_recording():
134131
span.set_attribute(ERROR_TYPE, type(exc).__qualname__)
135-
span.set_attribute(
136-
PROCESS_EXIT_CODE, getattr(exc, "exit_code", 1)
137-
)
132+
span.set_attribute(PROCESS_EXIT_CODE, getattr(exc, "exit_code", 1))
138133
raise
139134

140135

instrumentation/opentelemetry-instrumentation-asyncclick/tests/test_asyncclick.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import asyncio
1818
import os
19+
import sys
1920
from typing import Any
2021
from unittest import IsolatedAsyncioTestCase, mock
2122

@@ -158,6 +159,36 @@ async def command_raises() -> None:
158159
},
159160
)
160161

162+
@mock.patch("sys.argv", ["command.py"])
163+
def test_disabled_when_asgi_instrumentation_loaded(self):
164+
@asyncclick.command()
165+
async def command():
166+
pass
167+
168+
with mock.patch.dict(
169+
sys.modules,
170+
{**sys.modules, "opentelemetry.instrumentation.asgi": mock.Mock()},
171+
):
172+
result = run_asyncclick_command_test(command)
173+
self.assertEqual(result.exit_code, 0)
174+
175+
self.assertFalse(self.memory_exporter.get_finished_spans())
176+
177+
@mock.patch("sys.argv", ["command.py"])
178+
def test_disabled_when_wsgi_instrumentation_loaded(self):
179+
@asyncclick.command()
180+
async def command():
181+
pass
182+
183+
with mock.patch.dict(
184+
sys.modules,
185+
{**sys.modules, "opentelemetry.instrumentation.wsgi": mock.Mock()},
186+
):
187+
result = run_asyncclick_command_test(command)
188+
self.assertEqual(result.exit_code, 0)
189+
190+
self.assertFalse(self.memory_exporter.get_finished_spans())
191+
161192
def test_uninstrument(self):
162193
AsyncClickInstrumentor().uninstrument()
163194

0 commit comments

Comments
 (0)