def client_response_hook(self, span, resp_data): """used to capture the response data this function is called twice, once during each resp_phase""" resp_phase = resp_data['type'] if resp_phase == "http.response.start": status_code = resp_data["status"] set_status_code(span, status_code) headers = dict(Headers(raw=resp_data['headers'])) should_capture_body = self._capture_headers( self._process_response_headers, self.HTTP_RESPONSE_HEADER_PREFIX, span, headers, self._process_response_body) span.set_attribute('hypertrace.capture', should_capture_body) elif resp_phase == 'http.response.body': should_capture = span.attributes.get('hypertrace.capture') if should_capture: body_data = resp_data['body'] body_str = None if isinstance(body_data, bytes): body_str = body_data.decode('UTF8', 'backslashreplace') else: body_str = body_data resp_body_str = self.grab_first_n_bytes(body_str) span.set_attribute('http.response.body', resp_body_str)
async def wrapped_receive(): with self.tracer.start_as_current_span( span_name + " asgi." + scope["type"] + ".receive") as receive_span: message = await receive() if receive_span.is_recording(): if message["type"] == "websocket.receive": set_status_code(receive_span, 200) receive_span.set_attribute("type", message["type"]) return message
async def wrapped_receive(): with self.tracer.start_as_current_span(" ".join( (span_name, scope["type"], "receive"))) as receive_span: if callable(self.client_request_hook): self.client_request_hook(receive_span, scope) message = await receive() if receive_span.is_recording(): if message["type"] == "websocket.receive": set_status_code(receive_span, 200) receive_span.set_attribute("type", message["type"]) return message
async def wrapped_send(message): send_span = span if callable(self.client_response_hook): self.client_response_hook(send_span, message) if send_span.is_recording(): if message["type"] == "http.response.start": status_code = message["status"] set_status_code(span, status_code) elif message["type"] == "websocket.send": set_status_code(span, 200) elif message["type"] == 'http.response.body': pass await send(message)
def process_response(self, request, response): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return response is_asgi_request = _is_asgi_request(request) if not _is_asgi_supported and is_asgi_request: return response activation = request.META.pop(self._environ_activation_key, None) span = request.META.pop(self._environ_span_key, None) if activation and span: if is_asgi_request: set_status_code(span, response.status_code) else: add_response_attributes( span, f"{response.status_code} {response.reason_phrase}", response, ) propagator = get_global_response_propagator() if propagator: propagator.inject(response) # record any exceptions raised while processing the request exception = request.META.pop(self._environ_exception_key, None) if _DjangoMiddleware._otel_response_hook: _DjangoMiddleware._otel_response_hook( # pylint: disable=not-callable span, request, response ) if exception: activation.__exit__( type(exception), exception, getattr(exception, "__traceback__", None), ) else: activation.__exit__(None, None, None) if request.META.get(self._environ_token, None) is not None: detach(request.META.get(self._environ_token)) request.META.pop(self._environ_token) return response
async def wrapped_send(message): with self.tracer.start_as_current_span( span_name + " asgi." + scope["type"] + ".send") as send_span: if send_span.is_recording(): if message["type"] == "http.response.start": status_code = message["status"] set_status_code(send_span, status_code) set_status_code(span, status_code) elif message["type"] == "websocket.send": set_status_code(send_span, 200) set_status_code(span, 200) send_span.set_attribute("type", message["type"]) await send(message)
async def replaced_ot_middleware_call(self, scope, receive, send): # pylint:disable=R0914 """The ASGI application Args: scope: A ASGI environment. receive: An awaitable callable yielding dictionaries send: An awaitable callable taking a single dictionary as argument. """ if scope["type"] not in ("http", "websocket"): return await self.app(scope, receive, send) _, _, url = get_host_port_url_tuple(scope) if self.excluded_urls and self.excluded_urls.url_disabled(url): return await self.app(scope, receive, send) token = context.attach(extract(scope, getter=asgi_getter)) span_name, additional_attributes = self.default_span_details(scope) request = Request(scope, receive=receive) body = await request.body() try: with self.tracer.start_as_current_span( span_name, kind=trace.SpanKind.SERVER, ) as span: if span.is_recording(): attributes = collect_request_attributes(scope) attributes.update(additional_attributes) for key, value in attributes.items(): span.set_attribute(key, value) should_forward_to_handler = True if callable(self.server_request_hook): should_forward_to_handler = self.server_request_hook( span, scope, body) @wraps(receive) async def wrapped_receive(): with self.tracer.start_as_current_span(" ".join( (span_name, scope["type"], "receive"))) as receive_span: if callable(self.client_request_hook): self.client_request_hook(receive_span, scope) message = await receive() if receive_span.is_recording(): if message["type"] == "websocket.receive": set_status_code(receive_span, 200) receive_span.set_attribute("type", message["type"]) return message @wraps(send) async def wrapped_send(message): send_span = span if callable(self.client_response_hook): self.client_response_hook(send_span, message) if send_span.is_recording(): if message["type"] == "http.response.start": status_code = message["status"] set_status_code(span, status_code) elif message["type"] == "websocket.send": set_status_code(span, 200) elif message["type"] == 'http.response.body': pass await send(message) if should_forward_to_handler: await self.app(scope, wrapped_receive, wrapped_send) else: set_status_code(span, 403) raise HypertraceException(status_code=403) finally: context.detach(token)
def test_response_attributes_invalid_status_code(self): otel_asgi.set_status_code(self.span, "Invalid Status Code") self.assertEqual(self.span.set_status.call_count, 1)
def test_response_attributes(self): otel_asgi.set_status_code(self.span, 404) expected = (mock.call(SpanAttributes.HTTP_STATUS_CODE, 404), ) self.assertEqual(self.span.set_attribute.call_count, 1) self.assertEqual(self.span.set_attribute.call_count, 1) self.span.set_attribute.assert_has_calls(expected, any_order=True)
def process_response(self, request, response): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return response is_asgi_request = _is_asgi_request(request) if not _is_asgi_supported and is_asgi_request: return response activation = request.META.pop(self._environ_activation_key, None) span = request.META.pop(self._environ_span_key, None) active_requests_count_attrs = request.META.pop( self._environ_active_request_attr_key, None ) duration_attrs = request.META.pop( self._environ_duration_attr_key, None ) if duration_attrs: duration_attrs[ SpanAttributes.HTTP_STATUS_CODE ] = response.status_code request_start_time = request.META.pop(self._environ_timer_key, None) if activation and span: if is_asgi_request: set_status_code(span, response.status_code) if span.is_recording() and span.kind == SpanKind.SERVER: custom_headers = {} for key, value in response.items(): asgi_setter.set(custom_headers, key, value) custom_res_attributes = ( asgi_collect_custom_response_attributes(custom_headers) ) for key, value in custom_res_attributes.items(): span.set_attribute(key, value) else: add_response_attributes( span, f"{response.status_code} {response.reason_phrase}", response.items(), ) if span.is_recording() and span.kind == SpanKind.SERVER: custom_attributes = ( wsgi_collect_custom_response_headers_attributes( response.items() ) ) if len(custom_attributes) > 0: span.set_attributes(custom_attributes) propagator = get_global_response_propagator() if propagator: propagator.inject(response) # record any exceptions raised while processing the request exception = request.META.pop(self._environ_exception_key, None) if _DjangoMiddleware._otel_response_hook: _DjangoMiddleware._otel_response_hook( # pylint: disable=not-callable span, request, response ) if exception: activation.__exit__( type(exception), exception, getattr(exception, "__traceback__", None), ) else: activation.__exit__(None, None, None) if request_start_time is not None: duration = max( round((default_timer() - request_start_time) * 1000), 0 ) self._duration_histogram.record(duration, duration_attrs) self._active_request_counter.add(-1, active_requests_count_attrs) if request.META.get(self._environ_token, None) is not None: detach(request.META.get(self._environ_token)) request.META.pop(self._environ_token) return response