Skip to content

Commit b941569

Browse files
author
Filip Nikolovski
committed
set request attributes in env, set status code in metric attributes independent from tracing decisions; update tests
1 parent 7754f0d commit b941569

File tree

2 files changed

+108
-13
lines changed

2 files changed

+108
-13
lines changed

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

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -214,17 +214,20 @@ def response_hook(span, req, resp):
214214
extract_attributes_from_object,
215215
)
216216
from opentelemetry.metrics import get_meter
217+
from opentelemetry.semconv.attributes.http_attributes import (
218+
HTTP_ROUTE,
219+
)
217220
from opentelemetry.semconv.metrics import MetricInstruments
218221
from opentelemetry.semconv.metrics.http_metrics import (
219222
HTTP_SERVER_REQUEST_DURATION,
220223
)
221-
from opentelemetry.semconv.trace import SpanAttributes
222224
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
223225

224226
_logger = getLogger(__name__)
225227

226228
_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
227229
_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
230+
_ENVIRON_REQ_ATTRS = "opentelemetry-falcon.req_attrs"
228231
_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
229232
_ENVIRON_TOKEN = "opentelemetry-falcon.token"
230233
_ENVIRON_EXC = "opentelemetry-falcon.exc"
@@ -247,6 +250,31 @@ def response_hook(span, req, resp):
247250
_falcon_version = 1
248251

249252

253+
def set_status_code(
254+
span,
255+
status_code,
256+
metric_attributes=None,
257+
sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT,
258+
):
259+
"""Adds HTTP response attributes to span using the status_code argument."""
260+
status_code_str = str(status_code)
261+
262+
try:
263+
status_code = int(status_code)
264+
except ValueError:
265+
status_code = -1
266+
if metric_attributes is None:
267+
metric_attributes = {}
268+
_set_status(
269+
span,
270+
metric_attributes,
271+
status_code,
272+
status_code_str,
273+
server_span=True,
274+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
275+
)
276+
277+
250278
class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
251279
_instrumented_falcon_apps = set()
252280

@@ -393,6 +421,7 @@ def __call__(self, env, start_response):
393421
activation.__enter__()
394422
env[_ENVIRON_SPAN_KEY] = span
395423
env[_ENVIRON_ACTIVATION_KEY] = activation
424+
env[_ENVIRON_REQ_ATTRS] = attributes
396425
exception = None
397426

398427
def _start_response(status, response_headers, *args, **kwargs):
@@ -478,8 +507,9 @@ def process_resource(self, req, resp, resource, params):
478507

479508
def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201,R0912
480509
span = req.env.get(_ENVIRON_SPAN_KEY)
510+
req_attrs = req.env.get(_ENVIRON_REQ_ATTRS)
481511

482-
if not span or not span.is_recording():
512+
if not span:
483513
return
484514

485515
status = resp.status
@@ -497,17 +527,22 @@ def process_response(self, req, resp, resource, req_succeeded=None): # pylint:d
497527
else:
498528
status = "500"
499529

500-
status = status.split(" ")[0]
530+
status_code = status.split(" ")[0]
501531
try:
502-
_set_status(
532+
set_status_code(
503533
span,
504-
{},
505-
int(status),
506-
resp.status,
507-
span.kind == trace.SpanKind.SERVER,
508-
self._sem_conv_opt_in_mode,
534+
status_code,
535+
req_attrs,
536+
sem_conv_opt_in_mode=self._sem_conv_opt_in_mode,
509537
)
510538

539+
if (
540+
_report_new(self._sem_conv_opt_in_mode)
541+
and req.uri_template
542+
and req_attrs is not None
543+
):
544+
req_attrs[HTTP_ROUTE] = req.uri_template
545+
511546
# Falcon 1 does not support response headers. So
512547
# send an empty dict.
513548
response_headers = {}
@@ -518,9 +553,7 @@ def process_response(self, req, resp, resource, req_succeeded=None): # pylint:d
518553
# Check if low-cardinality route is available as per semantic-conventions
519554
if req.uri_template:
520555
span.update_name(f"{req.method} {req.uri_template}")
521-
span.set_attribute(
522-
SpanAttributes.HTTP_ROUTE, req.uri_template
523-
)
556+
span.set_attribute(HTTP_ROUTE, req.uri_template)
524557
else:
525558
span.update_name(f"{req.method}")
526559

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

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,47 @@ def test_500(self):
308308
span.attributes[SpanAttributes.NET_PEER_IP], "127.0.0.1"
309309
)
310310

