Skip to content

Commit

Permalink
click: ignore click based servers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
xrmx committed Jan 7, 2025
1 parent 3d5935f commit 770a6e3
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ def hello():
import click
from wrapt import wrap_function_wrapper

try:
from flask.cli import ScriptInfo as FlaskScriptInfo
except ImportError:
FlaskScripInfo = None


from opentelemetry import trace
from opentelemetry.instrumentation.click.package import _instruments
from opentelemetry.instrumentation.click.version import __version__
Expand All @@ -66,6 +72,20 @@ def hello():
_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
Expand All @@ -74,6 +94,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@
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
Expand Down Expand Up @@ -60,7 +66,7 @@ def command():
)

@mock.patch("sys.argv", ["flask", "command"])
def test_flask_run_command_wrapping(self):
def test_flask_command_wrapping(self):
@click.command()
def command():
pass
Expand Down Expand Up @@ -162,6 +168,27 @@ def command_raises():
},
)

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()

Expand Down

0 comments on commit 770a6e3

Please sign in to comment.