Skip to content

Commit 0c1fbde

Browse files
committed
click: ignore click based servers
We don't want to create a root span for long running processes like development web servers otherwise all requests would have the same trace id which is unfortunate.
1 parent 3d5935f commit 0c1fbde

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ def hello():
4747
import click
4848
from wrapt import wrap_function_wrapper
4949

50+
try:
51+
from flask.cli import ScriptInfo as FlaskScriptInfo
52+
except ImportError:
53+
FlaskScripInfo = None
54+
55+
5056
from opentelemetry import trace
5157
from opentelemetry.instrumentation.click.package import _instruments
5258
from opentelemetry.instrumentation.click.version import __version__
@@ -66,6 +72,20 @@ def hello():
6672
_logger = getLogger(__name__)
6773

6874

75+
def _skip_webserver(ctx: click.Context):
76+
# flask run
77+
if (
78+
ctx.info_name == "run"
79+
and FlaskScriptInfo
80+
and isinstance(ctx.obj, FlaskScriptInfo)
81+
):
82+
return True
83+
# uvicorn
84+
if ctx.info_name == "uvicorn":
85+
return True
86+
return False
87+
88+
6989
def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
7090
# Subclasses of Command include groups and CLI runners, but
7191
# we only want to instrument the actual commands which are
@@ -74,6 +94,12 @@ def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
7494
return wrapped(*args, **kwargs)
7595

7696
ctx = args[0]
97+
98+
# we don't want to create a root span for long running processes like development web servers
99+
# because otherwise all requests would have the same trace id
100+
if _skip_webserver(ctx):
101+
return wrapped(*args, **kwargs)
102+
77103
span_name = ctx.info_name
78104
span_attributes = {
79105
PROCESS_COMMAND_ARGS: sys.argv,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
asgiref==3.8.1
2+
blinker==1.7.0
23
click==8.1.7
34
Deprecated==1.2.14
5+
Flask==3.0.2
46
iniconfig==2.0.0
7+
itsdangerous==2.1.2
8+
Jinja2==3.1.4
9+
MarkupSafe==2.1.2
510
packaging==24.0
611
pluggy==1.5.0
712
py-cpuinfo==9.0.0
813
pytest==7.4.4
914
pytest-asyncio==0.23.5
1015
tomli==2.0.1
1116
typing_extensions==4.12.2
17+
Werkzeug==3.0.6
1218
wrapt==1.16.0
1319
zipp==3.19.2
1420
-e opentelemetry-instrumentation
1521
-e instrumentation/opentelemetry-instrumentation-click
22+
-e instrumentation/opentelemetry-instrumentation-flask
23+
-e instrumentation/opentelemetry-instrumentation-wsgi
24+
-e util/opentelemetry-util-http

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
from unittest import mock
1717

1818
import click
19+
import pytest
1920
from click.testing import CliRunner
2021

22+
try:
23+
from flask.cli import run_command as flask_run_command
24+
except ImportError:
25+
flask_run_command = None
26+
2127
from opentelemetry.instrumentation.click import ClickInstrumentor
2228
from opentelemetry.test.test_base import TestBase
2329
from opentelemetry.trace import SpanKind
@@ -60,7 +66,7 @@ def command():
6066
)
6167

6268
@mock.patch("sys.argv", ["flask", "command"])
63-
def test_flask_run_command_wrapping(self):
69+
def test_flask_command_wrapping(self):
6470
@click.command()
6571
def command():
6672
pass
@@ -162,6 +168,25 @@ def command_raises():
162168
},
163169
)
164170

171+
def test_uvicorn_cli_command_ignored(self):
172+
@click.command("uvicorn")
173+
def command_uvicorn():
174+
pass
175+
176+
runner = CliRunner()
177+
result = runner.invoke(command_uvicorn)
178+
self.assertEqual(result.exit_code, 0)
179+
180+
self.assertFalse(self.memory_exporter.get_finished_spans())
181+
182+
@pytest.mark.skipif(flask_run_command is None, reason="requires flask")
183+
def test_flask_run_command_ignored(self):
184+
runner = CliRunner()
185+
result = runner.invoke(flask_run_command)
186+
self.assertEqual(result.exit_code, 1)
187+
188+
self.assertFalse(self.memory_exporter.get_finished_spans())
189+
165190
def test_uninstrument(self):
166191
ClickInstrumentor().uninstrument()
167192

0 commit comments

Comments
 (0)