diff --git a/README.md b/README.md index 282870b..ac71843 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ LOGGING = { 'level': 'INFO', 'formatter': 'logzioFormat', 'token': '<>', + 'logzio_type': 'python-handler', 'logs_drain_timeout': 5, 'url': 'https://<>:8071', 'retries_no': 4, @@ -211,9 +212,52 @@ LOGGING = { - logzio_type - Log type, for searching in logz.io (defaults to "python"), it cannot contain a space. - appname - Your django app -Please note that if you are using `python 3.8` it is preferred to use the `logging.config.dictConfig` method, as mentioned in [python's documentation](https://docs.python.org/3/library/logging.config.html#configuration-file-format). +## Trace context + +If you're sending traces with OpenTelemetry instrumentation (auto or manual), you can correlate your logs with the trace context. +That way, your logs will have traces data in it, such as service name, span id and trace id. +To enable this feature, set the `add_context` param in your handler configuration to `True`, like in this example: + +```python +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'logzioFormat': { + 'format': '{"additional_field": "value"}', + 'validate': False + } + }, + 'handlers': { + 'logzio': { + 'class': 'logzio.handler.LogzioHandler', + 'level': 'INFO', + 'formatter': 'logzioFormat', + 'token': '<>', + 'logzio_type': 'python-handler', + 'logs_drain_timeout': 5, + 'url': 'https://<>:8071', + 'retries_no': 4, + 'retry_timeout': 2, + 'add_context': True + } + }, + 'loggers': { + '': { + 'level': 'DEBUG', + 'handlers': ['logzio'], + 'propagate': True + } + } +} +``` + +Please note that if you are using `python 3.8`, it is preferred to use the `logging.config.dictConfig` method, as mentioned in [python's documentation](https://docs.python.org/3/library/logging.config.html#configuration-file-format). ## Release Notes +- 4.0.0 + - Add ability to automatically attach trace context to the logs. + - 3.1.1 - Bug fixes (issue #68, exception message formatting) - Added CI: Tests and Auto release diff --git a/logzio/handler.py b/logzio/handler.py index e0a4b45..e9f21e4 100644 --- a/logzio/handler.py +++ b/logzio/handler.py @@ -8,6 +8,8 @@ import logging.handlers from .sender import LogzioSender from .exceptions import LogzioException +from opentelemetry.instrumentation.logging import LoggingInstrumentor + class LogzioHandler(logging.Handler): @@ -20,13 +22,17 @@ class LogzioHandler(logging.Handler): backup_logs=True, network_timeout=10.0, retries_no=4, - retry_timeout=2): + retry_timeout=2, + add_context=False): if not token: raise LogzioException('Logz.io Token must be provided') self.logzio_type = logzio_type + if add_context: + LoggingInstrumentor().instrument(set_logging_format=True) + self.logzio_sender = LogzioSender( token=token, url=url, diff --git a/requirements.txt b/requirements.txt index dff3f70..558c4a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -requests>=2.27.0 \ No newline at end of file +requests>=2.27.0 +protobuf==3.20.1 +opentelemetry-instrumentation-logging==0.32b0 \ No newline at end of file diff --git a/setup.py b/setup.py index 0b6d2e1..a1bddcd 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages setup( name="logzio-python-handler", - version='3.1.1', + version='4.0.0', description="Logging handler to send logs to your Logz.io account with bulk SSL", keywords="logging handler logz.io bulk https", author="roiravhon", @@ -13,7 +13,9 @@ setup( license="Apache License 2", packages=find_packages(), install_requires=[ - "requests>=2.27.0" + "requests>=2.27.0", + "protobuf==3.20.1", + "opentelemetry-instrumentation-logging==0.32b0" ], test_requires=[ "future" diff --git a/tests/test_add_context.py b/tests/test_add_context.py new file mode 100644 index 0000000..d77fbf3 --- /dev/null +++ b/tests/test_add_context.py @@ -0,0 +1,80 @@ +import fnmatch +import logging.config +import os +import time +import json +from unittest import TestCase + +from .mockLogzioListener import listener + + +def _find(pattern, path): + result = [] + for root, dirs, files in os.walk(path): + for name in files: + if fnmatch.fnmatch(name, pattern): + result.append(os.path.join(root, name)) + + break # Not descending recursively + return result + + +class TestAddContext(TestCase): + def setUp(self): + self.logzio_listener = listener.MockLogzioListener() + self.logzio_listener.clear_logs_buffer() + self.logzio_listener.clear_server_error() + self.logs_drain_timeout = 1 + self.retries_no = 4 + self.retry_timeout = 2 + + logging_configuration = { + "version": 1, + "formatters": { + "logzio": { + "format": '{"key": "value"}', + "validate": False + } + }, + "handlers": { + "LogzioHandler": { + "class": "logzio.handler.LogzioHandler", + "formatter": "logzio", + "level": "DEBUG", + "token": "token", + 'logzio_type': "type", + 'logs_drain_timeout': self.logs_drain_timeout, + 'url': "http://" + self.logzio_listener.get_host() + ":" + str(self.logzio_listener.get_port()), + 'debug': True, + 'retries_no': self.retries_no, + 'retry_timeout': self.retry_timeout, + 'add_context': True + } + }, + "loggers": { + "test": { + "handlers": ["LogzioHandler"], + "level": "DEBUG" + } + } + } + + logging.config.dictConfig(logging_configuration) + self.logger = logging.getLogger('test') + + for curr_file in _find("logzio-failures-*.txt", "."): + os.remove(curr_file) + + def test_add_context(self): + log_message = "this log should have a trace context" + self.logger.info(log_message) + time.sleep(self.logs_drain_timeout * 2) + logs_list = self.logzio_listener.logs_list + for current_log in logs_list: + if log_message in current_log: + log_dict = json.loads(current_log) + self.assertTrue('otelSpanID' in log_dict) + self.assertTrue('otelTraceID' in log_dict) + self.assertTrue('otelServiceName' in log_dict) + +