diff --git a/CHANGELOG.md b/CHANGELOG.md index a4403d40..9b106a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 1.12.0 [unreleased] +### Features +1. [#161](https://github.com/influxdata/influxdb-client-python/pull/161): Added logging message for retries + ## 1.11.0 [2020-10-02] ### Features diff --git a/influxdb_client/client/exceptions.py b/influxdb_client/client/exceptions.py new file mode 100644 index 00000000..5a1b3c1d --- /dev/null +++ b/influxdb_client/client/exceptions.py @@ -0,0 +1,37 @@ +"""Exceptions utils for InfluxDB.""" + +import logging + +from urllib3 import HTTPResponse + +logger = logging.getLogger(__name__) + + +class InfluxDBError(Exception): + """Raised when a server error occurs.""" + + def __init__(self, response: HTTPResponse): + """Initialize the InfluxDBError handler.""" + self.response = response + self.message = self._get_message(response) + self.retry_after = response.getheader('Retry-After') + super().__init__(self.message) + + def _get_message(self, response): + # Body + if response.data: + import json + try: + return json.loads(response.data)["message"] + except Exception as e: + logging.debug(f"Cannot parse error response to JSON: {response.data}, {e}") + return response.data + + # Header + for header_key in ["X-Platform-Error-Code", "X-Influx-Error", "X-InfluxDb-Error"]: + header_value = response.getheader(header_key) + if header_value is not None: + return header_value + + # Http Status + return response.reason diff --git a/influxdb_client/client/write/retry.py b/influxdb_client/client/write/retry.py index d488ca9a..9c002ddc 100644 --- a/influxdb_client/client/write/retry.py +++ b/influxdb_client/client/write/retry.py @@ -1,10 +1,15 @@ """Implementation for Retry strategy during HTTP requests.""" +import logging from itertools import takewhile from random import random from urllib3 import Retry +from influxdb_client.client.exceptions import InfluxDBError + +logger = logging.getLogger(__name__) + class WritesRetry(Retry): """ @@ -63,5 +68,24 @@ def get_retry_after(self, response): retry_after += self._jitter_delay() return retry_after + def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None): + """Return a new Retry object with incremented retry counters.""" + new_retry = super().increment(method, url, response, error, _pool, _stacktrace) + + if response is not None: + parsed_error = InfluxDBError(response=response) + elif error is not None: + parsed_error = error + else: + parsed_error = f"Failed request to: {url}" + + message = f"The retriable error occurred during request. Reason: '{parsed_error}'." + if isinstance(parsed_error, InfluxDBError): + message += f" Retry in {parsed_error.retry_after}s." + + logger.warning(message) + + return new_retry + def _jitter_delay(self): return self.jitter_interval * random() diff --git a/tests/test_InfluxDBError.py b/tests/test_InfluxDBError.py new file mode 100644 index 00000000..b864c3d0 --- /dev/null +++ b/tests/test_InfluxDBError.py @@ -0,0 +1,48 @@ +import unittest + +from urllib3 import HTTPResponse + +from influxdb_client.client.exceptions import InfluxDBError + + +class TestInfluxDBError(unittest.TestCase): + def test_response(self): + response = HTTPResponse() + self.assertEqual(response, InfluxDBError(response=response).response) + + def test_message(self): + + response = HTTPResponse() + response.headers.add('X-Platform-Error-Code', 'too many requests 1') + self.assertEqual("too many requests 1", str(InfluxDBError(response=response))) + + response = HTTPResponse() + response.headers.add('X-Influx-Error', 'too many requests 2') + self.assertEqual("too many requests 2", str(InfluxDBError(response=response))) + + response = HTTPResponse() + response.headers.add('X-InfluxDb-Error', 'too many requests 3') + self.assertEqual("too many requests 3", str(InfluxDBError(response=response))) + + response = HTTPResponse(body='{"code":"too many requests","message":"org 04014de4ed590000 has exceeded limited_write plan limit"}') + response.headers.add('X-InfluxDb-Error', 'error 3') + self.assertEqual("org 04014de4ed590000 has exceeded limited_write plan limit", str(InfluxDBError(response=response))) + + response = HTTPResponse(body='org 04014de4ed590000 has exceeded limited_write plan limit') + response.headers.add('X-InfluxDb-Error', 'error 3') + self.assertEqual("org 04014de4ed590000 has exceeded limited_write plan limit", str(InfluxDBError(response=response))) + + response = HTTPResponse(reason='too many requests 4') + self.assertEqual("too many requests 4", str(InfluxDBError(response=response))) + + def test_message_get_retry_after(self): + response = HTTPResponse(reason="too many requests") + response.headers.add('Retry-After', '63') + + influx_db_error = InfluxDBError(response=response) + self.assertEqual("too many requests", str(influx_db_error)) + self.assertEqual("63", influx_db_error.retry_after) + + influx_db_error = InfluxDBError(response=HTTPResponse(reason="too many requests")) + self.assertEqual("too many requests", str(influx_db_error)) + self.assertEqual(None, influx_db_error.retry_after) diff --git a/tests/test_WritesRetry.py b/tests/test_WritesRetry.py index 3dec1528..f278c051 100644 --- a/tests/test_WritesRetry.py +++ b/tests/test_WritesRetry.py @@ -138,3 +138,24 @@ def test_is_retry_respect_method(self): retry = WritesRetry(method_whitelist=["POST"]) self.assertFalse(retry.is_retry("GET", 429, False)) + + def test_logging(self): + response = HTTPResponse( + body='{"code":"too many requests","message":"org 04014de4ed590000 has exceeded limited_write plan limit"}') + response.headers.add('Retry-After', '63') + + with self.assertLogs('influxdb_client.client.write.retry', level='WARNING') as cm: + WritesRetry(total=5, backoff_factor=1, max_retry_delay=15) \ + .increment(response=response) \ + .increment(error=Exception("too many requests")) \ + .increment(url='http://localhost:9999') + + self.assertEqual("WARNING:influxdb_client.client.write.retry:The retriable error occurred during request. " + "Reason: 'org 04014de4ed590000 has exceeded limited_write plan limit'. Retry in 63s.", + cm.output[0]) + self.assertEqual("WARNING:influxdb_client.client.write.retry:The retriable error occurred during request. " + "Reason: 'too many requests'.", + cm.output[1]) + self.assertEqual("WARNING:influxdb_client.client.write.retry:The retriable error occurred during request. " + "Reason: 'Failed request to: http://localhost:9999'.", + cm.output[2])