def test_request_header( uuid4_mock, app, extra_config, extra_req_headers, expected_trace_id, expect_uuid_call, expected_span_id, expected_parent_span_id, expected_onwards_req_headers, expected_resp_headers, # unused here expected_dm_request_id_header_final_value, ): app.config.update(extra_config) request_id_init_app(app) assert app.config.get("DM_REQUEST_ID_HEADER") == expected_dm_request_id_header_final_value uuid4_mock.return_value = mock.Mock(hex=_GENERATED_TRACE_VALUE) with app.test_request_context(headers=extra_req_headers): assert request.request_id == request.trace_id == expected_trace_id assert request.span_id == expected_span_id assert request.parent_span_id == expected_parent_span_id assert request.get_onwards_request_headers() == expected_onwards_req_headers assert app.config.get("DM_REQUEST_ID_HEADER") == expected_dm_request_id_header_final_value assert uuid4_mock.called is expect_uuid_call
def test_request_header_zero_padded( spanid_random_mock, traceid_random_mock, app, ): request_id_init_app(app) traceid_random_mock.randrange.side_effect = assert_args_and_return( 0xbeef, 1 << 128) spanid_random_mock.randrange.side_effect = assert_args_and_return( 0xa, 1 << 64) with app.test_request_context(): assert request.request_id == request.trace_id == "0000000000000000000000000000beef" assert request.span_id is None assert request.get_onwards_request_headers() == { "DM-Request-ID": "0000000000000000000000000000beef", "X-B3-TraceId": "0000000000000000000000000000beef", "X-B3-SpanId": "000000000000000a", } assert request.get_extra_log_context() == AnySupersetOf({ 'parent_span_id': None, 'span_id': None, 'trace_id': '0000000000000000000000000000beef', }) assert traceid_random_mock.randrange.called is True assert spanid_random_mock.randrange.called is True
def test_request_header( spanid_random_mock, traceid_random_mock, app, extra_config, extra_req_headers, expected_trace_id, expect_trace_random_call, expected_span_id, expected_parent_span_id, expected_is_sampled, expected_debug_flag, expected_onwards_req_headers, expected_resp_headers, # unused here expected_dm_request_id_header_final_value, ): app.config.update(extra_config) request_id_init_app(app) assert app.config.get( "DM_REQUEST_ID_HEADER") == expected_dm_request_id_header_final_value traceid_random_mock.randrange.side_effect = assert_args_and_return( _GENERATED_TRACE_VALUE, 1 << 128) spanid_random_mock.randrange.side_effect = assert_args_and_return( _GENERATED_SPAN_VALUE, 1 << 64) with app.test_request_context(headers=extra_req_headers): assert request.request_id == request.trace_id == expected_trace_id assert request.span_id == expected_span_id assert request.parent_span_id == expected_parent_span_id assert request.is_sampled == expected_is_sampled assert request.debug_flag == expected_debug_flag assert request.get_onwards_request_headers( ) == expected_onwards_req_headers assert app.config.get("DM_REQUEST_ID_HEADER" ) == expected_dm_request_id_header_final_value assert request.get_extra_log_context() == { "trace_id": expected_trace_id, "span_id": expected_span_id, "parent_span_id": expected_parent_span_id, "is_sampled": "1" if expected_is_sampled else "0", "debug_flag": "1" if expected_debug_flag else "0", } assert traceid_random_mock.randrange.called is expect_trace_random_call assert spanid_random_mock.randrange.called is True
def _request(self, method, url, data=None, params=None): if not self._enabled: return None url = self._build_url(url, params) headers = { "Content-type": "application/json", "Authorization": "Bearer {}".format(self._auth_token), "User-agent": "DM-API-Client/{}".format(__version__), } if has_request_context(): if callable(getattr(request, "get_onwards_request_headers", None)): headers.update(request.get_onwards_request_headers()) elif getattr(request, "request_id", None) and current_app.config.get("DM_REQUEST_ID_HEADER"): # support old .request_id attr for compatibility headers[current_app.config["DM_REQUEST_ID_HEADER"]] = request.request_id # not using CaseInsensitiveDict as our header dict initially as technically .update()'s behaviour is undefined # for it, but past a certain point we want to be able to know we've resolved what our final header value is # going to be for any certain header name ci_headers = requests.structures.CaseInsensitiveDict(headers) # just in case anyone misses the point and thinks adding anything more to `headers` will do anything beyond here del headers # determine our final outgoing span id - find the first of DM_SPAN_ID_HEADERS which is set to something truthy child_span_id = next( ( ci_headers[header_name] for header_name in (current_app.config.get("DM_SPAN_ID_HEADERS") or ()) if ci_headers.get(header_name) ), None, ) if has_request_context() else None common_log_extra = { **({"childSpanId": child_span_id} if child_span_id is not None else {}), } logger.log( logging.DEBUG, "API request {method} {url}", extra={ **common_log_extra, 'method': method, 'url': url, }, ) start_time = time.perf_counter() try: response = self._requests_retry_session().request( method, url, headers=ci_headers, json=data, timeout=self._timeout ) response.raise_for_status() except requests.RequestException as e: api_error = HTTPError.create(e) elapsed_time = time.perf_counter() - start_time logger.log( logging.INFO if api_error.status_code == 404 else logging.WARNING, "API {api_method} request on {api_url} failed with {api_status} '{api_error}'", extra={ **common_log_extra, 'api_method': method, 'api_url': url, 'api_status': api_error.status_code, 'api_error': '{} raised {}'.format(api_error.message, str(e)), 'api_time': elapsed_time, }, ) raise api_error else: elapsed_time = time.perf_counter() - start_time logger.log( logging.INFO, "API {api_method} request on {api_url} finished in {api_time}", extra={ **common_log_extra, 'api_method': method, 'api_url': url, 'api_status': response.status_code, 'api_time': elapsed_time, }, ) try: return response.json() except ValueError: raise InvalidResponse(response, message="No JSON object could be decoded")