ASGI: Conditionally create SERVER spans (#843)

This commit is contained in:
Jakub Wach
2022-01-07 22:12:58 +01:00
committed by GitHub
parent 3de29868a9
commit 4c813c47e4
3 changed files with 80 additions and 15 deletions

View File

@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `opentelemetry-instrumentation-flask` Flask: Conditionally create SERVER spans
([#828](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/828))
- `opentelemetry-instrumentation-asgi` ASGI: Conditionally create SERVER spans
([#843](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/843))
## [1.8.0-0.27b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.8.0-0.27b0) - 2021-12-17
### Added

View File

@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=too-many-locals
"""
The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used
@ -110,7 +111,12 @@ from opentelemetry.instrumentation.utils import http_status_to_status_code
from opentelemetry.propagate import extract
from opentelemetry.propagators.textmap import Getter, Setter
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import Span, set_span_in_context
from opentelemetry.trace import (
INVALID_SPAN,
Span,
SpanKind,
set_span_in_context,
)
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.util.http import remove_url_credentials
@ -321,39 +327,48 @@ class OpenTelemetryMiddleware:
if self.excluded_urls and self.excluded_urls.url_disabled(url):
return await self.app(scope, receive, send)
token = context.attach(extract(scope, getter=asgi_getter))
server_span_name, additional_attributes = self.default_span_details(
scope
)
token = ctx = span_kind = None
if trace.get_current_span() is INVALID_SPAN:
ctx = extract(scope, getter=asgi_getter)
token = context.attach(ctx)
span_kind = SpanKind.SERVER
else:
ctx = context.get_current()
span_kind = SpanKind.INTERNAL
span_name, additional_attributes = self.default_span_details(scope)
try:
with self.tracer.start_as_current_span(
server_span_name,
kind=trace.SpanKind.SERVER,
) as server_span:
if server_span.is_recording():
span_name,
context=ctx,
kind=span_kind,
) as current_span:
if current_span.is_recording():
attributes = collect_request_attributes(scope)
attributes.update(additional_attributes)
for key, value in attributes.items():
server_span.set_attribute(key, value)
current_span.set_attribute(key, value)
if callable(self.server_request_hook):
self.server_request_hook(server_span, scope)
self.server_request_hook(current_span, scope)
otel_receive = self._get_otel_receive(
server_span_name, scope, receive
span_name, scope, receive
)
otel_send = self._get_otel_send(
server_span,
server_span_name,
current_span,
span_name,
scope,
send,
)
await self.app(scope, otel_receive, otel_send)
finally:
context.detach(token)
if token:
context.detach(token)
def _get_otel_receive(self, server_span_name, scope, receive):
@wraps(receive)

View File

@ -19,6 +19,7 @@ import fastapi
from fastapi.testclient import TestClient
import opentelemetry.instrumentation.fastapi as otel_fastapi
from opentelemetry import trace
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.trace import SpanAttributes
@ -329,3 +330,48 @@ class TestAutoInstrumentationLogic(unittest.TestCase):
should_be_original = fastapi.FastAPI
self.assertIs(original, should_be_original)
class TestWrappedApplication(TestBase):
def setUp(self):
super().setUp()
self.app = fastapi.FastAPI()
@self.app.get("/foobar")
async def _():
return {"message": "hello world"}
otel_fastapi.FastAPIInstrumentor().instrument_app(self.app)
self.client = TestClient(self.app)
self.tracer = self.tracer_provider.get_tracer(__name__)
def tearDown(self) -> None:
super().tearDown()
with self.disable_logging():
otel_fastapi.FastAPIInstrumentor().uninstrument_app(self.app)
def test_mark_span_internal_in_presence_of_span_from_other_framework(self):
with self.tracer.start_as_current_span(
"test", kind=trace.SpanKind.SERVER
) as parent_span:
resp = self.client.get("/foobar")
self.assertEqual(200, resp.status_code)
span_list = self.memory_exporter.get_finished_spans()
for span in span_list:
print(str(span.__class__) + ": " + str(span.__dict__))
# there should be 4 spans - single SERVER "test" and three INTERNAL "FastAPI"
self.assertEqual(trace.SpanKind.INTERNAL, span_list[0].kind)
self.assertEqual(trace.SpanKind.INTERNAL, span_list[1].kind)
# main INTERNAL span - child of test
self.assertEqual(trace.SpanKind.INTERNAL, span_list[2].kind)
self.assertEqual(
parent_span.context.span_id, span_list[2].parent.span_id
)
# SERVER "test"
self.assertEqual(trace.SpanKind.SERVER, span_list[3].kind)
self.assertEqual(
parent_span.context.span_id, span_list[3].context.span_id
)