@@ -103,11 +103,14 @@ def client_response_hook(span: Span, message: dict):
103103
104104from opentelemetry import context , trace
105105from opentelemetry .instrumentation .asgi .version import __version__ # noqa
106+ from opentelemetry .instrumentation .propagators import (
107+ get_global_response_propagator ,
108+ )
106109from opentelemetry .instrumentation .utils import http_status_to_status_code
107110from opentelemetry .propagate import extract
108- from opentelemetry .propagators .textmap import Getter
111+ from opentelemetry .propagators .textmap import Getter , Setter
109112from opentelemetry .semconv .trace import SpanAttributes
110- from opentelemetry .trace import Span
113+ from opentelemetry .trace import Span , set_span_in_context
111114from opentelemetry .trace .status import Status , StatusCode
112115from opentelemetry .util .http import remove_url_credentials
113116
@@ -152,6 +155,30 @@ def keys(self, carrier: dict) -> typing.List[str]:
152155asgi_getter = ASGIGetter ()
153156
154157
158+ class ASGISetter (Setter ):
159+ def set (
160+ self , carrier : dict , key : str , value : str
161+ ) -> None : # pylint: disable=no-self-use
162+ """Sets response header values on an ASGI scope according to `the spec <https://asgi.readthedocs.io/en/latest/specs/www.html#response-start-send-event>`_.
163+
164+ Args:
165+ carrier: ASGI scope object
166+ key: response header name to set
167+ value: response header value
168+ Returns:
169+ None
170+ """
171+ headers = carrier .get ("headers" )
172+ if not headers :
173+ headers = []
174+ carrier ["headers" ] = headers
175+
176+ headers .append ([key .lower ().encode (), value .encode ()])
177+
178+
179+ asgi_setter = ASGISetter ()
180+
181+
155182def collect_request_attributes (scope ):
156183 """Collects HTTP request attributes from the ASGI scope and returns a
157184 dictionary to be used as span creation attributes."""
@@ -295,54 +322,84 @@ async def __call__(self, scope, receive, send):
295322 return await self .app (scope , receive , send )
296323
297324 token = context .attach (extract (scope , getter = asgi_getter ))
298- span_name , additional_attributes = self .default_span_details (scope )
325+ server_span_name , additional_attributes = self .default_span_details (
326+ scope
327+ )
299328
300329 try :
301330 with self .tracer .start_as_current_span (
302- span_name ,
331+ server_span_name ,
303332 kind = trace .SpanKind .SERVER ,
304- ) as span :
305- if span .is_recording ():
333+ ) as server_span :
334+ if server_span .is_recording ():
306335 attributes = collect_request_attributes (scope )
307336 attributes .update (additional_attributes )
308337 for key , value in attributes .items ():
309- span .set_attribute (key , value )
338+ server_span .set_attribute (key , value )
310339
311340 if callable (self .server_request_hook ):
312- self .server_request_hook (span , scope )
313-
314- @wraps (receive )
315- async def wrapped_receive ():
316- with self .tracer .start_as_current_span (
317- " " .join ((span_name , scope ["type" ], "receive" ))
318- ) as receive_span :
319- if callable (self .client_request_hook ):
320- self .client_request_hook (receive_span , scope )
321- message = await receive ()
322- if receive_span .is_recording ():
323- if message ["type" ] == "websocket.receive" :
324- set_status_code (receive_span , 200 )
325- receive_span .set_attribute ("type" , message ["type" ])
326- return message
327-
328- @wraps (send )
329- async def wrapped_send (message ):
330- with self .tracer .start_as_current_span (
331- " " .join ((span_name , scope ["type" ], "send" ))
332- ) as send_span :
333- if callable (self .client_response_hook ):
334- self .client_response_hook (send_span , message )
335- if send_span .is_recording ():
336- if message ["type" ] == "http.response.start" :
337- status_code = message ["status" ]
338- set_status_code (span , status_code )
339- set_status_code (send_span , status_code )
340- elif message ["type" ] == "websocket.send" :
341- set_status_code (span , 200 )
342- set_status_code (send_span , 200 )
343- send_span .set_attribute ("type" , message ["type" ])
344- await send (message )
345-
346- await self .app (scope , wrapped_receive , wrapped_send )
341+ self .server_request_hook (server_span , scope )
342+
343+ otel_receive = self ._get_otel_receive (
344+ server_span_name , scope , receive
345+ )
346+
347+ otel_send = self ._get_otel_send (
348+ server_span ,
349+ server_span_name ,
350+ scope ,
351+ send ,
352+ )
353+
354+ await self .app (scope , otel_receive , otel_send )
347355 finally :
348356 context .detach (token )
357+
358+ def _get_otel_receive (self , server_span_name , scope , receive ):
359+ @wraps (receive )
360+ async def otel_receive ():
361+ with self .tracer .start_as_current_span (
362+ " " .join ((server_span_name , scope ["type" ], "receive" ))
363+ ) as receive_span :
364+ if callable (self .client_request_hook ):
365+ self .client_request_hook (receive_span , scope )
366+ message = await receive ()
367+ if receive_span .is_recording ():
368+ if message ["type" ] == "websocket.receive" :
369+ set_status_code (receive_span , 200 )
370+ receive_span .set_attribute ("type" , message ["type" ])
371+ return message
372+
373+ return otel_receive
374+
375+ def _get_otel_send (self , server_span , server_span_name , scope , send ):
376+ @wraps (send )
377+ async def otel_send (message ):
378+ with self .tracer .start_as_current_span (
379+ " " .join ((server_span_name , scope ["type" ], "send" ))
380+ ) as send_span :
381+ if callable (self .client_response_hook ):
382+ self .client_response_hook (send_span , message )
383+ if send_span .is_recording ():
384+ if message ["type" ] == "http.response.start" :
385+ status_code = message ["status" ]
386+ set_status_code (server_span , status_code )
387+ set_status_code (send_span , status_code )
388+ elif message ["type" ] == "websocket.send" :
389+ set_status_code (server_span , 200 )
390+ set_status_code (send_span , 200 )
391+ send_span .set_attribute ("type" , message ["type" ])
392+
393+ propagator = get_global_response_propagator ()
394+ if propagator :
395+ propagator .inject (
396+ message ,
397+ context = set_span_in_context (
398+ server_span , trace .context_api .Context ()
399+ ),
400+ setter = asgi_setter ,
401+ )
402+
403+ await send (message )
404+
405+ return otel_send
0 commit comments