|
1 | 1 | #
|
2 | 2 | # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
3 | 3 | #
|
| 4 | + |
4 | 5 | import logging
|
5 | 6 | import os
|
6 | 7 | import urllib
|
|
44 | 45 | rate_limit_default_backoff_handler,
|
45 | 46 | user_defined_backoff_handler,
|
46 | 47 | )
|
| 48 | +from airbyte_cdk.sources.utils.types import JsonType |
47 | 49 | from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
|
48 | 50 | from airbyte_cdk.utils.constants import ENV_REQUEST_CACHE_PATH
|
49 | 51 | from airbyte_cdk.utils.stream_status_utils import (
|
@@ -335,6 +337,29 @@ def _send(
|
335 | 337 |
|
336 | 338 | return response # type: ignore # will either return a valid response of type requests.Response or raise an exception
|
337 | 339 |
|
| 340 | + def _get_response_body(self, response: requests.Response) -> Optional[JsonType]: |
| 341 | + """ |
| 342 | + Extracts and returns the body of an HTTP response. |
| 343 | +
|
| 344 | + This method attempts to parse the response body as JSON. If the response |
| 345 | + body is not valid JSON, it falls back to decoding the response content |
| 346 | + as a UTF-8 string. If both attempts fail, it returns None. |
| 347 | +
|
| 348 | + Args: |
| 349 | + response (requests.Response): The HTTP response object. |
| 350 | +
|
| 351 | + Returns: |
| 352 | + Optional[JsonType]: The parsed JSON object as a string, the decoded |
| 353 | + response content as a string, or None if both parsing attempts fail. |
| 354 | + """ |
| 355 | + try: |
| 356 | + return str(response.json()) |
| 357 | + except requests.exceptions.JSONDecodeError: |
| 358 | + try: |
| 359 | + return response.content.decode("utf-8") |
| 360 | + except Exception: |
| 361 | + return "The Content of the Response couldn't be decoded." |
| 362 | + |
338 | 363 | def _evict_key(self, prepared_request: requests.PreparedRequest) -> None:
|
339 | 364 | """
|
340 | 365 | Addresses high memory consumption when enabling concurrency in https://github.com/airbytehq/oncall/issues/6821.
|
@@ -377,13 +402,18 @@ def _handle_error_resolution(
|
377 | 402 |
|
378 | 403 | if error_resolution.response_action == ResponseAction.FAIL:
|
379 | 404 | if response is not None:
|
380 |
| - error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message '{response.content.decode('utf-8', errors='replace')}'" |
| 405 | + filtered_response_message = filter_secrets( |
| 406 | + f"Request (body): '{str(request.body)}'. Response (body): '{self._get_response_body(response)}'. Response (headers): '{response.headers}'." |
| 407 | + ) |
| 408 | + error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message: '{self._error_message_parser.parse_response_error_message(response)}'. {filtered_response_message}" |
381 | 409 | else:
|
382 | 410 | error_message = (
|
383 | 411 | f"'{request.method}' request to '{request.url}' failed with exception: '{exc}'"
|
384 | 412 | )
|
385 | 413 |
|
386 |
| - self._logger.warning(filter_secrets(error_message)) |
| 414 | + # ensure the exception message is emitted before raised |
| 415 | + self._logger.error(error_message) |
| 416 | + |
387 | 417 | raise MessageRepresentationAirbyteTracedErrors(
|
388 | 418 | internal_message=error_message,
|
389 | 419 | message=error_resolution.error_message or error_message,
|
|
0 commit comments