diff --git a/CHANGELOG.md b/CHANGELOG.md index 34355907c2..37d860fd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-tornado` Add support instrumentation for Tornado 5.1.1 ([#812](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/812)) + +- `opentelemetry-instrumentation-flask` Flask: Conditionally create SERVER spans + ([#828](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/828)) ## [1.7.1-0.26b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.7.0-0.26b0) - 2021-11-11 diff --git a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py index e96b005acd..de403f9ea3 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py @@ -176,15 +176,24 @@ def _before_request(): return flask_request_environ = flask.request.environ span_name = get_default_span_name() - token = context.attach( - extract(flask_request_environ, getter=otel_wsgi.wsgi_getter) - ) + + token = ctx = span_kind = None + + if trace.get_current_span() is trace.INVALID_SPAN: + ctx = extract(flask_request_environ, getter=otel_wsgi.wsgi_getter) + token = context.attach(ctx) + span_kind = trace.SpanKind.SERVER + else: + ctx = context.get_current() + span_kind = trace.SpanKind.INTERNAL span = tracer.start_span( span_name, - kind=trace.SpanKind.SERVER, + ctx, + kind=span_kind, start_time=flask_request_environ.get(_ENVIRON_STARTTIME_KEY), ) + if request_hook: request_hook(span, flask_request_environ) @@ -229,7 +238,9 @@ def _teardown_request(exc): activation.__exit__( type(exc), exc, getattr(exc, "__traceback__", None) ) - context.detach(flask.request.environ.get(_ENVIRON_TOKEN)) + + if flask.request.environ.get(_ENVIRON_TOKEN, None): + context.detach(flask.request.environ.get(_ENVIRON_TOKEN)) return _teardown_request diff --git a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py index 50dd188af1..a3064b52e5 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py +++ b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py @@ -23,6 +23,7 @@ get_global_response_propagator, set_global_response_propagator, ) +from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware from opentelemetry.sdk.resources import Resource from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.test.test_base import TestBase @@ -413,3 +414,31 @@ def test_custom_span_name(self): span_list[0].resource.attributes["service.name"], "flask-api-no-app", ) + + +class TestProgrammaticWrappedWithOtherFramework( + InstrumentationTest, TestBase, WsgiTestBase +): + def setUp(self): + super().setUp() + + self.app = Flask(__name__) + self.app.wsgi_app = OpenTelemetryMiddleware(self.app.wsgi_app) + FlaskInstrumentor().instrument_app(self.app) + self._common_initialization() + + def tearDown(self) -> None: + super().tearDown() + with self.disable_logging(): + FlaskInstrumentor().uninstrument_app(self.app) + + def test_mark_span_internal_in_presence_of_span_from_other_framework(self): + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + resp.get_data() + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(trace.SpanKind.INTERNAL, span_list[0].kind) + self.assertEqual(trace.SpanKind.SERVER, span_list[1].kind) + self.assertEqual( + span_list[0].parent.span_id, span_list[1].context.span_id + )