@@ -193,6 +193,14 @@ def response_hook(span, req, resp):
193193
194194import opentelemetry .instrumentation .wsgi as otel_wsgi
195195from opentelemetry import context , trace
196+ from opentelemetry .instrumentation ._semconv import (
197+ _get_schema_url ,
198+ _OpenTelemetrySemanticConventionStability ,
199+ _OpenTelemetryStabilitySignalType ,
200+ _report_new ,
201+ _report_old ,
202+ _StabilityMode ,
203+ )
196204from opentelemetry .instrumentation .falcon .package import _instruments
197205from opentelemetry .instrumentation .falcon .version import __version__
198206from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
@@ -203,18 +211,22 @@ def response_hook(span, req, resp):
203211from opentelemetry .instrumentation .utils import (
204212 _start_internal_or_server_span ,
205213 extract_attributes_from_object ,
206- http_status_to_status_code ,
207214)
208215from opentelemetry .metrics import get_meter
216+ from opentelemetry .semconv .attributes .http_attributes import (
217+ HTTP_ROUTE ,
218+ )
209219from opentelemetry .semconv .metrics import MetricInstruments
210- from opentelemetry .semconv .trace import SpanAttributes
211- from opentelemetry .trace .status import Status , StatusCode
220+ from opentelemetry .semconv .metrics .http_metrics import (
221+ HTTP_SERVER_REQUEST_DURATION ,
222+ )
212223from opentelemetry .util .http import get_excluded_urls , get_traced_request_attrs
213224
214225_logger = getLogger (__name__ )
215226
216227_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
217228_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
229+ _ENVIRON_REQ_ATTRS = "opentelemetry-falcon.req_attrs"
218230_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
219231_ENVIRON_TOKEN = "opentelemetry-falcon.token"
220232_ENVIRON_EXC = "opentelemetry-falcon.exc"
@@ -243,6 +255,10 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
243255 def __init__ (self , * args , ** kwargs ):
244256 otel_opts = kwargs .pop ("_otel_opts" , {})
245257
258+ self ._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability ._get_opentelemetry_stability_opt_in_mode (
259+ _OpenTelemetryStabilitySignalType .HTTP ,
260+ )
261+
246262 # inject trace middleware
247263 self ._middlewares_list = kwargs .pop ("middleware" , [])
248264 if self ._middlewares_list is None :
@@ -257,19 +273,30 @@ def __init__(self, *args, **kwargs):
257273 __name__ ,
258274 __version__ ,
259275 tracer_provider ,
260- schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
276+ schema_url = _get_schema_url ( self . _sem_conv_opt_in_mode ) ,
261277 )
262278 self ._otel_meter = get_meter (
263279 __name__ ,
264280 __version__ ,
265281 meter_provider ,
266- schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
267- )
268- self .duration_histogram = self ._otel_meter .create_histogram (
269- name = MetricInstruments .HTTP_SERVER_DURATION ,
270- unit = "ms" ,
271- description = "Measures the duration of inbound HTTP requests." ,
282+ schema_url = _get_schema_url (self ._sem_conv_opt_in_mode ),
272283 )
284+
285+ self .duration_histogram_old = None
286+ if _report_old (self ._sem_conv_opt_in_mode ):
287+ self .duration_histogram_old = self ._otel_meter .create_histogram (
288+ name = MetricInstruments .HTTP_SERVER_DURATION ,
289+ unit = "ms" ,
290+ description = "Measures the duration of inbound HTTP requests." ,
291+ )
292+ self .duration_histogram_new = None
293+ if _report_new (self ._sem_conv_opt_in_mode ):
294+ self .duration_histogram_new = self ._otel_meter .create_histogram (
295+ name = HTTP_SERVER_REQUEST_DURATION ,
296+ description = "Duration of HTTP server requests." ,
297+ unit = "s" ,
298+ )
299+
273300 self .active_requests_counter = self ._otel_meter .create_up_down_counter (
274301 name = MetricInstruments .HTTP_SERVER_ACTIVE_REQUESTS ,
275302 unit = "requests" ,
@@ -283,6 +310,7 @@ def __init__(self, *args, **kwargs):
283310 ),
284311 otel_opts .pop ("request_hook" , None ),
285312 otel_opts .pop ("response_hook" , None ),
313+ self ._sem_conv_opt_in_mode ,
286314 )
287315 self ._middlewares_list .insert (0 , trace_middleware )
288316 kwargs ["middleware" ] = self ._middlewares_list
@@ -343,11 +371,14 @@ def __call__(self, env, start_response):
343371 context_carrier = env ,
344372 context_getter = otel_wsgi .wsgi_getter ,
345373 )
346- attributes = otel_wsgi .collect_request_attributes (env )
374+ attributes = otel_wsgi .collect_request_attributes (
375+ env , self ._sem_conv_opt_in_mode
376+ )
347377 active_requests_count_attrs = (
348- otel_wsgi ._parse_active_request_count_attrs (attributes )
378+ otel_wsgi ._parse_active_request_count_attrs (
379+ attributes , self ._sem_conv_opt_in_mode
380+ )
349381 )
350- duration_attrs = otel_wsgi ._parse_duration_attrs (attributes )
351382 self .active_requests_counter .add (1 , active_requests_count_attrs )
352383
353384 if span .is_recording ():
@@ -364,6 +395,7 @@ def __call__(self, env, start_response):
364395 activation .__enter__ ()
365396 env [_ENVIRON_SPAN_KEY ] = span
366397 env [_ENVIRON_ACTIVATION_KEY ] = activation
398+ env [_ENVIRON_REQ_ATTRS ] = attributes
367399 exception = None
368400
369401 def _start_response (status , response_headers , * args , ** kwargs ):
@@ -379,12 +411,22 @@ def _start_response(status, response_headers, *args, **kwargs):
379411 exception = exc
380412 raise
381413 finally :
382- if span .is_recording ():
383- duration_attrs [SpanAttributes .HTTP_STATUS_CODE ] = (
384- span .attributes .get (SpanAttributes .HTTP_STATUS_CODE )
414+ duration_s = default_timer () - start
415+ if self .duration_histogram_old :
416+ duration_attrs = otel_wsgi ._parse_duration_attrs (
417+ attributes , _StabilityMode .DEFAULT
418+ )
419+ self .duration_histogram_old .record (
420+ max (round (duration_s * 1000 ), 0 ), duration_attrs
421+ )
422+ if self .duration_histogram_new :
423+ duration_attrs = otel_wsgi ._parse_duration_attrs (
424+ attributes , _StabilityMode .HTTP
425+ )
426+ self .duration_histogram_new .record (
427+ max (duration_s , 0 ), duration_attrs
385428 )
386- duration = max (round ((default_timer () - start ) * 1000 ), 0 )
387- self .duration_histogram .record (duration , duration_attrs )
429+
388430 self .active_requests_counter .add (- 1 , active_requests_count_attrs )
389431 if exception is None :
390432 activation .__exit__ (None , None , None )
@@ -407,11 +449,13 @@ def __init__(
407449 traced_request_attrs = None ,
408450 request_hook = None ,
409451 response_hook = None ,
452+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
410453 ):
411454 self .tracer = tracer
412455 self ._traced_request_attrs = traced_request_attrs
413456 self ._request_hook = request_hook
414457 self ._response_hook = response_hook
458+ self ._sem_conv_opt_in_mode = sem_conv_opt_in_mode
415459
416460 def process_request (self , req , resp ):
417461 span = req .env .get (_ENVIRON_SPAN_KEY )
@@ -437,58 +481,60 @@ def process_resource(self, req, resp, resource, params):
437481
438482 def process_response (self , req , resp , resource , req_succeeded = None ): # pylint:disable=R0201,R0912
439483 span = req .env .get (_ENVIRON_SPAN_KEY )
484+ req_attrs = req .env .get (_ENVIRON_REQ_ATTRS )
440485
441- if not span or not span . is_recording () :
486+ if not span :
442487 return
443488
444489 status = resp .status
445- reason = None
446490 if resource is None :
447- status = "404"
448- reason = "NotFound"
491+ status = falcon .HTTP_404
449492 else :
493+ exc_type , exc = None , None
450494 if _ENVIRON_EXC in req .env :
451495 exc = req .env [_ENVIRON_EXC ]
452496 exc_type = type (exc )
453- else :
454- exc_type , exc = None , None
497+
455498 if exc_type and not req_succeeded :
456499 if "HTTPNotFound" in exc_type .__name__ :
457- status = "404"
458- reason = "NotFound"
500+ status = falcon .HTTP_404
501+ elif isinstance (exc , (falcon .HTTPError , falcon .HTTPStatus )):
502+ try :
503+ if _falcon_version > 2 :
504+ status = falcon .code_to_http_status (exc .status )
505+ else :
506+ status = exc .status
507+ except ValueError :
508+ status = falcon .HTTP_500
459509 else :
460- status = "500"
461- reason = f"{ exc_type .__name__ } : { exc } "
510+ status = falcon .HTTP_500
511+
512+ # Falcon 1 does not support response headers. So
513+ # send an empty dict.
514+ response_headers = {}
515+ if _falcon_version > 1 :
516+ response_headers = resp .headers
517+
518+ otel_wsgi .add_response_attributes (
519+ span ,
520+ status ,
521+ response_headers ,
522+ req_attrs ,
523+ self ._sem_conv_opt_in_mode ,
524+ )
462525
463- status = status .split (" " )[0 ]
526+ if (
527+ _report_new (self ._sem_conv_opt_in_mode )
528+ and req .uri_template
529+ and req_attrs is not None
530+ ):
531+ req_attrs [HTTP_ROUTE ] = req .uri_template
464532 try :
465- status_code = int (status )
466- span .set_attribute (SpanAttributes .HTTP_STATUS_CODE , status_code )
467- otel_status_code = http_status_to_status_code (
468- status_code , server_span = True
469- )
470-
471- # set the description only when the status code is ERROR
472- if otel_status_code is not StatusCode .ERROR :
473- reason = None
474-
475- span .set_status (
476- Status (
477- status_code = otel_status_code ,
478- description = reason ,
479- )
480- )
481-
482- # Falcon 1 does not support response headers. So
483- # send an empty dict.
484- response_headers = {}
485- if _falcon_version > 1 :
486- response_headers = resp .headers
487-
488533 if span .is_recording () and span .kind == trace .SpanKind .SERVER :
489534 # Check if low-cardinality route is available as per semantic-conventions
490535 if req .uri_template :
491536 span .update_name (f"{ req .method } { req .uri_template } " )
537+ span .set_attribute (HTTP_ROUTE , req .uri_template )
492538 else :
493539 span .update_name (f"{ req .method } " )
494540
0 commit comments