@@ -97,15 +97,22 @@ def GET(self):
97
97
98
98
.. code-block:: python
99
99
100
+ from wsgiref.types import WSGIEnvironment, StartResponse
101
+ from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
102
+
103
+ def app(environ: WSGIEnvironment, start_response: StartResponse):
104
+ start_response("200 OK", [("Content-Type", "text/plain"), ("Content-Length", "13")])
105
+ return [b"Hello, World!"]
106
+
100
107
def request_hook(span: Span, environ: WSGIEnvironment):
101
108
if span and span.is_recording():
102
109
span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
103
110
104
- def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_headers: List ):
111
+ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_headers: list[tuple[str, str]] ):
105
112
if span and span.is_recording():
106
113
span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
107
114
108
- OpenTelemetryMiddleware(request_hook=request_hook, response_hook=response_hook)
115
+ OpenTelemetryMiddleware(app, request_hook=request_hook, response_hook=response_hook)
109
116
110
117
Capture HTTP request and response headers
111
118
*****************************************
@@ -207,10 +214,12 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
207
214
---
208
215
"""
209
216
217
+ from __future__ import annotations
218
+
210
219
import functools
211
- import typing
212
220
import wsgiref .util as wsgiref_util
213
221
from timeit import default_timer
222
+ from typing import TYPE_CHECKING , Any , Callable , Dict , Iterable , TypeVar , cast
214
223
215
224
from opentelemetry import context , trace
216
225
from opentelemetry .instrumentation ._semconv import (
@@ -240,14 +249,15 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
240
249
)
241
250
from opentelemetry .instrumentation .utils import _start_internal_or_server_span
242
251
from opentelemetry .instrumentation .wsgi .version import __version__
243
- from opentelemetry .metrics import get_meter
252
+ from opentelemetry .metrics import MeterProvider , get_meter
244
253
from opentelemetry .propagators .textmap import Getter
245
254
from opentelemetry .semconv .attributes .error_attributes import ERROR_TYPE
246
255
from opentelemetry .semconv .metrics import MetricInstruments
247
256
from opentelemetry .semconv .metrics .http_metrics import (
248
257
HTTP_SERVER_REQUEST_DURATION ,
249
258
)
250
259
from opentelemetry .semconv .trace import SpanAttributes
260
+ from opentelemetry .trace import TracerProvider
251
261
from opentelemetry .trace .status import Status , StatusCode
252
262
from opentelemetry .util .http import (
253
263
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS ,
@@ -262,15 +272,23 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
262
272
sanitize_method ,
263
273
)
264
274
275
+ if TYPE_CHECKING :
276
+ from wsgiref .types import StartResponse , WSGIApplication , WSGIEnvironment
277
+
278
+
279
+ T = TypeVar ("T" )
280
+ RequestHook = Callable [[trace .Span , "WSGIEnvironment" ], None ]
281
+ ResponseHook = Callable [
282
+ [trace .Span , "WSGIEnvironment" , str , "list[tuple[str, str]]" ], None
283
+ ]
284
+
265
285
_HTTP_VERSION_PREFIX = "HTTP/"
266
286
_CARRIER_KEY_PREFIX = "HTTP_"
267
287
_CARRIER_KEY_PREFIX_LEN = len (_CARRIER_KEY_PREFIX )
268
288
269
289
270
- class WSGIGetter (Getter [dict ]):
271
- def get (
272
- self , carrier : dict , key : str
273
- ) -> typing .Optional [typing .List [str ]]:
290
+ class WSGIGetter (Getter [Dict [str , Any ]]):
291
+ def get (self , carrier : dict [str , Any ], key : str ) -> list [str ] | None :
274
292
"""Getter implementation to retrieve a HTTP header value from the
275
293
PEP3333-conforming WSGI environ
276
294
@@ -287,7 +305,7 @@ def get(
287
305
return [value ]
288
306
return None
289
307
290
- def keys (self , carrier ):
308
+ def keys (self , carrier : dict [ str , Any ] ):
291
309
return [
292
310
key [_CARRIER_KEY_PREFIX_LEN :].lower ().replace ("_" , "-" )
293
311
for key in carrier
@@ -298,26 +316,19 @@ def keys(self, carrier):
298
316
wsgi_getter = WSGIGetter ()
299
317
300
318
301
- def setifnotnone (dic , key , value ):
302
- if value is not None :
303
- dic [key ] = value
304
-
305
-
306
319
# pylint: disable=too-many-branches
307
-
308
-
309
320
def collect_request_attributes (
310
- environ ,
311
- sem_conv_opt_in_mode = _StabilityMode .DEFAULT ,
321
+ environ : WSGIEnvironment ,
322
+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
312
323
):
313
324
"""Collects HTTP request attributes from the PEP3333-conforming
314
325
WSGI environ and returns a dictionary to be used as span creation attributes.
315
326
"""
316
- result = {}
327
+ result : dict [ str , str | None ] = {}
317
328
_set_http_method (
318
329
result ,
319
330
environ .get ("REQUEST_METHOD" , "" ),
320
- sanitize_method (environ .get ("REQUEST_METHOD" , "" )),
331
+ sanitize_method (cast ( str , environ .get ("REQUEST_METHOD" , "" ) )),
321
332
sem_conv_opt_in_mode ,
322
333
)
323
334
# old semconv v1.12.0
@@ -385,7 +396,7 @@ def collect_request_attributes(
385
396
return result
386
397
387
398
388
- def collect_custom_request_headers_attributes (environ ):
399
+ def collect_custom_request_headers_attributes (environ : WSGIEnvironment ):
389
400
"""Returns custom HTTP request headers which are configured by the user
390
401
from the PEP3333-conforming WSGI environ to be used as span creation attributes as described
391
402
in the specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers
@@ -411,7 +422,9 @@ def collect_custom_request_headers_attributes(environ):
411
422
)
412
423
413
424
414
- def collect_custom_response_headers_attributes (response_headers ):
425
+ def collect_custom_response_headers_attributes (
426
+ response_headers : list [tuple [str , str ]],
427
+ ):
415
428
"""Returns custom HTTP response headers which are configured by the user from the
416
429
PEP3333-conforming WSGI environ as described in the specification
417
430
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers
@@ -422,7 +435,7 @@ def collect_custom_response_headers_attributes(response_headers):
422
435
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
423
436
)
424
437
)
425
- response_headers_dict = {}
438
+ response_headers_dict : dict [ str , str ] = {}
426
439
if response_headers :
427
440
for key , val in response_headers :
428
441
key = key .lower ()
@@ -440,7 +453,8 @@ def collect_custom_response_headers_attributes(response_headers):
440
453
)
441
454
442
455
443
- def _parse_status_code (resp_status ):
456
+ # TODO: Used only on the `opentelemetry-instrumentation-pyramid` package - It can be moved there.
457
+ def _parse_status_code (resp_status : str ) -> int | None :
444
458
status_code , _ = resp_status .split (" " , 1 )
445
459
try :
446
460
return int (status_code )
@@ -449,7 +463,7 @@ def _parse_status_code(resp_status):
449
463
450
464
451
465
def _parse_active_request_count_attrs (
452
- req_attrs , sem_conv_opt_in_mode = _StabilityMode .DEFAULT
466
+ req_attrs , sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT
453
467
):
454
468
return _filter_semconv_active_request_count_attr (
455
469
req_attrs ,
@@ -460,7 +474,8 @@ def _parse_active_request_count_attrs(
460
474
461
475
462
476
def _parse_duration_attrs (
463
- req_attrs , sem_conv_opt_in_mode = _StabilityMode .DEFAULT
477
+ req_attrs : dict [str , str | None ],
478
+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
464
479
):
465
480
return _filter_semconv_duration_attrs (
466
481
req_attrs ,
@@ -471,11 +486,11 @@ def _parse_duration_attrs(
471
486
472
487
473
488
def add_response_attributes (
474
- span ,
475
- start_response_status ,
476
- response_headers ,
477
- duration_attrs = None ,
478
- sem_conv_opt_in_mode = _StabilityMode .DEFAULT ,
489
+ span : trace . Span ,
490
+ start_response_status : str ,
491
+ response_headers : list [ tuple [ str , str ]] ,
492
+ duration_attrs : dict [ str , str | None ] | None = None ,
493
+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
479
494
): # pylint: disable=unused-argument
480
495
"""Adds HTTP response attributes to span using the arguments
481
496
passed to a PEP3333-conforming start_response callable.
@@ -497,7 +512,7 @@ def add_response_attributes(
497
512
)
498
513
499
514
500
- def get_default_span_name (environ ) :
515
+ def get_default_span_name (environ : WSGIEnvironment ) -> str :
501
516
"""
502
517
Default span name is the HTTP method and URL path, or just the method.
503
518
https://github.com/open-telemetry/opentelemetry-specification/pull/3165
@@ -508,10 +523,12 @@ def get_default_span_name(environ):
508
523
Returns:
509
524
The span name.
510
525
"""
511
- method = sanitize_method (environ .get ("REQUEST_METHOD" , "" ).strip ())
526
+ method = sanitize_method (
527
+ cast (str , environ .get ("REQUEST_METHOD" , "" )).strip ()
528
+ )
512
529
if method == "_OTHER" :
513
530
return "HTTP"
514
- path = environ .get ("PATH_INFO" , "" ).strip ()
531
+ path = cast ( str , environ .get ("PATH_INFO" , "" ) ).strip ()
515
532
if method and path :
516
533
return f"{ method } { path } "
517
534
return method
@@ -538,11 +555,11 @@ class OpenTelemetryMiddleware:
538
555
539
556
def __init__ (
540
557
self ,
541
- wsgi ,
542
- request_hook = None ,
543
- response_hook = None ,
544
- tracer_provider = None ,
545
- meter_provider = None ,
558
+ wsgi : WSGIApplication ,
559
+ request_hook : RequestHook | None = None ,
560
+ response_hook : ResponseHook | None = None ,
561
+ tracer_provider : TracerProvider | None = None ,
562
+ meter_provider : MeterProvider | None = None ,
546
563
):
547
564
# initialize semantic conventions opt-in if needed
548
565
_OpenTelemetrySemanticConventionStability ._initialize ()
@@ -589,14 +606,19 @@ def __init__(
589
606
590
607
@staticmethod
591
608
def _create_start_response (
592
- span ,
593
- start_response ,
594
- response_hook ,
595
- duration_attrs ,
596
- sem_conv_opt_in_mode ,
609
+ span : trace . Span ,
610
+ start_response : StartResponse ,
611
+ response_hook : Callable [[ str , list [ tuple [ str , str ]]], None ] | None ,
612
+ duration_attrs : dict [ str , str | None ] ,
613
+ sem_conv_opt_in_mode : _StabilityMode ,
597
614
):
598
615
@functools .wraps (start_response )
599
- def _start_response (status , response_headers , * args , ** kwargs ):
616
+ def _start_response (
617
+ status : str ,
618
+ response_headers : list [tuple [str , str ]],
619
+ * args : Any ,
620
+ ** kwargs : Any ,
621
+ ):
600
622
add_response_attributes (
601
623
span ,
602
624
status ,
@@ -617,7 +639,9 @@ def _start_response(status, response_headers, *args, **kwargs):
617
639
return _start_response
618
640
619
641
# pylint: disable=too-many-branches
620
- def __call__ (self , environ , start_response ):
642
+ def __call__ (
643
+ self , environ : WSGIEnvironment , start_response : StartResponse
644
+ ):
621
645
"""The WSGI application
622
646
623
647
Args:
@@ -699,7 +723,9 @@ def __call__(self, environ, start_response):
699
723
# Put this in a subfunction to not delay the call to the wrapped
700
724
# WSGI application (instrumentation should change the application
701
725
# behavior as little as possible).
702
- def _end_span_after_iterating (iterable , span , token ):
726
+ def _end_span_after_iterating (
727
+ iterable : Iterable [T ], span : trace .Span , token : object
728
+ ) -> Iterable [T ]:
703
729
try :
704
730
with trace .use_span (span ):
705
731
yield from iterable
@@ -713,10 +739,8 @@ def _end_span_after_iterating(iterable, span, token):
713
739
714
740
715
741
# TODO: inherit from opentelemetry.instrumentation.propagators.Setter
716
-
717
-
718
742
class ResponsePropagationSetter :
719
- def set (self , carrier , key , value ): # pylint: disable=no-self-use
743
+ def set (self , carrier : list [ tuple [ str , T ]], key : str , value : T ): # pylint: disable=no-self-use
720
744
carrier .append ((key , value ))
721
745
722
746
0 commit comments