- import sys
- import json
- import logging
- import datetime
- import traceback
- import logging.handlers
-
- from .sender import LogzioSender
- from .exceptions import LogzioException
-
-
- class ExtraFieldsLogFilter(logging.Filter):
-
- def __init__(self, extra: dict, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.extra = extra
-
- def filter(self, record):
- record.__dict__.update(self.extra)
- return True
-
-
- class LogzioHandler(logging.Handler):
-
- def __init__(self,
- token,
- logzio_type="python",
- logs_drain_timeout=3,
- url="https://listener.logz.io:8071",
- debug=False,
- backup_logs=True,
- network_timeout=10.0,
- retries_no=4,
- 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:
- try:
- from opentelemetry.instrumentation.logging import LoggingInstrumentor
- LoggingInstrumentor().instrument(set_logging_format=True)
- except ImportError:
- print("""Can't add trace context.
- OpenTelemetry logging optional package isn't installed.
- Please install the following package:
- pip install 'logzio-python-handler[opentelemetry-logging]'""")
- self.logzio_sender = LogzioSender(
- token=token,
- url=url,
- logs_drain_timeout=logs_drain_timeout,
- debug=debug,
- backup_logs=backup_logs,
- network_timeout=network_timeout,
- number_of_retries=retries_no,
- retry_timeout=retry_timeout)
- logging.Handler.__init__(self)
-
- def __del__(self):
- del self.logzio_sender
-
- def extra_fields(self, message):
-
- not_allowed_keys = (
- 'args', 'asctime', 'created', 'exc_info', 'stack_info', 'exc_text',
- 'filename', 'funcName', 'levelname', 'levelno', 'lineno', 'module',
- 'msecs', 'msecs', 'message', 'msg', 'name', 'pathname', 'process',
- 'processName', 'relativeCreated', 'thread', 'threadName')
-
- if sys.version_info < (3, 0):
- # long and basestring don't exist in py3 so, NOQA
- var_type = (basestring, bool, dict, float, # NOQA
- int, long, list, type(None)) # NOQA
- else:
- var_type = (str, bool, dict, float, int, list, type(None))
-
- extra_fields = {}
-
- for key, value in message.__dict__.items():
- if key not in not_allowed_keys:
- if isinstance(value, var_type):
- extra_fields[key] = value
- else:
- extra_fields[key] = repr(value)
-
- return extra_fields
-
- def flush(self):
- self.logzio_sender.flush()
-
- def format(self, record):
- message = super(LogzioHandler, self).format(record)
- try:
- if record.exc_info:
- message = message.split("\n")[0] # only keep the original formatted message part
- return json.loads(message)
- except (TypeError, ValueError):
- return message
-
- def format_exception(self, exc_info):
- return '\n'.join(traceback.format_exception(*exc_info))
-
- def format_message(self, message):
- now = datetime.datetime.utcnow()
- timestamp = now.strftime('%Y-%m-%dT%H:%M:%S') + \
- '.%03d' % (now.microsecond / 1000) + 'Z'
-
- return_json = {
- 'logger': message.name,
- 'line_number': message.lineno,
- 'path_name': message.pathname,
- 'log_level': message.levelname,
- 'type': self.logzio_type,
- 'message': message.getMessage(),
- '@timestamp': timestamp
- }
-
- if message.exc_info:
- return_json['exception'] = self.format_exception(message.exc_info)
-
- # # We want to ignore default logging formatting on exceptions
- # # As we handle those differently directly into exception field
-
- formatted_message = self.format(message)
- # Exception with multiple fields, apply them to log json.
- if isinstance(formatted_message, dict):
- return_json.update(formatted_message)
- # No exception, apply default formatted message
- elif not message.exc_info:
- return_json['message'] = formatted_message
-
- return_json.update(self.extra_fields(message))
- return return_json
-
- def emit(self, record):
- self.logzio_sender.append(self.format_message(record))
|