Skip to content

Commit dc5f880

Browse files
committed
WIP flask cli
1 parent 07c3324 commit dc5f880

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,15 @@ def response_hook(span: Span, status: str, response_headers: List):
240240
"""
241241

242242
import weakref
243+
from functools import partial
243244
from logging import getLogger
244245
from time import time_ns
245246
from timeit import default_timer
246247
from typing import Collection
247248

248249
import flask
249250
from packaging import version as package_version
251+
from wrapt import wrap_function_wrapper
250252

251253
import opentelemetry.instrumentation.wsgi as otel_wsgi
252254
from opentelemetry import context, trace
@@ -264,14 +266,19 @@ def response_hook(span: Span, status: str, response_headers: List):
264266
from opentelemetry.instrumentation.propagators import (
265267
get_global_response_propagator,
266268
)
267-
from opentelemetry.instrumentation.utils import _start_internal_or_server_span
269+
from opentelemetry.instrumentation.utils import (
270+
_start_internal_or_server_span,
271+
unwrap,
272+
)
268273
from opentelemetry.metrics import get_meter
274+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
269275
from opentelemetry.semconv.attributes.http_attributes import HTTP_ROUTE
270276
from opentelemetry.semconv.metrics import MetricInstruments
271277
from opentelemetry.semconv.metrics.http_metrics import (
272278
HTTP_SERVER_REQUEST_DURATION,
273279
)
274280
from opentelemetry.semconv.trace import SpanAttributes
281+
from opentelemetry.trace.status import StatusCode
275282
from opentelemetry.util._importlib_metadata import version
276283
from opentelemetry.util.http import (
277284
get_excluded_urls,
@@ -620,6 +627,26 @@ def __init__(self, *args, **kwargs):
620627
self.teardown_request(_teardown_request)
621628

622629

630+
def _cli_command_wrapper(wrapped, instance, args, kwargs, tracer):
631+
span_name = "cli span name"
632+
span_attributes = {}
633+
634+
print("AAAAAAAAAAA", args, kwargs)
635+
print("AAAAAAAAAAA", wrapped, instance)
636+
637+
with tracer.start_as_current_span(
638+
name=span_name,
639+
kind=trace.SpanKind.INTERNAL,
640+
attributes=span_attributes,
641+
) as span:
642+
try:
643+
return wrapped(*args, **kwargs)
644+
except Exception as exc:
645+
span.set_status(StatusCode.ERROR, str(exc))
646+
span.set_attribute(ERROR_TYPE, exc.__class__.__qualname__)
647+
raise
648+
649+
623650
class FlaskInstrumentor(BaseInstrumentor):
624651
# pylint: disable=protected-access,attribute-defined-outside-init
625652
"""An instrumentor for flask.Flask
@@ -662,9 +689,24 @@ def _instrument(self, **kwargs):
662689

663690
flask.Flask = _InstrumentedFlask
664691

692+
# TODO: this is duplicated from _InstrumentedFlask, hopefully will be resolved once moving out of subclassing
693+
tracer = trace.get_tracer(
694+
__name__,
695+
__version__,
696+
tracer_provider,
697+
schema_url=_get_schema_url(sem_conv_opt_in_mode),
698+
)
699+
wrap_function_wrapper(
700+
"flask.cli",
701+
"AppGroup.command",
702+
partial(_cli_command_wrapper, tracer=tracer),
703+
)
704+
665705
def _uninstrument(self, **kwargs):
666706
flask.Flask = self._original_flask
667707

708+
unwrap(flask.cli.AppGroup, "command")
709+
668710
# pylint: disable=too-many-locals
669711
@staticmethod
670712
def instrument_app(

instrumentation/opentelemetry-instrumentation-flask/tests/test_automatic.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import click
1516
import flask
1617
from werkzeug.test import Client
1718
from werkzeug.wrappers import Response
@@ -94,3 +95,27 @@ def test_no_op_tracer_provider(self):
9495

9596
span_list = self.memory_exporter.get_finished_spans()
9697
self.assertEqual(len(span_list), 0)
98+
99+
100+
@app.cli.command("mycommand")
101+
@click.option("--option", default="default")
102+
def flask_command(option):
103+
pass
104+
105+
106+
def test_cli_command_wrapping(runner):
107+
FlaskInstrumentor().instrument()
108+
109+
result = runner.invoke(args=["mycommand"])
110+
(span,) = self.memory_exporter.get_finished_spans()
111+
112+
FlaskInstrumentor().uninstrument()
113+
114+
115+
def test_cli_command_wrapping_with_options(runner):
116+
FlaskInstrumentor().instrument()
117+
result_with_option = runner.invoke(
118+
args=["mycommand", "--option", "option"]
119+
)
120+
(span,) = self.memory_exporter.get_finished_spans()
121+
FlaskInstrumentor().uninstrument()

0 commit comments

Comments
 (0)