Conditionally create server spans for falcon (#867)

* Making span as internal for falcon in presence of a span in current context

* Updating changelog

* Fixing lint and generate build failures

* Resolving comments: Converting snippet to re-usable function

* Fixing build failures

* Resolving comments: Creating wrapper for start span to make internal/server span

* Rerun docker tests

* Resolving comments: Refactoring
This commit is contained in:
Ashutosh Goel
2022-01-21 21:54:01 +05:30
committed by GitHub
parent dd72d94615
commit 8bb1e74ccf
4 changed files with 68 additions and 8 deletions

View File

@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#817](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/817))
- `opentelemetry-instrumentation-kafka-python` added kafka-python module instrumentation.
([#814](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/814))
- `opentelemetry-instrumentation-falcon` Falcon: Conditionally create SERVER spans
([#867](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/867))
### Fixed
- `opentelemetry-instrumentation-django` Django: Conditionally create SERVER spans

View File

@ -107,10 +107,10 @@ from opentelemetry.instrumentation.propagators import (
get_global_response_propagator,
)
from opentelemetry.instrumentation.utils import (
_start_internal_or_server_span,
extract_attributes_from_object,
http_status_to_status_code,
)
from opentelemetry.propagate import extract
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace.status import Status
from opentelemetry.util._time import _time_ns
@ -195,12 +195,14 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
start_time = _time_ns()
token = context.attach(extract(env, getter=otel_wsgi.wsgi_getter))
span = self._tracer.start_span(
otel_wsgi.get_default_span_name(env),
kind=trace.SpanKind.SERVER,
span, token = _start_internal_or_server_span(
tracer=self._tracer,
span_name=otel_wsgi.get_default_span_name(env),
start_time=start_time,
context_carrier=env,
context_getter=otel_wsgi.wsgi_getter,
)
if span.is_recording():
attributes = otel_wsgi.collect_request_attributes(env)
for key, value in attributes.items():
@ -216,7 +218,8 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
status, response_headers, *args, **kwargs
)
activation.__exit__(None, None, None)
context.detach(token)
if token is not None:
context.detach(token)
return response
try:
@ -227,7 +230,8 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
exc,
getattr(exc, "__traceback__", None),
)
context.detach(token)
if token is not None:
context.detach(token)
raise

View File

@ -16,6 +16,7 @@ from unittest.mock import Mock, patch
from falcon import testing
from opentelemetry import trace
from opentelemetry.instrumentation.falcon import FalconInstrumentor
from opentelemetry.instrumentation.propagators import (
TraceResponsePropagator,
@ -264,3 +265,18 @@ class TestFalconInstrumentationHooks(TestFalconBase):
self.assertEqual(
span.attributes["request_hook_attr"], "value from hook"
)
class TestFalconInstrumentationWrappedWithOtherFramework(TestFalconBase):
def test_mark_span_internal_in_presence_of_span_from_other_framework(self):
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span(
"test", kind=trace.SpanKind.SERVER
) as parent_span:
self.client().simulate_request(method="GET", path="/hello")
span = self.memory_exporter.get_finished_spans()[0]
assert span.status.is_ok
self.assertEqual(trace.SpanKind.INTERNAL, span.kind)
self.assertEqual(
span.parent.span_id, parent_span.get_span_context().span_id
)

View File

@ -16,9 +16,12 @@ from typing import Dict, Sequence
from wrapt import ObjectProxy
from opentelemetry import context, trace
# pylint: disable=unused-import
# pylint: disable=E0611
from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY # noqa: F401
from opentelemetry.propagate import extract
from opentelemetry.trace import StatusCode
@ -67,3 +70,39 @@ def unwrap(obj, attr: str):
func = getattr(obj, attr, None)
if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"):
setattr(obj, attr, func.__wrapped__)
def _start_internal_or_server_span(
tracer, span_name, start_time, context_carrier, context_getter
):
"""Returns internal or server span along with the token which can be used by caller to reset context
Args:
tracer : tracer in use by given instrumentation library
name (string): name of the span
start_time : start time of the span
context_carrier : object which contains values that are
used to construct a Context. This object
must be paired with an appropriate getter
which understands how to extract a value from it.
context_getter : an object which contains a get function that can retrieve zero
or more values from the carrier and a keys function that can get all the keys
from carrier.
"""
token = ctx = span_kind = None
if trace.get_current_span() is trace.INVALID_SPAN:
ctx = extract(context_carrier, getter=context_getter)
token = context.attach(ctx)
span_kind = trace.SpanKind.SERVER
else:
ctx = context.get_current()
span_kind = trace.SpanKind.INTERNAL
span = tracer.start_span(
name=span_name,
context=ctx,
kind=span_kind,
start_time=start_time,
)
return span, token