Skip to content

Commit 2d68dc5

Browse files
author
Filip Nikolovski
committed
Implement new HTTP semantic convention opt-in for Falcon
1 parent f9dc90f commit 2d68dc5

File tree

3 files changed

+56
-30
lines changed

3 files changed

+56
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- `opentelemetry-instrumentation-kafka-python` Instrument temporary fork, kafka-python-ng
1313
inside kafka-python's instrumentation
1414
([#2537](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2537)))
15+
- `opentelemetry-instrumentation-falcon` Implement new HTTP semantic convention opt-in for Falcon
1516

1617
## Breaking changes
1718

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

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ def response_hook(span, req, resp):
193193

194194
import opentelemetry.instrumentation.wsgi as otel_wsgi
195195
from opentelemetry import context, trace
196+
from opentelemetry.instrumentation._semconv import (
197+
_HTTPStabilityMode,
198+
_OpenTelemetrySemanticConventionStability,
199+
_OpenTelemetryStabilitySignalType,
200+
_report_new,
201+
_report_old,
202+
_set_status,
203+
)
196204
from opentelemetry.instrumentation.falcon.package import _instruments
197205
from opentelemetry.instrumentation.falcon.version import __version__
198206
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@@ -203,12 +211,12 @@ def response_hook(span, req, resp):
203211
from opentelemetry.instrumentation.utils import (
204212
_start_internal_or_server_span,
205213
extract_attributes_from_object,
206-
http_status_to_status_code,
207214
)
208215
from opentelemetry.metrics import get_meter
216+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
209217
from opentelemetry.semconv.metrics import MetricInstruments
210218
from opentelemetry.semconv.trace import SpanAttributes
211-
from opentelemetry.trace.status import Status, StatusCode
219+
from opentelemetry.trace.status import StatusCode
212220
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
213221

214222
_logger = getLogger(__name__)
@@ -243,6 +251,11 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
243251
def __init__(self, *args, **kwargs):
244252
otel_opts = kwargs.pop("_otel_opts", {})
245253

254+
_OpenTelemetrySemanticConventionStability._initialize()
255+
self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
256+
_OpenTelemetryStabilitySignalType.HTTP,
257+
)
258+
246259
# inject trace middleware
247260
self._middlewares_list = kwargs.pop("middleware", [])
248261
if self._middlewares_list is None:
@@ -283,6 +296,7 @@ def __init__(self, *args, **kwargs):
283296
),
284297
otel_opts.pop("request_hook", None),
285298
otel_opts.pop("response_hook", None),
299+
self._sem_conv_opt_in_mode,
286300
)
287301
self._middlewares_list.insert(0, trace_middleware)
288302
kwargs["middleware"] = self._middlewares_list
@@ -382,9 +396,21 @@ def _start_response(status, response_headers, *args, **kwargs):
382396
raise
383397
finally:
384398
if span.is_recording():
385-
duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = (
386-
span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
387-
)
399+
if _report_old(self._sem_conv_opt_in_mode):
400+
duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = (
401+
span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
402+
)
403+
if _report_new(self._sem_conv_opt_in_mode):
404+
duration_attrs[
405+
SpanAttributes.HTTP_RESPONSE_STATUS_CODE
406+
] = span.attributes.get(
407+
SpanAttributes.HTTP_RESPONSE_STATUS_CODE
408+
)
409+
if span.status.status_code == StatusCode.ERROR:
410+
duration_attrs[ERROR_TYPE] = span.attributes.get(
411+
ERROR_TYPE
412+
)
413+
388414
duration = max(round((default_timer() - start) * 1000), 0)
389415
self.duration_histogram.record(duration, duration_attrs)
390416
self.active_requests_counter.add(-1, active_requests_count_attrs)
@@ -409,11 +435,13 @@ def __init__(
409435
traced_request_attrs=None,
410436
request_hook=None,
411437
response_hook=None,
438+
sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT,
412439
):
413440
self.tracer = tracer
414441
self._traced_request_attrs = traced_request_attrs
415442
self._request_hook = request_hook
416443
self._response_hook = response_hook
444+
self._sem_conv_opt_in_mode = sem_conv_opt_in_mode
417445

