mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-29 13:12:39 +08:00
click: ignore click based servers (#3174)
* click: ignore click based servers We don't want to create a root span for long running processes like servers otherwise all requests would have the same trace id which is unfortunate. --------- Co-authored-by: Tammy Baylis <96076570+tammy-baylis-swi@users.noreply.github.com>
This commit is contained in:

committed by
GitHub

parent
147e3f754e
commit
908437db5d
@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- `opentelemetry-instrumentation-httpx` Fix `RequestInfo`/`ResponseInfo` type hints
|
||||
([#3105](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3105))
|
||||
- `opentelemetry-instrumentation-click` Disable tracing of well-known server click commands
|
||||
([#3174](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3174))
|
||||
- `opentelemetry-instrumentation` Fix `get_dist_dependency_conflicts` if no distribution requires
|
||||
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168))
|
||||
|
||||
|
@ -13,7 +13,9 @@
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Instrument `click`_ CLI applications.
|
||||
Instrument `click`_ CLI applications. The instrumentor will avoid instrumenting
|
||||
well-known servers (e.g. *flask run* and *uvicorn*) to avoid unexpected effects
|
||||
like every request having the same Trace ID.
|
||||
|
||||
.. _click: https://pypi.org/project/click/
|
||||
|
||||
@ -47,6 +49,12 @@ from typing import Collection
|
||||
import click
|
||||
from wrapt import wrap_function_wrapper
|
||||
|
||||
try:
|
||||
from flask.cli import ScriptInfo as FlaskScriptInfo
|
||||
except ImportError:
|
||||
FlaskScriptInfo = None
|
||||
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.click.package import _instruments
|
||||
from opentelemetry.instrumentation.click.version import __version__
|
||||
@ -66,6 +74,20 @@ from opentelemetry.trace.status import StatusCode
|
||||
_logger = getLogger(__name__)
|
||||
|
||||
|
||||
def _skip_servers(ctx: click.Context):
|
||||
# flask run
|
||||
if (
|
||||
ctx.info_name == "run"
|
||||
and FlaskScriptInfo
|
||||
and isinstance(ctx.obj, FlaskScriptInfo)
|
||||
):
|
||||
return True
|
||||
# uvicorn
|
||||
if ctx.info_name == "uvicorn":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
|
||||
# Subclasses of Command include groups and CLI runners, but
|
||||
# we only want to instrument the actual commands which are
|
||||
@ -74,6 +96,12 @@ def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
ctx = args[0]
|
||||
|
||||
# we don't want to create a root span for long running processes like servers
|
||||
# otherwise all requests would have the same trace id
|
||||
if _skip_servers(ctx):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
span_name = ctx.info_name
|
||||
span_attributes = {
|
||||
PROCESS_COMMAND_ARGS: sys.argv,
|
||||
|
@ -1,7 +1,12 @@
|
||||
asgiref==3.8.1
|
||||
blinker==1.7.0
|
||||
click==8.1.7
|
||||
Deprecated==1.2.14
|
||||
Flask==3.0.2
|
||||
iniconfig==2.0.0
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.4
|
||||
MarkupSafe==2.1.2
|
||||
packaging==24.0
|
||||
pluggy==1.5.0
|
||||
py-cpuinfo==9.0.0
|
||||
@ -9,7 +14,11 @@ pytest==7.4.4
|
||||
pytest-asyncio==0.23.5
|
||||
tomli==2.0.1
|
||||
typing_extensions==4.12.2
|
||||
Werkzeug==3.0.6
|
||||
wrapt==1.16.0
|
||||
zipp==3.19.2
|
||||
-e opentelemetry-instrumentation
|
||||
-e instrumentation/opentelemetry-instrumentation-click
|
||||
-e instrumentation/opentelemetry-instrumentation-flask
|
||||
-e instrumentation/opentelemetry-instrumentation-wsgi
|
||||
-e util/opentelemetry-util-http
|
||||
|
@ -16,8 +16,14 @@ import os
|
||||
from unittest import mock
|
||||
|
||||
import click
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
try:
|
||||
from flask import cli as flask_cli
|
||||
except ImportError:
|
||||
flask_cli = None
|
||||
|
||||
from opentelemetry.instrumentation.click import ClickInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
from opentelemetry.trace import SpanKind
|
||||
@ -60,7 +66,7 @@ class ClickTestCase(TestBase):
|
||||
)
|
||||
|
||||
@mock.patch("sys.argv", ["flask", "command"])
|
||||
def test_flask_run_command_wrapping(self):
|
||||
def test_flask_command_wrapping(self):
|
||||
@click.command()
|
||||
def command():
|
||||
pass
|
||||
@ -162,6 +168,27 @@ class ClickTestCase(TestBase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_uvicorn_cli_command_ignored(self):
|
||||
@click.command("uvicorn")
|
||||
def command_uvicorn():
|
||||
pass
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(command_uvicorn)
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.assertFalse(self.memory_exporter.get_finished_spans())
|
||||
|
||||
@pytest.mark.skipif(flask_cli is None, reason="requires flask")
|
||||
def test_flask_run_command_ignored(self):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
flask_cli.run_command, obj=flask_cli.ScriptInfo()
|
||||
)
|
||||
self.assertEqual(result.exit_code, 2)
|
||||
|
||||
self.assertFalse(self.memory_exporter.get_finished_spans())
|
||||
|
||||
def test_uninstrument(self):
|
||||
ClickInstrumentor().uninstrument()
|
||||
|
||||
|
Reference in New Issue
Block a user