mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-31 22:23:12 +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
|
- `opentelemetry-instrumentation-httpx` Fix `RequestInfo`/`ResponseInfo` type hints
|
||||||
([#3105](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3105))
|
([#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
|
- `opentelemetry-instrumentation` Fix `get_dist_dependency_conflicts` if no distribution requires
|
||||||
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168))
|
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168))
|
||||||
|
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
# limitations under the License.
|
# 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/
|
.. _click: https://pypi.org/project/click/
|
||||||
|
|
||||||
@ -47,6 +49,12 @@ from typing import Collection
|
|||||||
import click
|
import click
|
||||||
from wrapt import wrap_function_wrapper
|
from wrapt import wrap_function_wrapper
|
||||||
|
|
||||||
|
try:
|
||||||
|
from flask.cli import ScriptInfo as FlaskScriptInfo
|
||||||
|
except ImportError:
|
||||||
|
FlaskScriptInfo = None
|
||||||
|
|
||||||
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.instrumentation.click.package import _instruments
|
from opentelemetry.instrumentation.click.package import _instruments
|
||||||
from opentelemetry.instrumentation.click.version import __version__
|
from opentelemetry.instrumentation.click.version import __version__
|
||||||
@ -66,6 +74,20 @@ from opentelemetry.trace.status import StatusCode
|
|||||||
_logger = getLogger(__name__)
|
_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):
|
def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
|
||||||
# Subclasses of Command include groups and CLI runners, but
|
# Subclasses of Command include groups and CLI runners, but
|
||||||
# we only want to instrument the actual commands which are
|
# 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)
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
ctx = args[0]
|
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_name = ctx.info_name
|
||||||
span_attributes = {
|
span_attributes = {
|
||||||
PROCESS_COMMAND_ARGS: sys.argv,
|
PROCESS_COMMAND_ARGS: sys.argv,
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
|
blinker==1.7.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
Deprecated==1.2.14
|
Deprecated==1.2.14
|
||||||
|
Flask==3.0.2
|
||||||
iniconfig==2.0.0
|
iniconfig==2.0.0
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
Jinja2==3.1.4
|
||||||
|
MarkupSafe==2.1.2
|
||||||
packaging==24.0
|
packaging==24.0
|
||||||
pluggy==1.5.0
|
pluggy==1.5.0
|
||||||
py-cpuinfo==9.0.0
|
py-cpuinfo==9.0.0
|
||||||
@ -9,7 +14,11 @@ pytest==7.4.4
|
|||||||
pytest-asyncio==0.23.5
|
pytest-asyncio==0.23.5
|
||||||
tomli==2.0.1
|
tomli==2.0.1
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.12.2
|
||||||
|
Werkzeug==3.0.6
|
||||||
wrapt==1.16.0
|
wrapt==1.16.0
|
||||||
zipp==3.19.2
|
zipp==3.19.2
|
||||||
-e opentelemetry-instrumentation
|
-e opentelemetry-instrumentation
|
||||||
-e instrumentation/opentelemetry-instrumentation-click
|
-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
|
from unittest import mock
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
import pytest
|
||||||
from click.testing import CliRunner
|
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.instrumentation.click import ClickInstrumentor
|
||||||
from opentelemetry.test.test_base import TestBase
|
from opentelemetry.test.test_base import TestBase
|
||||||
from opentelemetry.trace import SpanKind
|
from opentelemetry.trace import SpanKind
|
||||||
@ -60,7 +66,7 @@ class ClickTestCase(TestBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch("sys.argv", ["flask", "command"])
|
@mock.patch("sys.argv", ["flask", "command"])
|
||||||
def test_flask_run_command_wrapping(self):
|
def test_flask_command_wrapping(self):
|
||||||
@click.command()
|
@click.command()
|
||||||
def command():
|
def command():
|
||||||
pass
|
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):
|
def test_uninstrument(self):
|
||||||
ClickInstrumentor().uninstrument()
|
ClickInstrumentor().uninstrument()
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user