Skip to content

Commit 3b97af6

Browse files
committed
fix(click): issue with environment sniffing for click instrumentation
1 parent 21a35b6 commit 3b97af6

File tree

3 files changed

+28
-40
lines changed

3 files changed

+28
-40
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-click`: fix issue where starting uvicorn via `python -m` would cause the click instrumentation to give all requests the same trace id
1617
- `opentelemetry-instrumentation-botocore`: migrate off the deprecated events API to use the logs API
1718
([#3624](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3624))
1819
- `opentelemetry-instrumentation-dbapi`: fix crash retrieving libpq version when enabling commenter with psycopg

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

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,11 @@ def hello():
4949
import click
5050
from wrapt import wrap_function_wrapper
5151

52-
try:
53-
from flask.cli import ScriptInfo as FlaskScriptInfo
54-
except ImportError:
55-
FlaskScriptInfo = None
56-
57-
5852
from opentelemetry import trace
5953
from opentelemetry.instrumentation.click.package import _instruments
6054
from opentelemetry.instrumentation.click.version import __version__
6155
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
62-
from opentelemetry.instrumentation.utils import (
63-
unwrap,
64-
)
56+
from opentelemetry.instrumentation.utils import unwrap
6557
from opentelemetry.semconv._incubating.attributes.process_attributes import (
6658
PROCESS_COMMAND_ARGS,
6759
PROCESS_EXECUTABLE_NAME,
@@ -74,20 +66,6 @@ def hello():
7466
_logger = getLogger(__name__)
7567

7668

77-
def _skip_servers(ctx: click.Context):
78-
# flask run
79-
if (
80-
ctx.info_name == "run"
81-
and FlaskScriptInfo
82-
and isinstance(ctx.obj, FlaskScriptInfo)
83-
):
84-
return True
85-
# uvicorn
86-
if ctx.info_name == "uvicorn":
87-
return True
88-
return False
89-
90-
9169
def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
9270
# Subclasses of Command include groups and CLI runners, but
9371
# we only want to instrument the actual commands which are
@@ -99,7 +77,10 @@ def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
9977

10078
# we don't want to create a root span for long running processes like servers
10179
# otherwise all requests would have the same trace id
102-
if _skip_servers(ctx):
80+
if (
81+
"opentelemetry.instrumentation.asgi" in sys.modules
82+
or "opentelemetry.instrumentation.wsgi" in sys.modules
83+
):
10384
return wrapped(*args, **kwargs)
10485

10586
span_name = ctx.info_name

instrumentation/opentelemetry-instrumentation-click/tests/test_click.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@
1313
# limitations under the License.
1414

1515
import os
16+
import sys
1617
from unittest import mock
1718

1819
import click
19-
import pytest
2020
from click.testing import CliRunner
2121

22-
try:
23-
from flask import cli as flask_cli
24-
except ImportError:
25-
flask_cli = None
26-
2722
from opentelemetry.instrumentation.click import ClickInstrumentor
2823
from opentelemetry.test.test_base import TestBase
2924
from opentelemetry.trace import SpanKind
@@ -168,24 +163,35 @@ def command_raises():
168163
},
169164
)
170165

171-
def test_uvicorn_cli_command_ignored(self):
172-
@click.command("uvicorn")
173-
def command_uvicorn():
166+
@mock.patch("sys.argv", ["command.py"])
167+
def test_disabled_when_asgi_instrumentation_loaded(self):
168+
@click.command()
169+
def command():
174170
pass
175171

176172
runner = CliRunner()
177-
result = runner.invoke(command_uvicorn)
173+
with mock.patch.dict(
174+
sys.modules,
175+
{**sys.modules, "opentelemetry.instrumentation.asgi": mock.Mock()},
176+
):
177+
result = runner.invoke(command)
178178
self.assertEqual(result.exit_code, 0)
179179

180180
self.assertFalse(self.memory_exporter.get_finished_spans())
181181

182-
@pytest.mark.skipif(flask_cli is None, reason="requires flask")
183-
def test_flask_run_command_ignored(self):
182+
@mock.patch("sys.argv", ["command.py"])
183+
def test_disabled_when_wsgi_instrumentation_loaded(self):
184+
@click.command()
185+
def command():
186+
pass
187+
184188
runner = CliRunner()
185-
result = runner.invoke(
186-
flask_cli.run_command, obj=flask_cli.ScriptInfo()
187-
)
188-
self.assertEqual(result.exit_code, 2)
189+
with mock.patch.dict(
190+
sys.modules,
191+
{**sys.modules, "opentelemetry.instrumentation.wsgi": mock.Mock()},
192+
):
193+
result = runner.invoke(command)
194+
self.assertEqual(result.exit_code, 0)
189195

190196
self.assertFalse(self.memory_exporter.get_finished_spans())
191197

0 commit comments

Comments
 (0)