Skip to content

Commit 8bb1e74

Browse files
authored
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
1 parent dd72d94 commit 8bb1e74

File tree

4 files changed

+68
-8
lines changed

4 files changed

+68
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
([#817](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/817))
1414
- `opentelemetry-instrumentation-kafka-python` added kafka-python module instrumentation.
1515
([#814](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/814))
16-
16+
- `opentelemetry-instrumentation-falcon` Falcon: Conditionally create SERVER spans
17+
([#867](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/867))
1718
### Fixed
1819

1920
- `opentelemetry-instrumentation-django` Django: Conditionally create SERVER spans

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ def response_hook(span, req, resp):
107107
get_global_response_propagator,
108108
)
109109
from opentelemetry.instrumentation.utils import (
110+
_start_internal_or_server_span,
110111
extract_attributes_from_object,
111112
http_status_to_status_code,
112113
)
113-
from opentelemetry.propagate import extract
114114
from opentelemetry.semconv.trace import SpanAttributes
115115
from opentelemetry.trace.status import Status
116116
from opentelemetry.util._time import _time_ns
@@ -195,12 +195,14 @@ def __call__(self, env, start_response):
195195

196196
start_time = _time_ns()
197197

198-
token = context.attach(extract(env, getter=otel_wsgi.wsgi_getter))
199-
span = self._tracer.start_span(
200-
otel_wsgi.get_default_span_name(env),
201-
kind=trace.SpanKind.SERVER,
198+
span, token = _start_internal_or_server_span(
199+
tracer=self._tracer,
200+
span_name=otel_wsgi.get_default_span_name(env),
202201
start_time=start_time,
202+
context_carrier=env,
203+
context_getter=otel_wsgi.wsgi_getter,
203204
)
205+
204206
if span.is_recording():
205207
attributes = otel_wsgi.collect_request_attributes(env)
206208
for key, value in attributes.items():
@@ -216,7 +218,8 @@ def _start_response(status, response_headers, *args, **kwargs):
216218
status, response_headers, *args, **kwargs
217219
)
218220
activation.__exit__(None, None, None)
219-
context.detach(token)
221+
if token is not None:
222+
context.detach(token)
220223
return response
221224

222225
try:
@@ -227,7 +230,8 @@ def _start_response(status, response_headers, *args, **kwargs):
227230
exc,
228231
getattr(exc, "__traceback__", None),
229232
)
230-
context.detach(token)
233+
if token is not None:
234+
context.detach(token)
231235
raise
232236

233237

instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from falcon import testing
1818

19+
from opentelemetry import trace
1920
from opentelemetry.instrumentation.falcon import FalconInstrumentor
2021
from opentelemetry.instrumentation.propagators import (
2122
TraceResponsePropagator,
@@ -264,3 +265,18 @@ def test_hooks(self):
264265
self.assertEqual(
265266
span.attributes["request_hook_attr"], "value from hook"
266267
)
268+
269+
270+
class TestFalconInstrumentationWrappedWithOtherFramework(TestFalconBase):
271+
def test_mark_span_internal_in_presence_of_span_from_other_framework(self):
272+
tracer = trace.get_tracer(__name__)
273+
with tracer.start_as_current_span(
274+
"test", kind=trace.SpanKind.SERVER
275+
) as parent_span:
276+
self.client().simulate_request(method="GET", path="/hello")
277+
span = self.memory_exporter.get_finished_spans()[0]
278+
assert span.status.is_ok
279+
self.assertEqual(trace.SpanKind.INTERNAL, span.kind)
280+
self.assertEqual(
281+
span.parent.span_id, parent_span.get_span_context().span_id
282+
)

opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616

1717
from wrapt import ObjectProxy
1818

19+
from opentelemetry import context, trace
20+
1921
# pylint: disable=unused-import
2022
# pylint: disable=E0611
2123
from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY # noqa: F401
24+
from opentelemetry.propagate import extract
2225
from opentelemetry.trace import StatusCode
2326

2427

@@ -67,3 +70,39 @@ def unwrap(obj, attr: str):
6770
func = getattr(obj, attr, None)
6871
if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"):
6972
setattr(obj, attr, func.__wrapped__)
73+
74+
75+
def _start_internal_or_server_span(
76+
tracer, span_name, start_time, context_carrier, context_getter
77+
):
78+
"""Returns internal or server span along with the token which can be used by caller to reset context
79+
80+
81+
Args:
82+
tracer : tracer in use by given instrumentation library
83+
name (string): name of the span
84+
start_time : start time of the span
85+
context_carrier : object which contains values that are
86+
used to construct a Context. This object
87+
must be paired with an appropriate getter
88+
which understands how to extract a value from it.
89+
context_getter : an object which contains a get function that can retrieve zero
90+
or more values from the carrier and a keys function that can get all the keys
91+
from carrier.
92+
"""
93+
94+
token = ctx = span_kind = None
95+
if trace.get_current_span() is trace.INVALID_SPAN:
96+
ctx = extract(context_carrier, getter=context_getter)
97+
token = context.attach(ctx)
98+
span_kind = trace.SpanKind.SERVER
99+
else:
100+
ctx = context.get_current()
101+
span_kind = trace.SpanKind.INTERNAL
102+
span = tracer.start_span(
103+
name=span_name,
104+
context=ctx,
105+
kind=span_kind,
106+
start_time=start_time,
107+
)
108+
return span, token

0 commit comments

Comments
 (0)