Logging handler to send logs to your OpenSearch cluster with bulk SSL. Forked from https://github.com/logzio/logzio-python-handler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
4.6 KiB

1 year ago
1 year ago
7 years ago
3 years ago
3 years ago
1 year ago
6 years ago
  1. import sys
  2. import json
  3. import logging
  4. import datetime
  5. import traceback
  6. import logging.handlers
  7. from .sender import LogzioSender
  8. from .exceptions import LogzioException
  9. class ExtraFieldsLogFilter(logging.Filter):
  10. def __init__(self, extra: dict, *args, **kwargs):
  11. super().__init__(*args, **kwargs)
  12. self.extra = extra
  13. def filter(self, record):
  14. record.__dict__.update(self.extra)
  15. return True
  16. class LogzioHandler(logging.Handler):
  17. def __init__(self,
  18. token,
  19. logzio_type="python",
  20. logs_drain_timeout=3,
  21. url="https://listener.logz.io:8071",
  22. debug=False,
  23. backup_logs=True,
  24. network_timeout=10.0,
  25. retries_no=4,
  26. retry_timeout=2,
  27. add_context=False):
  28. if not token:
  29. raise LogzioException('Logz.io Token must be provided')
  30. self.logzio_type = logzio_type
  31. if add_context:
  32. try:
  33. from opentelemetry.instrumentation.logging import LoggingInstrumentor
  34. LoggingInstrumentor().instrument(set_logging_format=True)
  35. except ImportError:
  36. print("""Can't add trace context.
  37. OpenTelemetry logging optional package isn't installed.
  38. Please install the following package:
  39. pip install 'logzio-python-handler[opentelemetry-logging]'""")
  40. self.logzio_sender = LogzioSender(
  41. token=token,
  42. url=url,
  43. logs_drain_timeout=logs_drain_timeout,
  44. debug=debug,
  45. backup_logs=backup_logs,
  46. network_timeout=network_timeout,
  47. number_of_retries=retries_no,
  48. retry_timeout=retry_timeout)
  49. logging.Handler.__init__(self)
  50. def __del__(self):
  51. del self.logzio_sender
  52. def extra_fields(self, message):
  53. not_allowed_keys = (
  54. 'args', 'asctime', 'created', 'exc_info', 'stack_info', 'exc_text',
  55. 'filename', 'funcName', 'levelname', 'levelno', 'lineno', 'module',
  56. 'msecs', 'msecs', 'message', 'msg', 'name', 'pathname', 'process',
  57. 'processName', 'relativeCreated', 'thread', 'threadName')
  58. if sys.version_info < (3, 0):
  59. # long and basestring don't exist in py3 so, NOQA
  60. var_type = (basestring, bool, dict, float, # NOQA
  61. int, long, list, type(None)) # NOQA
  62. else:
  63. var_type = (str, bool, dict, float, int, list, type(None))
  64. extra_fields = {}
  65. for key, value in message.__dict__.items():
  66. if key not in not_allowed_keys:
  67. if isinstance(value, var_type):
  68. extra_fields[key] = value
  69. else:
  70. extra_fields[key] = repr(value)
  71. return extra_fields
  72. def flush(self):
  73. self.logzio_sender.flush()
  74. def format(self, record):
  75. message = super(LogzioHandler, self).format(record)
  76. try:
  77. if record.exc_info:
  78. message = message.split("\n")[0] # only keep the original formatted message part
  79. return json.loads(message)
  80. except (TypeError, ValueError):
  81. return message
  82. def format_exception(self, exc_info):
  83. return '\n'.join(traceback.format_exception(*exc_info))
  84. def format_message(self, message):
  85. now = datetime.datetime.utcnow()
  86. timestamp = now.strftime('%Y-%m-%dT%H:%M:%S') + \
  87. '.%03d' % (now.microsecond / 1000) + 'Z'
  88. return_json = {
  89. 'logger': message.name,
  90. 'line_number': message.lineno,
  91. 'path_name': message.pathname,
  92. 'log_level': message.levelname,
  93. 'type': self.logzio_type,
  94. 'message': message.getMessage(),
  95. '@timestamp': timestamp
  96. }
  97. if message.exc_info:
  98. return_json['exception'] = self.format_exception(message.exc_info)
  99. # # We want to ignore default logging formatting on exceptions
  100. # # As we handle those differently directly into exception field
  101. formatted_message = self.format(message)
  102. # Exception with multiple fields, apply them to log json.
  103. if isinstance(formatted_message, dict):
  104. return_json.update(formatted_message)
  105. # No exception, apply default formatted message
  106. elif not message.exc_info:
  107. return_json['message'] = formatted_message
  108. return_json.update(self.extra_fields(message))
  109. return return_json
  110. def emit(self, record):
  111. self.logzio_sender.append(self.format_message(record))