@@ -483,7 +483,7 @@ def get_default_span_details(scope: dict) -> Tuple[str, dict]:
483
483
484
484
485
485
def _collect_target_attribute (
486
- scope : typing .Dict [str , typing .Any ]
486
+ scope : typing .Dict [str , typing .Any ],
487
487
) -> typing .Optional [str ]:
488
488
"""
489
489
Returns the target path as defined by the Semantic Conventions.
@@ -529,6 +529,7 @@ class OpenTelemetryMiddleware:
529
529
the current globally configured one is used.
530
530
meter_provider: The optional meter provider to use. If omitted
531
531
the current globally configured one is used.
532
+ exclude_spans: Optionally exclude HTTP `send` and/or `receive` spans from the trace.
532
533
"""
533
534
534
535
# pylint: disable=too-many-branches
@@ -547,6 +548,7 @@ def __init__(
547
548
http_capture_headers_server_request : list [str ] | None = None ,
548
549
http_capture_headers_server_response : list [str ] | None = None ,
549
550
http_capture_headers_sanitize_fields : list [str ] | None = None ,
551
+ exclude_spans : list [typing .Literal ["receive" , "send" ]] | None = None ,
550
552
):
551
553
# initialize semantic conventions opt-in if needed
552
554
_OpenTelemetrySemanticConventionStability ._initialize ()
@@ -653,6 +655,12 @@ def __init__(
653
655
)
654
656
or []
655
657
)
658
+ self .exclude_receive_span = (
659
+ "receive" in exclude_spans if exclude_spans else False
660
+ )
661
+ self .exclude_send_span = (
662
+ "send" in exclude_spans if exclude_spans else False
663
+ )
656
664
657
665
# pylint: disable=too-many-statements
658
666
async def __call__ (
@@ -796,8 +804,10 @@ async def __call__(
796
804
span .end ()
797
805
798
806
# pylint: enable=too-many-branches
799
-
800
807
def _get_otel_receive (self , server_span_name , scope , receive ):
808
+ if self .exclude_receive_span :
809
+ return receive
810
+
801
811
@wraps (receive )
802
812
async def otel_receive ():
803
813
with self .tracer .start_as_current_span (
@@ -821,6 +831,66 @@ async def otel_receive():
821
831
822
832
return otel_receive
823
833
834
+ def _set_send_span (
835
+ self ,
836
+ server_span_name ,
837
+ scope ,
838
+ send ,
839
+ message ,
840
+ status_code ,
841
+ expecting_trailers ,
842
+ ):
843
+ """Set send span attributes and status code."""
844
+ with self .tracer .start_as_current_span (
845
+ " " .join ((server_span_name , scope ["type" ], "send" ))
846
+ ) as send_span :
847
+ if callable (self .client_response_hook ):
848
+ self .client_response_hook (send_span , scope , message )
849
+
850
+ if send_span .is_recording ():
851
+ if message ["type" ] == "http.response.start" :
852
+ expecting_trailers = message .get ("trailers" , False )
853
+ send_span .set_attribute ("asgi.event.type" , message ["type" ])
854
+
855
+ if status_code :
856
+ set_status_code (
857
+ send_span ,
858
+ status_code ,
859
+ None ,
860
+ self ._sem_conv_opt_in_mode ,
861
+ )
862
+ return expecting_trailers
863
+
864
+ def _set_server_span (
865
+ self , server_span , message , status_code , duration_attrs
866
+ ):
867
+ """Set server span attributes and status code."""
868
+ if (
869
+ server_span .is_recording ()
870
+ and server_span .kind == trace .SpanKind .SERVER
871
+ and "headers" in message
872
+ ):
873
+ custom_response_attributes = (
874
+ collect_custom_headers_attributes (
875
+ message ,
876
+ self .http_capture_headers_sanitize_fields ,
877
+ self .http_capture_headers_server_response ,
878
+ normalise_response_header_name ,
879
+ )
880
+ if self .http_capture_headers_server_response
881
+ else {}
882
+ )
883
+ if len (custom_response_attributes ) > 0 :
884
+ server_span .set_attributes (custom_response_attributes )
885
+
886
+ if status_code :
887
+ set_status_code (
888
+ server_span ,
889
+ status_code ,
890
+ duration_attrs ,
891
+ self ._sem_conv_opt_in_mode ,
892
+ )
893
+
824
894
def _get_otel_send (
825
895
self ,
826
896
server_span ,
@@ -834,74 +904,46 @@ def _get_otel_send(
834
904
@wraps (send )
835
905
async def otel_send (message : dict [str , Any ]):
836
906
nonlocal expecting_trailers
837
- with self .tracer .start_as_current_span (
838
- " " .join ((server_span_name , scope ["type" ], "send" ))
839
- ) as send_span :
840
- if callable (self .client_response_hook ):
841
- self .client_response_hook (send_span , scope , message )
842
907
843
- status_code = None
844
- if message ["type" ] == "http.response.start" :
845
- status_code = message ["status" ]
846
- elif message ["type" ] == "websocket.send" :
847
- status_code = 200
848
-
849
- if send_span .is_recording ():
850
- if message ["type" ] == "http.response.start" :
851
- expecting_trailers = message .get ("trailers" , False )
852
- send_span .set_attribute ("asgi.event.type" , message ["type" ])
853
- if (
854
- server_span .is_recording ()
855
- and server_span .kind == trace .SpanKind .SERVER
856
- and "headers" in message
857
- ):
858
- custom_response_attributes = (
859
- collect_custom_headers_attributes (
860
- message ,
861
- self .http_capture_headers_sanitize_fields ,
862
- self .http_capture_headers_server_response ,
863
- normalise_response_header_name ,
864
- )
865
- if self .http_capture_headers_server_response
866
- else {}
867
- )
868
- if len (custom_response_attributes ) > 0 :
869
- server_span .set_attributes (
870
- custom_response_attributes
871
- )
872
- if status_code :
873
- # We record metrics only once
874
- set_status_code (
875
- server_span ,
876
- status_code ,
877
- duration_attrs ,
878
- self ._sem_conv_opt_in_mode ,
879
- )
880
- set_status_code (
881
- send_span ,
882
- status_code ,
883
- None ,
884
- self ._sem_conv_opt_in_mode ,
885
- )
908
+ status_code = None
909
+ if message ["type" ] == "http.response.start" :
910
+ status_code = message ["status" ]
911
+ elif message ["type" ] == "websocket.send" :
912
+ status_code = 200
886
913
887
- propagator = get_global_response_propagator ()
888
- if propagator :
889
- propagator . inject (
890
- message ,
891
- context = set_span_in_context (
892
- server_span , trace . context_api . Context ()
893
- ) ,
894
- setter = asgi_setter ,
895
- )
914
+ if not self . exclude_send_span :
915
+ expecting_trailers = self . _set_send_span (
916
+ server_span_name ,
917
+ scope ,
918
+ send ,
919
+ message ,
920
+ status_code ,
921
+ expecting_trailers ,
922
+ )
896
923
897
- content_length = asgi_getter .get (message , "content-length" )
898
- if content_length :
899
- try :
900
- self .content_length_header = int (content_length [0 ])
901
- except ValueError :
902
- pass
924
+ self ._set_server_span (
925
+ server_span , message , status_code , duration_attrs
926
+ )
927
+
928
+ propagator = get_global_response_propagator ()
929
+ if propagator :
930
+ propagator .inject (
931
+ message ,
932
+ context = set_span_in_context (
933
+ server_span , trace .context_api .Context ()
934
+ ),
935
+ setter = asgi_setter ,
936
+ )
937
+
938
+ content_length = asgi_getter .get (message , "content-length" )
939
+ if content_length :
940
+ try :
941
+ self .content_length_header = int (content_length [0 ])
942
+ except ValueError :
943
+ pass
944
+
945
+ await send (message )
903
946
904
- await send (message )
905
947
# pylint: disable=too-many-boolean-expressions
906
948
if (
907
949
not expecting_trailers
0 commit comments