@@ -193,6 +193,14 @@ def response_hook(span, req, resp):
193
193
194
194
import opentelemetry .instrumentation .wsgi as otel_wsgi
195
195
from 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
+ )
196
204
from opentelemetry .instrumentation .falcon .package import _instruments
197
205
from opentelemetry .instrumentation .falcon .version import __version__
198
206
from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
@@ -203,18 +211,22 @@ def response_hook(span, req, resp):
203
211
from opentelemetry .instrumentation .utils import (
204
212
_start_internal_or_server_span ,
205
213
extract_attributes_from_object ,
206
- http_status_to_status_code ,
207
214
)
208
215
from opentelemetry .metrics import get_meter
216
+ from opentelemetry .semconv .attributes .http_attributes import (
217
+ HTTP_ROUTE ,
218
+ )
209
219
from 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
+ )
212
223
from opentelemetry .util .http import get_excluded_urls , get_traced_request_attrs
213
224
214
225
_logger = getLogger (__name__ )
215
226
216
227
_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
217
228
_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
229
+ _ENVIRON_REQ_ATTRS = "opentelemetry-falcon.req_attrs"
218
230
_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
219
231
_ENVIRON_TOKEN = "opentelemetry-falcon.token"
220
232
_ENVIRON_EXC = "opentelemetry-falcon.exc"
@@ -243,6 +255,10 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
243
255
def __init__ (self , * args , ** kwargs ):
244
256
otel_opts = kwargs .pop ("_otel_opts" , {})
245
257
258
+ self ._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability ._get_opentelemetry_stability_opt_in_mode (
259
+ _OpenTelemetryStabilitySignalType .HTTP ,
260
+ )
261
+
246
262
# inject trace middleware
247
263
self ._middlewares_list = kwargs .pop ("middleware" , [])
248
264
if self ._middlewares_list is None :
@@ -257,19 +273,30 @@ def __init__(self, *args, **kwargs):
257
273
__name__ ,
258
274
__version__ ,
259
275
tracer_provider ,
260
- schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
276
+ schema_url = _get_schema_url ( self . _sem_conv_opt_in_mode ) ,
261
277
)
262
278
self ._otel_meter = get_meter (
263
279
__name__ ,
264
280
__version__ ,
265
281
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 ),
272
283
)
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
+
273
300
self .active_requests_counter = self ._otel_meter .create_up_down_counter (
274
301
name = MetricInstruments .HTTP_SERVER_ACTIVE_REQUESTS ,
275
302
unit = "requests" ,
@@ -283,6 +310,7 @@ def __init__(self, *args, **kwargs):
283
310
),
284
311
otel_opts .pop ("request_hook" , None ),
285
312
otel_opts .pop ("response_hook" , None ),
313
+ self ._sem_conv_opt_in_mode ,
286
314
)
287
315
self ._middlewares_list .insert (0 , trace_middleware )
288
316
kwargs ["middleware" ] = self ._middlewares_list
@@ -343,11 +371,14 @@ def __call__(self, env, start_response):
343
371
context_carrier = env ,
344
372
context_getter = otel_wsgi .wsgi_getter ,
345
373
)
346
- attributes = otel_wsgi .collect_request_attributes (env )
374
+ attributes = otel_wsgi .collect_request_attributes (
375
+ env , self ._sem_conv_opt_in_mode
376
+ )
347
377
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
+ )
349
381
)
350
- duration_attrs = otel_wsgi ._parse_duration_attrs (attributes )
351
382
self .active_requests_counter .add (1 , active_requests_count_attrs )
352
383
353
384
if span .is_recording ():
@@ -364,6 +395,7 @@ def __call__(self, env, start_response):
364
395
activation .__enter__ ()
365
396
env [_ENVIRON_SPAN_KEY ] = span
366
397
env [_ENVIRON_ACTIVATION_KEY ] = activation
398
+ env [_ENVIRON_REQ_ATTRS ] = attributes
367
399
exception = None
368
400
369
401
def _start_response (status , response_headers , * args , ** kwargs ):
@@ -379,12 +411,22 @@ def _start_response(status, response_headers, *args, **kwargs):
379
411
exception = exc
380
412
raise
381
413
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
385
428
)
386
- duration = max (round ((default_timer () - start ) * 1000 ), 0 )
387
- self .duration_histogram .record (duration , duration_attrs )
429
+
388
430
self .active_requests_counter .add (- 1 , active_requests_count_attrs )
389
431
if exception is None :
390
432
activation .__exit__ (None , None , None )
@@ -407,11 +449,13 @@ def __init__(
407
449
traced_request_attrs = None ,
408
450
request_hook = None ,
409
451
response_hook = None ,
452
+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
410
453
):
411
454
self .tracer = tracer
412
455
self ._traced_request_attrs = traced_request_attrs
413
456
self ._request_hook = request_hook
414
457
self ._response_hook = response_hook
458
+ self ._sem_conv_opt_in_mode = sem_conv_opt_in_mode
415
459
416
460
def process_request (self , req , resp ):
417
461
span = req .env .get (_ENVIRON_SPAN_KEY )
@@ -437,58 +481,60 @@ def process_resource(self, req, resp, resource, params):
437
481
438
482
def process_response (self , req , resp , resource , req_succeeded = None ): # pylint:disable=R0201,R0912
439
483
span = req .env .get (_ENVIRON_SPAN_KEY )
484
+ req_attrs = req .env .get (_ENVIRON_REQ_ATTRS )
440
485
441
- if not span or not span . is_recording () :
486
+ if not span :
442
487
return
443
488
444
489
status = resp .status
445
- reason = None
446
490
if resource is None :
447
- status = "404"
448
- reason = "NotFound"
491
+ status = falcon .HTTP_404
449
492
else :
493
+ exc_type , exc = None , None
450
494
if _ENVIRON_EXC in req .env :
451
495
exc = req .env [_ENVIRON_EXC ]
452
496
exc_type = type (exc )
453
- else :
454
- exc_type , exc = None , None
497
+
455
498
if exc_type and not req_succeeded :
456
499
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
459
509
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
+ )
462
525
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
464
532
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
-
488
533
if span .is_recording () and span .kind == trace .SpanKind .SERVER :
489
534
# Check if low-cardinality route is available as per semantic-conventions
490
535
if req .uri_template :
491
536
span .update_name (f"{ req .method } { req .uri_template } " )
537
+ span .set_attribute (HTTP_ROUTE , req .uri_template )
492
538
else :
493
539
span .update_name (f"{ req .method } " )
494
540
0 commit comments