@@ -103,11 +103,14 @@ def client_response_hook(span: Span, message: dict):
103
103
104
104
from opentelemetry import context , trace
105
105
from opentelemetry .instrumentation .asgi .version import __version__ # noqa
106
+ from opentelemetry .instrumentation .propagators import (
107
+ get_global_response_propagator ,
108
+ )
106
109
from opentelemetry .instrumentation .utils import http_status_to_status_code
107
110
from opentelemetry .propagate import extract
108
- from opentelemetry .propagators .textmap import Getter
111
+ from opentelemetry .propagators .textmap import Getter , Setter
109
112
from opentelemetry .semconv .trace import SpanAttributes
110
- from opentelemetry .trace import Span
113
+ from opentelemetry .trace import Span , set_span_in_context
111
114
from opentelemetry .trace .status import Status , StatusCode
112
115
from opentelemetry .util .http import remove_url_credentials
113
116
@@ -152,6 +155,30 @@ def keys(self, carrier: dict) -> typing.List[str]:
152
155
asgi_getter = ASGIGetter ()
153
156
154
157
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
+
155
182
def collect_request_attributes (scope ):
156
183
"""Collects HTTP request attributes from the ASGI scope and returns a
157
184
dictionary to be used as span creation attributes."""
@@ -295,54 +322,84 @@ async def __call__(self, scope, receive, send):
295
322
return await self .app (scope , receive , send )
296
323
297
324
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
+ )
299
328
300
329
try :
301
330
with self .tracer .start_as_current_span (
302
- span_name ,
331
+ server_span_name ,
303
332
kind = trace .SpanKind .SERVER ,
304
- ) as span :
305
- if span .is_recording ():
333
+ ) as server_span :
334
+ if server_span .is_recording ():
306
335
attributes = collect_request_attributes (scope )
307
336
attributes .update (additional_attributes )
308
337
for key , value in attributes .items ():
309
- span .set_attribute (key , value )
338
+ server_span .set_attribute (key , value )
310
339
311
340
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 )
347
355
finally :
348
356
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