Skip to content

Commit ba73170

Browse files
committed
fix(click): issue with environment sniffing for click instrumentation
1 parent ebe5bdf commit ba73170

File tree

3 files changed

+36
-49
lines changed

3 files changed

+36
-49
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: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,8 @@ def hello():
4747
from typing import Collection
4848

4949
import click
50-
from wrapt import wrap_function_wrapper
51-
52-
try:
53-
from flask.cli import ScriptInfo as FlaskScriptInfo
54-
except ImportError:
55-
FlaskScriptInfo = None
56-
57-
58-
from opentelemetry import trace
59-
from opentelemetry.instrumentation.click.package import _instruments
60-
from opentelemetry.instrumentation.click.version import __version__
6150
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
62-
from opentelemetry.instrumentation.utils import (
63-
unwrap,
64-
)
51+
from opentelemetry.instrumentation.utils import unwrap
6552
from opentelemetry.semconv._incubating.attributes.process_attributes import (
6653
PROCESS_COMMAND_ARGS,
6754
PROCESS_EXECUTABLE_NAME,
@@ -70,22 +57,13 @@ def hello():
7057
)
7158
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
7259
from opentelemetry.trace.status import StatusCode
60+
from wrapt import wrap_function_wrapper
7361

74-
_logger = getLogger(__name__)
75-
62+
from opentelemetry import trace
63+
from opentelemetry.instrumentation.click.package import _instruments
64+
from opentelemetry.instrumentation.click.version import __version__
7665

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
66+
_logger = getLogger(__name__)
8967

9068

9169
def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
@@ -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
@@ -121,9 +102,7 @@ def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
121102
span.set_status(StatusCode.ERROR, str(exc))
122103
if span.is_recording():
123104
span.set_attribute(ERROR_TYPE, exc.__class__.__qualname__)
124-
span.set_attribute(
125-
PROCESS_EXIT_CODE, getattr(exc, "exit_code", 1)
126-
)
105+
span.set_attribute(PROCESS_EXIT_CODE, getattr(exc, "exit_code", 1))
127106
raise
128107

129108

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

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

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

1819
import click
1920
import pytest
2021
from click.testing import CliRunner
21-
22-
try:
23-
from flask import cli as flask_cli
24-
except ImportError:
25-
flask_cli = None
26-
27-
from opentelemetry.instrumentation.click import ClickInstrumentor
2822
from opentelemetry.test.test_base import TestBase
2923
from opentelemetry.trace import SpanKind
3024
from opentelemetry.trace.status import StatusCode
3125

26+
from opentelemetry.instrumentation.click import ClickInstrumentor
27+
3228

3329
class ClickTestCase(TestBase):
3430
# pylint: disable=unbalanced-tuple-unpacking
@@ -168,24 +164,35 @@ def command_raises():
168164
},
169165
)
170166

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

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

180181
self.assertFalse(self.memory_exporter.get_finished_spans())
181182

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

190197
self.assertFalse(self.memory_exporter.get_finished_spans())
191198

0 commit comments

Comments
 (0)