From ec5a1d873e71cab9e7b6659d513fd920896a1504 Mon Sep 17 00:00:00 2001 From: bboger Date: Tue, 15 Feb 2022 10:06:25 +0100 Subject: [PATCH 01/10] feat: add InfluxLoggingHandler with tests. --- influxdb_client/__init__.py | 1 + influxdb_client/client/loggingHandler.py | 47 ++++++++ tests/test_loggingHandler.py | 137 +++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 influxdb_client/client/loggingHandler.py create mode 100644 tests/test_loggingHandler.py diff --git a/influxdb_client/__init__.py b/influxdb_client/__init__.py index 90f2715b..7ce0aca8 100644 --- a/influxdb_client/__init__.py +++ b/influxdb_client/__init__.py @@ -383,6 +383,7 @@ from influxdb_client.client.users_api import UsersApi from influxdb_client.client.write_api import WriteApi, WriteOptions from influxdb_client.client.influxdb_client import InfluxDBClient +from influxdb_client.client.loggingHandler import InfluxLoggingHandler from influxdb_client.client.write.point import Point from influxdb_client.version import CLIENT_VERSION diff --git a/influxdb_client/client/loggingHandler.py b/influxdb_client/client/loggingHandler.py new file mode 100644 index 00000000..8d9d32d3 --- /dev/null +++ b/influxdb_client/client/loggingHandler.py @@ -0,0 +1,47 @@ +""" +Use the influxdb_client together with python native logging +""" +import logging + +from influxdb_client import InfluxDBClient + + +class InfluxLoggingHandler(logging.Handler): + DEFAULT_LOG_RECORD_KEYS = logging.makeLogRecord({}).__dict__.keys() + + def __init__(self, *, url, token, org, bucket, client_args=None, write_api_args=None): + super().__init__() + + self.bucket = bucket + + client_args = {} if client_args is None else client_args + self.client = InfluxDBClient(url=url, token=token, org=org, **client_args) + + write_api_args = {} if write_api_args is None else write_api_args + self.write_api = self.client.write_api(**write_api_args) + + def __del__(self): + self.close() + + def close(self) -> None: + self.write_api.close() + self.client.close() + super().close() + + def emit(self, record: logging.LogRecord) -> None: + """ Emit a record via the influxDB WriteApi """ + try: + extra = self._get_extra_values(record) + return self.write_api.write(record=record.msg, **extra) + except (KeyboardInterrupt, SystemExit): + raise + except (Exception,): + self.handleError(record) + + def _get_extra_values(self, record: logging.LogRecord) -> dict: + """extracts all items from the record that were injected by logging.debug(msg, extra={key: value, ...})""" + extra = {key: value + for key, value in record.__dict__.items() if key not in self.DEFAULT_LOG_RECORD_KEYS} + if 'bucket' not in extra.keys(): + extra['bucket'] = self.bucket + return extra diff --git a/tests/test_loggingHandler.py b/tests/test_loggingHandler.py new file mode 100644 index 00000000..ac6845fe --- /dev/null +++ b/tests/test_loggingHandler.py @@ -0,0 +1,137 @@ +import logging +import unittest +import unittest.mock + +import urllib3 + +from influxdb_client import InfluxLoggingHandler, InfluxDBClient, WriteApi, WritePrecision, Point + + +class LoggingBaseTestCase(unittest.TestCase): + fake_line_record = "tag,field=value 123456" + URL_TOKEN_ORG = { + 'url': 'http://example.com', + 'token': 'my-token', + 'org': 'my-org', + } + BUCKET = 'my-bucket' + + def setUp(self) -> None: + self.mock_InfluxDBClient = unittest.mock.patch("influxdb_client.client.loggingHandler.InfluxDBClient").start() + self.mock_client = unittest.mock.MagicMock(spec=InfluxDBClient) + self.mock_write_api = unittest.mock.MagicMock(spec=WriteApi) + self.mock_client.write_api.return_value = self.mock_write_api + self.mock_InfluxDBClient.return_value = self.mock_client + + def gen_handler_and_logger(self): + self.handler = InfluxLoggingHandler(**self.URL_TOKEN_ORG, bucket=self.BUCKET) + self.handler.setLevel(logging.DEBUG) + + self.logger = logging.getLogger("test-logger") + self.logger.setLevel(logging.DEBUG) + + def tearDown(self) -> None: + unittest.mock.patch.stopall() + + +class TestHandlerCreation(LoggingBaseTestCase): + def test_can_create_handler(self): + self.handler = InfluxLoggingHandler(**self.URL_TOKEN_ORG, bucket=self.BUCKET) + self.mock_InfluxDBClient.assert_called_once_with(**self.URL_TOKEN_ORG) + self.assertEqual(self.BUCKET, self.handler.bucket) + self.mock_client.write_api.assert_called_once_with() + + def test_can_create_handler_with_optional_args_for_client(self): + self.handler = InfluxLoggingHandler(**self.URL_TOKEN_ORG, bucket=self.BUCKET, + client_args={'arg2': 2.90, 'optArg': 'whot'}) + self.mock_InfluxDBClient.assert_called_once_with(**self.URL_TOKEN_ORG, arg2=2.90, optArg="whot") + self.mock_client.write_api.assert_called_once_with() + + def test_can_create_handler_with_args_for_write_api(self): + self.handler = InfluxLoggingHandler(**self.URL_TOKEN_ORG, bucket=self.BUCKET, + client_args={'arg2': 2.90, 'optArg': 'whot'}, + write_api_args={'foo': 'bar'}) + self.mock_InfluxDBClient.assert_called_once_with(**self.URL_TOKEN_ORG, arg2=2.90, optArg="whot") + self.mock_client.write_api.assert_called_once_with(foo='bar') + + +class CreatedHandlerTestCase(LoggingBaseTestCase): + def setUp(self) -> None: + super().setUp() + self.gen_handler_and_logger() + + +class LoggingSetupAndTearDown(CreatedHandlerTestCase): + def test_is_handler(self): + self.assertIsInstance(self.handler, logging.Handler) + + def test_set_up_client(self): + self.mock_InfluxDBClient.assert_called_once() + + def test_closes_connections_on_close(self): + self.handler.close() + self.mock_write_api.close.assert_called_once() + self.mock_client.close.assert_called_once() + + def test_handler_can_be_attached_to_logger(self): + self.logger.addHandler(self.handler) + self.assertTrue(self.logger.hasHandlers()) + self.assertTrue(self.handler in self.logger.handlers) + + +class LoggingWithAttachedHandler(CreatedHandlerTestCase): + + def setUp(self) -> None: + super().setUp() + self.logger.addHandler(self.handler) + + +class LoggingHandlerTest(LoggingWithAttachedHandler): + + def test_can_log_str(self): + self.logger.debug(self.fake_line_record) + self.mock_write_api.write.assert_called_once_with(bucket="my-bucket", record=self.fake_line_record) + + def test_can_log_points(self): + point = Point("measurement").field("field_name", "field_value").time(333, WritePrecision.NS) + self.logger.debug(point) + self.mock_write_api.write.assert_called_once_with(bucket="my-bucket", record=point) + + def test_catches_urllib_exceptions(self): + self.mock_write_api.write.side_effect = urllib3.exceptions.HTTPError() + try: + with unittest.mock.patch("logging.sys.stderr") as _: + # Handler writes logging errors to stderr. Don't display it in the test output. + self.logger.debug(self.fake_line_record) + finally: + self.mock_write_api.write.side_effect = None + + def test_raises_on_exit(self): + try: + self.mock_write_api.write.side_effect = KeyboardInterrupt() + self.assertRaises(KeyboardInterrupt, self.logger.debug, self.fake_line_record) + self.mock_write_api.write.side_effect = SystemExit() + self.assertRaises(SystemExit, self.logger.debug, self.fake_line_record) + finally: + self.mock_write_api.write.side_effect = None + + def test_can_set_bucket(self): + self.handler.bucket = "new-bucket" + self.logger.debug(self.fake_line_record) + self.mock_write_api.write.assert_called_once_with(bucket="new-bucket", record=self.fake_line_record) + + def test_can_pass_bucket_on_log(self): + self.logger.debug(self.fake_line_record, extra={'bucket': "other-bucket"}) + self.mock_write_api.write.assert_called_once_with(bucket="other-bucket", record=self.fake_line_record) + + def test_can_pass_optional_params_on_log(self): + self.logger.debug(self.fake_line_record, extra={'org': "other-org", 'write_precision': WritePrecision.S, + "arg3": 3, "arg2": "two"}) + self.mock_write_api.write.assert_called_once_with(bucket="my-bucket", org='other-org', + record=self.fake_line_record, + write_precision=WritePrecision.S, + arg3=3, arg2="two") + + +if __name__ == "__main__": + unittest.main() From 7eb513773bc4f057707cf79a509e9967649aba78 Mon Sep 17 00:00:00 2001 From: bboger Date: Wed, 16 Feb 2022 09:43:06 +0100 Subject: [PATCH 02/10] chore: add pydocstyle docs --- influxdb_client/client/loggingHandler.py | 27 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/influxdb_client/client/loggingHandler.py b/influxdb_client/client/loggingHandler.py index 8d9d32d3..d543af59 100644 --- a/influxdb_client/client/loggingHandler.py +++ b/influxdb_client/client/loggingHandler.py @@ -1,15 +1,26 @@ -""" -Use the influxdb_client together with python native logging -""" +"""Use the influxdb_client with python native logging.""" import logging from influxdb_client import InfluxDBClient class InfluxLoggingHandler(logging.Handler): + """ + InfluxLoggingHandler instances dispatch logging events to influx. + + There is no need to set a Formater. + The raw input will be passed on to the influx write api. + """ + DEFAULT_LOG_RECORD_KEYS = logging.makeLogRecord({}).__dict__.keys() def __init__(self, *, url, token, org, bucket, client_args=None, write_api_args=None): + """ + Initialize defaults. + + The arguments `client_args` and `write_api_args` can be dicts of kwargs. + They are passed on to the InfluxDBClient and write_api calls respectively. + """ super().__init__() self.bucket = bucket @@ -21,15 +32,17 @@ def __init__(self, *, url, token, org, bucket, client_args=None, write_api_args= self.write_api = self.client.write_api(**write_api_args) def __del__(self): + """Make sure all resources are closed.""" self.close() def close(self) -> None: + """Close the write_api, client and logger.""" self.write_api.close() self.client.close() super().close() def emit(self, record: logging.LogRecord) -> None: - """ Emit a record via the influxDB WriteApi """ + """Emit a record via the influxDB WriteApi.""" try: extra = self._get_extra_values(record) return self.write_api.write(record=record.msg, **extra) @@ -39,7 +52,11 @@ def emit(self, record: logging.LogRecord) -> None: self.handleError(record) def _get_extra_values(self, record: logging.LogRecord) -> dict: - """extracts all items from the record that were injected by logging.debug(msg, extra={key: value, ...})""" + """ + Extract all items from the record that were injected via extra. + + Example: `logging.debug(msg, extra={key: value, ...})`. + """ extra = {key: value for key, value in record.__dict__.items() if key not in self.DEFAULT_LOG_RECORD_KEYS} if 'bucket' not in extra.keys(): From 469ec88d15341f55d0ae0bc2077c8e1ccbbbaf75 Mon Sep 17 00:00:00 2001 From: bboger Date: Wed, 16 Feb 2022 10:00:15 +0100 Subject: [PATCH 03/10] docs: add InfluxLoggingHandler to changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c9a954..0286bded 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,9 @@ This release introduces a support for new version of InfluxDB OSS API definition 1. [#408](https://github.com/influxdata/influxdb-client-python/pull/408): Improve error message when the client cannot find organization by name 1. [#407](https://github.com/influxdata/influxdb-client-python/pull/407): Use `pandas.concat()` instead of deprecated `DataFrame.append()` [DataFrame] +### Features +1. [#405](https://github.com/influxdata/influxdb-client-python/pull/405): Add `InfluxLoggingHandler`. A handler to use the influx client in native python logging. + ## 1.25.0 [2022-01-20] ### Features From 9cf4b76b52ac6d3f23480af2b87de48712c9c681 Mon Sep 17 00:00:00 2001 From: bboger Date: Wed, 16 Feb 2022 10:47:33 +0100 Subject: [PATCH 04/10] docs: add a logging handler example --- examples/logging_handler.py | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/logging_handler.py diff --git a/examples/logging_handler.py b/examples/logging_handler.py new file mode 100644 index 00000000..ef6ab859 --- /dev/null +++ b/examples/logging_handler.py @@ -0,0 +1,47 @@ +""" +Show the usage of influx with python native logging. + +This is useful if you +* want to log to influx and a local file. +* want to set up influx logging in a project without specifying it in submodules +""" +import datetime +import logging +import time + +from influxdb_client import InfluxLoggingHandler, WritePrecision, Point + +DATA_LOGGER_NAME = '…' + + +def setup_logger(): + """ + Set up data logger with the influx logging handler. + + This can happen in your core module. + """ + influx_logging_handler = InfluxLoggingHandler(url="…", token="…", org="…", bucket="…", + client_args={'arg1': '…'}, + write_api_args={'arg': '…'}) + influx_logging_handler.setLevel(logging.DEBUG) + + data_logger = logging.getLogger(DATA_LOGGER_NAME) + data_logger.setLevel(logging.DEBUG) + data_logger.addHandler(influx_logging_handler) + # feel free to add other handlers here. + # if you find yourself writing filters e.g. to only log points to influx, think about adding a PR :) + + +def use_logger(): + """Use the logger. This can happen in any submodule.""" + # `data_logger` will have the influx_logging_handler attached if setup_logger was called somewhere. + data_logger = logging.getLogger(DATA_LOGGER_NAME) + # write a line yourself + data_logger.debug(f"my-measurement,host=host1 temperature=25.3 {int(time.time() * 1e9)}") + # or make use of the influxdb helpers like Point + data_logger.debug( + Point('my-measurement') + .tag('host', 'host1') + .field('temperature', 25.3) + .time(datetime.datetime.utcnow(), WritePrecision.MS) + ) From 762fd6eb572af337c9dabc2d7a5742a826dfe528 Mon Sep 17 00:00:00 2001 From: bboger Date: Thu, 17 Feb 2022 10:36:35 +0100 Subject: [PATCH 05/10] feat: add modifiable __str__ to Point. --- influxdb_client/client/write/point.py | 22 ++++++++++-- tests/test_point.py | 52 +++++++++++++++++---------- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/influxdb_client/client/write/point.py b/influxdb_client/client/write/point.py index c9fcbfbc..9f0dda24 100644 --- a/influxdb_client/client/write/point.py +++ b/influxdb_client/client/write/point.py @@ -1,6 +1,5 @@ """Point data structure to represent LineProtocol.""" - import math from builtins import int from datetime import datetime, timedelta @@ -54,6 +53,8 @@ class Point(object): Ref: http://bit.ly/influxdata-point """ + __str___rep = None + @staticmethod def measurement(measurement): """Create a new Point with specified measurement name.""" @@ -146,7 +147,6 @@ def __init__(self, measurement_name): self._name = measurement_name self._time = None self._write_precision = DEFAULT_WRITE_PRECISION - pass def time(self, time, write_precision=DEFAULT_WRITE_PRECISION): """ @@ -195,6 +195,24 @@ def write_precision(self): """Get precision.""" return self._write_precision + @classmethod + def set_str_rep(cls, rep_function): + """Set the string representation for all Points.""" + cls.__str___rep = rep_function + + def __str__(self): + """ + Create string representation of this Point. + + Can be set via `Point.set_str_rep`. Defaults to `to_line_protocol` + Example: + .. code-block:: python + Point.set_str_rep(lambda p: f'{p._name} - {p._tags} - {p._fields} - {p._time}') + """ + if self.__str___rep is None: + return self.to_line_protocol() + return self.__str___rep() + def _append_tags(tags): _return = [] diff --git a/tests/test_point.py b/tests/test_point.py index 53fd0cc5..b599a844 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -11,6 +11,18 @@ class PointTest(unittest.TestCase): + def test_ToStr(self): + point = Point.measurement("h2o").tag("location", "europe").field("level", 2.2) + expected_str = "h2o,location=europe level=2.2" + self.assertEqual(expected_str, str(point)) + + def my_str_rep(p: Point) -> str: + return f'{p._name} - {p._tags} - {p._fields} - {p._time}' + + Point.set_str_rep(my_str_rep) + + self.assertEqual(my_str_rep(point), str(point)) + def test_MeasurementEscape(self): point = Point.measurement("h2 o").tag("location", "europe").tag("", "warn").field("level", 2) self.assertEqual(point.to_line_protocol(), "h2\\ o,location=europe level=2i") @@ -36,17 +48,17 @@ def test_TagEmptyValue(self): self.assertEqual("h2o,location=europe level=2i", point.to_line_protocol()) def test_TagEscapingKeyAndValue(self): - point = Point.measurement("h\n2\ro\t_data") \ .tag("new\nline", "new\nline") \ .tag("carriage\rreturn", "carriage\nreturn") \ .tag("t\tab", "t\tab") \ .field("level", 2) - self.assertEqual("h\\n2\\ro\\t_data,carriage\\rreturn=carriage\\nreturn,new\\nline=new\\nline,t\\tab=t\\tab level=2i", point.to_line_protocol()) + self.assertEqual( + "h\\n2\\ro\\t_data,carriage\\rreturn=carriage\\nreturn,new\\nline=new\\nline,t\\tab=t\\tab level=2i", + point.to_line_protocol()) def test_EqualSignEscaping(self): - point = Point.measurement("h=2o") \ .tag("l=ocation", "e=urope") \ .field("l=evel", 2) @@ -391,22 +403,24 @@ def test_backslash(self): def test_numpy_types(self): from influxdb_client.extras import np - point = Point.measurement("h2o")\ - .tag("location", "europe")\ - .field("np.float1", np.float(1.123))\ - .field("np.float2", np.float16(2.123))\ - .field("np.float3", np.float32(3.123))\ - .field("np.float4", np.float64(4.123))\ - .field("np.int1", np.int8(1))\ - .field("np.int2", np.int16(2))\ - .field("np.int3", np.int32(3))\ - .field("np.int4", np.int64(4))\ - .field("np.uint1", np.uint8(5))\ - .field("np.uint2", np.uint16(6))\ - .field("np.uint3", np.uint32(7))\ + point = Point.measurement("h2o") \ + .tag("location", "europe") \ + .field("np.float1", np.float(1.123)) \ + .field("np.float2", np.float16(2.123)) \ + .field("np.float3", np.float32(3.123)) \ + .field("np.float4", np.float64(4.123)) \ + .field("np.int1", np.int8(1)) \ + .field("np.int2", np.int16(2)) \ + .field("np.int3", np.int32(3)) \ + .field("np.int4", np.int64(4)) \ + .field("np.uint1", np.uint8(5)) \ + .field("np.uint2", np.uint16(6)) \ + .field("np.uint3", np.uint32(7)) \ .field("np.uint4", np.uint64(8)) - self.assertEqual("h2o,location=europe np.float1=1.123,np.float2=2.123,np.float3=3.123,np.float4=4.123,np.int1=1i,np.int2=2i,np.int3=3i,np.int4=4i,np.uint1=5i,np.uint2=6i,np.uint3=7i,np.uint4=8i", point.to_line_protocol()) + self.assertEqual( + "h2o,location=europe np.float1=1.123,np.float2=2.123,np.float3=3.123,np.float4=4.123,np.int1=1i,np.int2=2i,np.int3=3i,np.int4=4i,np.uint1=5i,np.uint2=6i,np.uint3=7i,np.uint4=8i", + point.to_line_protocol()) def test_from_dictionary_custom_measurement(self): dictionary = { @@ -457,7 +471,9 @@ def test_from_dictionary_custom_fields(self): record_measurement_key="name", record_tag_keys=["location", "version"], record_field_keys=["pressure", "temperature"]) - self.assertEqual("sensor_pt859,location=warehouse_125,version=2021.06.05.5874 pressure=125i,temperature=10i 1632208639", point.to_line_protocol()) + self.assertEqual( + "sensor_pt859,location=warehouse_125,version=2021.06.05.5874 pressure=125i,temperature=10i 1632208639", + point.to_line_protocol()) def test_from_dictionary_tolerant_to_missing_tags_and_fields(self): dictionary = { From 95f6ba0815dcbebfad566528d3239af4a544e2c1 Mon Sep 17 00:00:00 2001 From: bboger Date: Thu, 17 Feb 2022 17:53:27 +0100 Subject: [PATCH 06/10] feat: allow loggingHandler to use Formatters --- influxdb_client/client/loggingHandler.py | 12 ++++++------ tests/test_loggingHandler.py | 14 +++++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/influxdb_client/client/loggingHandler.py b/influxdb_client/client/loggingHandler.py index d543af59..2eab193e 100644 --- a/influxdb_client/client/loggingHandler.py +++ b/influxdb_client/client/loggingHandler.py @@ -12,7 +12,7 @@ class InfluxLoggingHandler(logging.Handler): The raw input will be passed on to the influx write api. """ - DEFAULT_LOG_RECORD_KEYS = logging.makeLogRecord({}).__dict__.keys() + DEFAULT_LOG_RECORD_KEYS = list(logging.makeLogRecord({}).__dict__.keys()) + ['message'] def __init__(self, *, url, token, org, bucket, client_args=None, write_api_args=None): """ @@ -44,8 +44,9 @@ def close(self) -> None: def emit(self, record: logging.LogRecord) -> None: """Emit a record via the influxDB WriteApi.""" try: + message = self.format(record) extra = self._get_extra_values(record) - return self.write_api.write(record=record.msg, **extra) + return self.write_api.write(record=message, **extra) except (KeyboardInterrupt, SystemExit): raise except (Exception,): @@ -57,8 +58,7 @@ def _get_extra_values(self, record: logging.LogRecord) -> dict: Example: `logging.debug(msg, extra={key: value, ...})`. """ - extra = {key: value - for key, value in record.__dict__.items() if key not in self.DEFAULT_LOG_RECORD_KEYS} - if 'bucket' not in extra.keys(): - extra['bucket'] = self.bucket + extra = {'bucket': self.bucket} + extra.update({key: value for key, value in record.__dict__.items() + if key not in self.DEFAULT_LOG_RECORD_KEYS}) return extra diff --git a/tests/test_loggingHandler.py b/tests/test_loggingHandler.py index ac6845fe..238e2031 100644 --- a/tests/test_loggingHandler.py +++ b/tests/test_loggingHandler.py @@ -95,7 +95,7 @@ def test_can_log_str(self): def test_can_log_points(self): point = Point("measurement").field("field_name", "field_value").time(333, WritePrecision.NS) self.logger.debug(point) - self.mock_write_api.write.assert_called_once_with(bucket="my-bucket", record=point) + self.mock_write_api.write.assert_called_once_with(bucket="my-bucket", record=str(point)) def test_catches_urllib_exceptions(self): self.mock_write_api.write.side_effect = urllib3.exceptions.HTTPError() @@ -132,6 +132,18 @@ def test_can_pass_optional_params_on_log(self): write_precision=WritePrecision.S, arg3=3, arg2="two") + def test_formatter(self): + class MyFormatter(logging.Formatter): + def format(self, record: logging.LogRecord) -> str: + time_ns = int(record.created * 1e9) * 0 + 123 + return f"{record.name},level={record.levelname} message=\"{record.msg}\" {time_ns}" + + self.handler.setFormatter(MyFormatter()) + msg = "a debug message" + self.logger.debug(msg) + expected_record = f"test-logger,level=DEBUG message=\"{msg}\" 123" + self.mock_write_api.write.assert_called_once_with(bucket="my-bucket", record=expected_record) + if __name__ == "__main__": unittest.main() From a645cd3512261b377914d3a3ef6a2b1691d70c01 Mon Sep 17 00:00:00 2001 From: bboger Date: Fri, 25 Feb 2022 20:01:46 +0100 Subject: [PATCH 07/10] chore: fulfill requirements for PR --- CHANGELOG.md | 3 +-- examples/README.md | 1 + examples/logging_handler.py | 13 ++++++++++--- influxdb_client/client/write/point.py | 15 ++------------- tests/test_point.py | 9 +-------- 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0286bded..4d3506ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features 1. [#412](https://github.com/influxdata/influxdb-client-python/pull/412): `DeleteApi` uses default value from `InfluxDBClient.org` if an `org` parameter is not specified +2. [#405](https://github.com/influxdata/influxdb-client-python/pull/405): Add `InfluxLoggingHandler`. A handler to use the influx client in native python logging. ### CI 1. [#411](https://github.com/influxdata/influxdb-client-python/pull/411): Use new Codecov uploader for reporting code coverage @@ -45,8 +46,6 @@ This release introduces a support for new version of InfluxDB OSS API definition 1. [#408](https://github.com/influxdata/influxdb-client-python/pull/408): Improve error message when the client cannot find organization by name 1. [#407](https://github.com/influxdata/influxdb-client-python/pull/407): Use `pandas.concat()` instead of deprecated `DataFrame.append()` [DataFrame] -### Features -1. [#405](https://github.com/influxdata/influxdb-client-python/pull/405): Add `InfluxLoggingHandler`. A handler to use the influx client in native python logging. ## 1.25.0 [2022-01-20] diff --git a/examples/README.md b/examples/README.md index 5ea32ec5..dd009033 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,6 +9,7 @@ - [import_data_set_sync_batching.py](import_data_set_sync_batching.py) - How to use [RxPY](https://rxpy.readthedocs.io/en/latest/) to prepare batches for synchronous write into InfluxDB - [write_api_callbacks.py](write_api_callbacks.py) - How to handle batch events - [write_structured_data.py](write_structured_data.py) - How to write structured data - [NamedTuple](https://docs.python.org/3/library/collections.html#collections.namedtuple), [Data Classes](https://docs.python.org/3/library/dataclasses.html) - (_requires Python v3.8+_) +- [logging_handler.py](logging_handler.py) - How to set up a python native logging handler that writes to influx. ## Queries - [query.py](query.py) - How to query data into `FluxTable`s, `Stream` and `CSV` diff --git a/examples/logging_handler.py b/examples/logging_handler.py index ef6ab859..08f2ae05 100644 --- a/examples/logging_handler.py +++ b/examples/logging_handler.py @@ -10,6 +10,7 @@ import time from influxdb_client import InfluxLoggingHandler, WritePrecision, Point +from influxdb_client.client.write_api import SYNCHRONOUS DATA_LOGGER_NAME = '…' @@ -20,9 +21,10 @@ def setup_logger(): This can happen in your core module. """ - influx_logging_handler = InfluxLoggingHandler(url="…", token="…", org="…", bucket="…", - client_args={'arg1': '…'}, - write_api_args={'arg': '…'}) + influx_logging_handler = InfluxLoggingHandler( + url="http://localhost:8086", token="my-token", org="my-org", bucket="my-bucket", + client_args={'timeout': 30_000}, # optional configuration of the client + write_api_args={'write_options': SYNCHRONOUS}) # optional configuration of the write api influx_logging_handler.setLevel(logging.DEBUG) data_logger = logging.getLogger(DATA_LOGGER_NAME) @@ -45,3 +47,8 @@ def use_logger(): .field('temperature', 25.3) .time(datetime.datetime.utcnow(), WritePrecision.MS) ) + + +if __name__ == "__main__": + setup_logger() + use_logger() diff --git a/influxdb_client/client/write/point.py b/influxdb_client/client/write/point.py index 9f0dda24..96d2261e 100644 --- a/influxdb_client/client/write/point.py +++ b/influxdb_client/client/write/point.py @@ -53,8 +53,6 @@ class Point(object): Ref: http://bit.ly/influxdata-point """ - __str___rep = None - @staticmethod def measurement(measurement): """Create a new Point with specified measurement name.""" @@ -201,17 +199,8 @@ def set_str_rep(cls, rep_function): cls.__str___rep = rep_function def __str__(self): - """ - Create string representation of this Point. - - Can be set via `Point.set_str_rep`. Defaults to `to_line_protocol` - Example: - .. code-block:: python - Point.set_str_rep(lambda p: f'{p._name} - {p._tags} - {p._fields} - {p._time}') - """ - if self.__str___rep is None: - return self.to_line_protocol() - return self.__str___rep() + """Create string representation of this Point.""" + return self.to_line_protocol() def _append_tags(tags): diff --git a/tests/test_point.py b/tests/test_point.py index b599a844..00528c7a 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -13,16 +13,9 @@ class PointTest(unittest.TestCase): def test_ToStr(self): point = Point.measurement("h2o").tag("location", "europe").field("level", 2.2) - expected_str = "h2o,location=europe level=2.2" + expected_str = point.to_line_protocol() self.assertEqual(expected_str, str(point)) - def my_str_rep(p: Point) -> str: - return f'{p._name} - {p._tags} - {p._fields} - {p._time}' - - Point.set_str_rep(my_str_rep) - - self.assertEqual(my_str_rep(point), str(point)) - def test_MeasurementEscape(self): point = Point.measurement("h2 o").tag("location", "europe").tag("", "warn").field("level", 2) self.assertEqual(point.to_line_protocol(), "h2\\ o,location=europe level=2i") From 7e1360ef7455fb6a709541b6e4d1cdc358a4db1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bedn=C3=A1=C5=99?= Date: Mon, 28 Feb 2022 09:16:58 +0100 Subject: [PATCH 08/10] fix: typo --- influxdb_client/client/loggingHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/influxdb_client/client/loggingHandler.py b/influxdb_client/client/loggingHandler.py index 2eab193e..445a828d 100644 --- a/influxdb_client/client/loggingHandler.py +++ b/influxdb_client/client/loggingHandler.py @@ -8,7 +8,7 @@ class InfluxLoggingHandler(logging.Handler): """ InfluxLoggingHandler instances dispatch logging events to influx. - There is no need to set a Formater. + There is no need to set a Formatter. The raw input will be passed on to the influx write api. """ From cee3aec9607990d32407b2268cb1a331191fa3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bedn=C3=A1=C5=99?= Date: Mon, 28 Feb 2022 09:18:02 +0100 Subject: [PATCH 09/10] docs: update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3506ee..810701bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Features 1. [#412](https://github.com/influxdata/influxdb-client-python/pull/412): `DeleteApi` uses default value from `InfluxDBClient.org` if an `org` parameter is not specified -2. [#405](https://github.com/influxdata/influxdb-client-python/pull/405): Add `InfluxLoggingHandler`. A handler to use the influx client in native python logging. +2. [#405](https://github.com/influxdata/influxdb-client-python/pull/405): Add `InfluxLoggingHandler`. A handler to use the client in native python logging. ### CI 1. [#411](https://github.com/influxdata/influxdb-client-python/pull/411): Use new Codecov uploader for reporting code coverage From 1e10cee5be0890239b4958fb421d89db96fcba12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bedn=C3=A1=C5=99?= Date: Mon, 28 Feb 2022 09:19:05 +0100 Subject: [PATCH 10/10] docs: update README.md --- examples/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index dd009033..2f262e9a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,7 +9,7 @@ - [import_data_set_sync_batching.py](import_data_set_sync_batching.py) - How to use [RxPY](https://rxpy.readthedocs.io/en/latest/) to prepare batches for synchronous write into InfluxDB - [write_api_callbacks.py](write_api_callbacks.py) - How to handle batch events - [write_structured_data.py](write_structured_data.py) - How to write structured data - [NamedTuple](https://docs.python.org/3/library/collections.html#collections.namedtuple), [Data Classes](https://docs.python.org/3/library/dataclasses.html) - (_requires Python v3.8+_) -- [logging_handler.py](logging_handler.py) - How to set up a python native logging handler that writes to influx. +- [logging_handler.py](logging_handler.py) - How to set up a python native logging handler that writes to InfluxDB ## Queries - [query.py](query.py) - How to query data into `FluxTable`s, `Stream` and `CSV` @@ -29,4 +29,4 @@ - [influxdb_18_example.py](influxdb_18_example.py) - How to connect to InfluxDB 1.8 - [nanosecond_precision.py](nanosecond_precision.py) - How to use nanoseconds precision - [invocable_scripts.py](invocable_scripts.py) - How to use Invocable scripts Cloud API to create custom endpoints that query data - \ No newline at end of file +