311+
def test_url_template_new_semconv(self):
312+
self.client().simulate_get("/user/123")
313+
spans = self.memory_exporter.get_finished_spans()
314+
metrics_list = self.memory_metrics_reader.get_metrics_data()
315+
316+
self.assertEqual(len(spans), 1)
317+
self.assertTrue(len(metrics_list.resource_metrics) != 0)
318+
span = spans[0]
319+
self.assertEqual(span.name, "GET /user/{user_id}")
320+
self.assertEqual(span.status.status_code, StatusCode.UNSET)
321+
self.assertEqual(
322+
span.status.description,
323+
None,
324+
)
325+
self.assertSpanHasAttributes(
326+
span,
327+
{
328+
SpanAttributes.HTTP_REQUEST_METHOD: "GET",
329+
SpanAttributes.SERVER_ADDRESS: "falconframework.org",
330+
SpanAttributes.URL_SCHEME: "http",
331+
SpanAttributes.SERVER_PORT: 80,
332+
SpanAttributes.URL_PATH: "/",
333+
SpanAttributes.CLIENT_PORT: 65133,
334+
SpanAttributes.NETWORK_PROTOCOL_VERSION: "1.1",
335+
"falcon.resource": "UserResource",
336+
SpanAttributes.HTTP_RESPONSE_STATUS_CODE: 200,
337+
SpanAttributes.HTTP_ROUTE: "/user/{user_id}",
338+
},
339+
)
340+
341+
for resource_metric in metrics_list.resource_metrics:
342+
for scope_metric in resource_metric.scope_metrics:
343+
for metric in scope_metric.metrics:
344+
if metric.name == "http.server.request.duration":
345+
data_points = list(metric.data.data_points)
346+
for point in data_points:
347+
self.assertIn(
348+
"http.route",
349+
point.attributes,
350+
)
351+
311352
def test_url_template(self):
312353
self.client().simulate_get("/user/123")
313354
spans = self.memory_exporter.get_finished_spans()
@@ -391,6 +432,28 @@ def test_traced_not_recording(self):
391432
self.assertFalse(mock_span.set_attribute.called)
392433
self.assertFalse(mock_span.set_status.called)
393434

435+
metrics_list = self.memory_metrics_reader.get_metrics_data()
436+
self.assertTrue(len(metrics_list.resource_metrics) != 0)
437+
438+
metrics_list = self.memory_metrics_reader.get_metrics_data()
439+
for resource_metric in metrics_list.resource_metrics:
440+
for scope_metric in resource_metric.scope_metrics:
441+
for metric in scope_metric.metrics:
442+
data_points = list(metric.data.data_points)
443+
self.assertEqual(len(data_points), 1)
444+
for point in list(metric.data.data_points):
445+
if isinstance(point, HistogramDataPoint):
446+
self.assertEqual(point.count, 1)
447+
if isinstance(point, NumberDataPoint):
448+
self.assertEqual(point.value, 0)
449+
for attr in point.attributes:
450+
self.assertIn(
451+
attr,
452+
_recommended_metrics_attrs_old[
453+
metric.name
454+
],
455+
)
456+
394457
def test_uninstrument_after_instrument(self):
395458
self.client().simulate_get(path="/hello")
396459
spans = self.memory_exporter.get_finished_spans()
@@ -486,7 +549,6 @@ def test_falcon_metric_values_both_semconv(self):
486549
self.assertEqual(point.value, 0)
487550
number_data_point_seen = True
488551
for attr in point.attributes:
489-
print(metric.name)
490552
self.assertIn(
491553
attr,
492554
_recommended_metrics_attrs_both[metric.name],

0 commit comments

Comments
 (0)