123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- # coding: utf-8
- import logging
- import types
- import datetime
- import os
- from collections import namedtuple
- from enum import Enum
- import simplejson
- from pythonjsonlogger import jsonlogger
- ###############################################################################
- class ServiceType(Enum):
- AGENT = 1
- TASK = 2
- EXPORT = 3
- class EventType(Enum):
- LOG = 1
- TASK_STARTED = 2
- TASK_FINISHED = 3
- TASK_STOPPED = 4
- TASK_CRASHED = 5
- STEP_COMPLETE = 6
- PROGRESS = 7
- METRICS = 8
- AGENT_READY = 9
- TASK_VERIFIED = 10
- TASK_REJECTED = 11
- TASK_SUBMITTED = 12
- TASK_SCHEDULED = 13
- AGENT_EXITED = 14
- LOGA = 15
- STEP_STARTED = 16
- FILES_UPLOADED = 17
- ###############################################################################
- # predefined levels
- # level name: level, default exc_info, description
- LogLevelSpec = namedtuple('LogLevelSpec', [
- 'int',
- 'add_exc_info',
- 'descr',
- ])
- LOGGING_LEVELS = {
- 'FATAL': LogLevelSpec(50, True, 'Critical error'),
- 'ERROR': LogLevelSpec(40, True, 'Error'), # may be shown to end user
- 'WARN': LogLevelSpec(30, False, 'Warning'), # may be shown to end user
- 'INFO': LogLevelSpec(20, False, 'Info'), # may be shown to end user
- 'DEBUG': LogLevelSpec(10, False, 'Debug'),
- 'TRACE': LogLevelSpec(5, False, 'Trace'),
- }
- def _set_logging_levels(levels, the_logger):
- for lvl_name, (lvl, def_exc_info, _) in levels.items():
- logging.addLevelName(lvl, lvl_name.upper()) # two mappings
- def construct_logger_member(lvl_val, default_exc_info):
- return lambda self, msg, *args, exc_info=default_exc_info, **kwargs: \
- self.log(lvl_val,
- msg,
- *args,
- exc_info=exc_info,
- **kwargs)
- func = construct_logger_member(lvl, def_exc_info)
- bound_method = types.MethodType(func, the_logger)
- setattr(the_logger, lvl_name.lower(), bound_method)
- ###############################################################################
- def _get_default_logging_fields():
- supported_keys = [
- 'asctime',
- # 'created',
- # 'filename',
- # 'funcName',
- 'levelname',
- # 'levelno',
- # 'lineno',
- # 'module',
- # 'msecs',
- 'message',
- # 'name',
- # 'pathname',
- # 'process',
- # 'processName',
- # 'relativeCreated',
- # 'thread',
- # 'threadName'
- ]
- return ' '.join(['%({0:s})'.format(k) for k in supported_keys])
- def dumps_ignore_nan(obj, *args, **kwargs):
- return simplejson.dumps(obj, ignore_nan=True, ensure_ascii=False, *args, **kwargs)
- class CustomJsonFormatter(jsonlogger.JsonFormatter):
- additional_fields = {}
- def __init__(self, format_string):
- super().__init__(format_string, json_serializer=dumps_ignore_nan)
- def process_log_record(self, log_record):
- log_record['timestamp'] = log_record.pop('asctime', None)
- levelname = log_record.pop('levelname', None)
- if levelname is not None:
- log_record['level'] = levelname.lower()
- e_info = log_record.pop('exc_info', None)
- if e_info is not None:
- if e_info == 'NoneType: None': # python logger is not ok here
- pass
- else:
- log_record['stack'] = e_info.split('\n')
- return jsonlogger.JsonFormatter.process_log_record(self, log_record)
- def add_fields(self, log_record, record, message_dict):
- super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
- for field, val in CustomJsonFormatter.additional_fields.items():
- if (val is not None) and (field not in log_record):
- log_record[field] = val
- def formatTime(self, record, datefmt=None):
- ct = datetime.datetime.fromtimestamp(record.created)
- t = ct.strftime('%Y-%m-%dT%H:%M:%S')
- s = '%s.%03dZ' % (t, record.msecs)
- return s
- def _construct_logger(the_logger, loglevel_text):
- for handler in the_logger.handlers:
- the_logger.removeHandler(handler)
- _set_logging_levels(LOGGING_LEVELS, the_logger)
- the_logger.setLevel(loglevel_text.upper())
- log_handler = logging.StreamHandler()
- add_logger_handler(the_logger, log_handler)
- the_logger.propagate = False
- ###############################################################################
- def add_logger_handler(the_logger, log_handler): # default format
- logger_fmt_string = _get_default_logging_fields()
- formatter = CustomJsonFormatter(logger_fmt_string)
- log_handler.setFormatter(formatter)
- the_logger.addHandler(log_handler)
- def add_default_logging_into_file(the_logger, log_dir):
- fname = 'log_{}.txt'.format(
- datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S"))
- ofpath = os.path.join(log_dir, fname)
- log_handler_file = logging.FileHandler(filename=ofpath)
- add_logger_handler(the_logger, log_handler_file)
- # runs on all formatters
- def change_formatters_default_values(the_logger, field_name, value):
- for handler in the_logger.handlers:
- hfaf = handler.formatter.additional_fields
- if value is not None:
- hfaf[field_name] = value
- else:
- hfaf.pop(field_name, None)
- def set_global_logger():
- loglevel = os.getenv('LOG_LEVEL', 'TRACE') # use the env to set loglevel
- the_logger = logging.getLogger('logger') # optional logger name
- _construct_logger(the_logger, loglevel)
- return the_logger
- def get_task_logger(task_id):
- loglevel = os.getenv('LOG_LEVEL', 'TRACE') # use the env to set loglevel
- logger_name = 'task_{}'.format(task_id)
- the_logger = logging.getLogger(logger_name) # optional logger name
- _construct_logger(the_logger, loglevel)
- return the_logger
- logger = set_global_logger()
|