418446
def process_request(self, req, resp):
419447
span = req.env.get(_ENVIRON_SPAN_KEY)
@@ -446,10 +474,8 @@ def process_response(
446474
return
447475

448476
status = resp.status
449-
reason = None
450477
if resource is None:
451478
status = "404"
452-
reason = "NotFound"
453479
else:
454480
if _ENVIRON_EXC in req.env:
455481
exc = req.env[_ENVIRON_EXC]
@@ -459,28 +485,18 @@ def process_response(
459485
if exc_type and not req_succeeded:
460486
if "HTTPNotFound" in exc_type.__name__:
461487
status = "404"
462-
reason = "NotFound"
463488
else:
464489
status = "500"
465-
reason = f"{exc_type.__name__}: {exc}"
466490

467491
status = status.split(" ")[0]
468492
try:
469-
status_code = int(status)
470-
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
471-
otel_status_code = http_status_to_status_code(
472-
status_code, server_span=True
473-
)
474-
475-
# set the description only when the status code is ERROR
476-
if otel_status_code is not StatusCode.ERROR:
477-
reason = None
478-
479-
span.set_status(
480-
Status(
481-
status_code=otel_status_code,
482-
description=reason,
483-
)
493+
_set_status(
494+
span,
495+
{},
496+
int(status),
497+
resp.status,
498+
span.kind == trace.SpanKind.SERVER,
499+
self._sem_conv_opt_in_mode,
484500
)
485501

486502
# Falcon 1 does not support response headers. So
@@ -493,6 +509,9 @@ def process_response(
493509
# Check if low-cardinality route is available as per semantic-conventions
494510
if req.uri_template:
495511
span.update_name(f"{req.method} {req.uri_template}")
512+
span.set_attribute(
513+
SpanAttributes.HTTP_ROUTE, req.uri_template
514+
)
496515
else:
497516
span.update_name(f"{req.method}")
498517

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
from opentelemetry import trace
2424
from opentelemetry.instrumentation._semconv import (
25+
_server_active_requests_count_attrs_new,
2526
_server_active_requests_count_attrs_old,
27+
_server_duration_attrs_new,
2628
_server_duration_attrs_old,
2729
)
2830
from opentelemetry.instrumentation.falcon import FalconInstrumentor
@@ -53,8 +55,10 @@
5355
"http.server.duration",
5456
]
5557
_recommended_attrs = {
56-
"http.server.active_requests": _server_active_requests_count_attrs_old,
57-
"http.server.duration": _server_duration_attrs_old,
58+
"http.server.active_requests": _server_active_requests_count_attrs_new
59+
+ _server_active_requests_count_attrs_old,
60+
"http.server.duration": _server_duration_attrs_new
61+
+ _server_duration_attrs_old,
5862
}
5963

6064

@@ -66,6 +70,7 @@ def setUp(self):
6670
{
6771
"OTEL_PYTHON_FALCON_EXCLUDED_URLS": "ping",
6872
"OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS": "query_string",
73+
"OTEL_SEMCONV_STABILITY_OPT_IN": "http/dup",
6974
},
7075
)
7176
self.env_patch.start()
@@ -129,6 +134,7 @@ def _test_method(self, method):
129134
SpanAttributes.HTTP_FLAVOR: "1.1",
130135
"falcon.resource": "HelloWorldResource",
131136
SpanAttributes.HTTP_STATUS_CODE: 201,
137+
SpanAttributes.HTTP_ROUTE: "/hello",
132138
},
133139
)
134140
# In falcon<3, NET_PEER_IP is always set by default to 127.0.0.1
@@ -180,10 +186,7 @@ def test_500(self):
180186
self.assertEqual(span.name, "GET /error")
181187
self.assertFalse(span.status.is_ok)
182188
self.assertEqual(span.status.status_code, StatusCode.ERROR)
183-
self.assertEqual(
184-
span.status.description,
185-
"NameError: name 'non_existent_var' is not defined",
186-
)
189+
self.assertEqual(span.status.description, None)
187190
self.assertSpanHasAttributes(
188191
span,
189192
{
@@ -196,6 +199,7 @@ def test_500(self):
196199
SpanAttributes.NET_PEER_PORT: 65133,
197200
SpanAttributes.HTTP_FLAVOR: "1.1",
198201
SpanAttributes.HTTP_STATUS_CODE: 500,
202+
SpanAttributes.HTTP_ROUTE: "/error",
199203
},
200204
)
201205
# In falcon<3, NET_PEER_IP is always set by default to 127.0.0.1
@@ -230,6 +234,7 @@ def test_url_template(self):
230234
SpanAttributes.HTTP_FLAVOR: "1.1",
231235
"falcon.resource": "UserResource",
232236
SpanAttributes.HTTP_STATUS_CODE: 200,
237+
SpanAttributes.HTTP_ROUTE: "/user/{user_id}",
233238
},
234239
)
235240

@@ -338,6 +343,7 @@ def test_falcon_metric_values(self):
338343
"net.host.port": 80,
339344
"net.host.name": "falconframework.org",
340345
"http.status_code": 404,
346+
"http.response.status_code": 404,
341347
}
342348
expected_requests_count_attributes = {
343349
"http.method": "GET",

0 commit comments

Comments
 (0)