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
示例#2
0
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
示例#3
0
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
示例#4
0
    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")