_log.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # coding: utf-8
  2. import logging
  3. import types
  4. import datetime
  5. import os
  6. from collections import namedtuple
  7. from enum import Enum
  8. import simplejson
  9. from pythonjsonlogger import jsonlogger
  10. ###############################################################################
  11. class ServiceType(Enum):
  12. AGENT = 1
  13. TASK = 2
  14. EXPORT = 3
  15. class EventType(Enum):
  16. LOG = 1
  17. TASK_STARTED = 2
  18. TASK_FINISHED = 3
  19. TASK_STOPPED = 4
  20. TASK_CRASHED = 5
  21. STEP_COMPLETE = 6
  22. PROGRESS = 7
  23. METRICS = 8
  24. AGENT_READY = 9
  25. TASK_VERIFIED = 10
  26. TASK_REJECTED = 11
  27. TASK_SUBMITTED = 12
  28. TASK_SCHEDULED = 13
  29. AGENT_EXITED = 14
  30. LOGA = 15
  31. STEP_STARTED = 16
  32. FILES_UPLOADED = 17
  33. ###############################################################################
  34. # predefined levels
  35. # level name: level, default exc_info, description
  36. LogLevelSpec = namedtuple('LogLevelSpec', [
  37. 'int',
  38. 'add_exc_info',
  39. 'descr',
  40. ])
  41. LOGGING_LEVELS = {
  42. 'FATAL': LogLevelSpec(50, True, 'Critical error'),
  43. 'ERROR': LogLevelSpec(40, True, 'Error'), # may be shown to end user
  44. 'WARN': LogLevelSpec(30, False, 'Warning'), # may be shown to end user
  45. 'INFO': LogLevelSpec(20, False, 'Info'), # may be shown to end user
  46. 'DEBUG': LogLevelSpec(10, False, 'Debug'),
  47. 'TRACE': LogLevelSpec(5, False, 'Trace'),
  48. }
  49. def _set_logging_levels(levels, the_logger):
  50. for lvl_name, (lvl, def_exc_info, _) in levels.items():
  51. logging.addLevelName(lvl, lvl_name.upper()) # two mappings
  52. def construct_logger_member(lvl_val, default_exc_info):
  53. return lambda self, msg, *args, exc_info=default_exc_info, **kwargs: \
  54. self.log(lvl_val,
  55. msg,
  56. *args,
  57. exc_info=exc_info,
  58. **kwargs)
  59. func = construct_logger_member(lvl, def_exc_info)
  60. bound_method = types.MethodType(func, the_logger)
  61. setattr(the_logger, lvl_name.lower(), bound_method)
  62. ###############################################################################
  63. def _get_default_logging_fields():
  64. supported_keys = [
  65. 'asctime',
  66. # 'created',
  67. # 'filename',
  68. # 'funcName',
  69. 'levelname',
  70. # 'levelno',
  71. # 'lineno',
  72. # 'module',
  73. # 'msecs',
  74. 'message',
  75. # 'name',
  76. # 'pathname',
  77. # 'process',
  78. # 'processName',
  79. # 'relativeCreated',
  80. # 'thread',
  81. # 'threadName'
  82. ]
  83. return ' '.join(['%({0:s})'.format(k) for k in supported_keys])
  84. def dumps_ignore_nan(obj, *args, **kwargs):
  85. return simplejson.dumps(obj, ignore_nan=True, ensure_ascii=False, *args, **kwargs)
  86. class CustomJsonFormatter(jsonlogger.JsonFormatter):
  87. additional_fields = {}
  88. def __init__(self, format_string):
  89. super().__init__(format_string, json_serializer=dumps_ignore_nan)
  90. def process_log_record(self, log_record):
  91. log_record['timestamp'] = log_record.pop('asctime', None)
  92. levelname = log_record.pop('levelname', None)
  93. if levelname is not None:
  94. log_record['level'] = levelname.lower()
  95. e_info = log_record.pop('exc_info', None)
  96. if e_info is not None:
  97. if e_info == 'NoneType: None': # python logger is not ok here
  98. pass
  99. else:
  100. log_record['stack'] = e_info.split('\n')
  101. return jsonlogger.JsonFormatter.process_log_record(self, log_record)
  102. def add_fields(self, log_record, record, message_dict):
  103. super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
  104. for field, val in CustomJsonFormatter.additional_fields.items():
  105. if (val is not None) and (field not in log_record):
  106. log_record[field] = val
  107. def formatTime(self, record, datefmt=None):
  108. ct = datetime.datetime.fromtimestamp(record.created)
  109. t = ct.strftime('%Y-%m-%dT%H:%M:%S')
  110. s = '%s.%03dZ' % (t, record.msecs)
  111. return s
  112. def _construct_logger(the_logger, loglevel_text):
  113. for handler in the_logger.handlers:
  114. the_logger.removeHandler(handler)
  115. _set_logging_levels(LOGGING_LEVELS, the_logger)
  116. the_logger.setLevel(loglevel_text.upper())
  117. log_handler = logging.StreamHandler()
  118. add_logger_handler(the_logger, log_handler)
  119. the_logger.propagate = False
  120. ###############################################################################
  121. def add_logger_handler(the_logger, log_handler): # default format
  122. logger_fmt_string = _get_default_logging_fields()
  123. formatter = CustomJsonFormatter(logger_fmt_string)
  124. log_handler.setFormatter(formatter)
  125. the_logger.addHandler(log_handler)
  126. def add_default_logging_into_file(the_logger, log_dir):
  127. fname = 'log_{}.txt'.format(
  128. datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S"))
  129. ofpath = os.path.join(log_dir, fname)
  130. log_handler_file = logging.FileHandler(filename=ofpath)
  131. add_logger_handler(the_logger, log_handler_file)
  132. # runs on all formatters
  133. def change_formatters_default_values(the_logger, field_name, value):
  134. for handler in the_logger.handlers:
  135. hfaf = handler.formatter.additional_fields
  136. if value is not None:
  137. hfaf[field_name] = value
  138. else:
  139. hfaf.pop(field_name, None)
  140. def set_global_logger():
  141. loglevel = os.getenv('LOG_LEVEL', 'TRACE') # use the env to set loglevel
  142. the_logger = logging.getLogger('logger') # optional logger name
  143. _construct_logger(the_logger, loglevel)
  144. return the_logger
  145. def get_task_logger(task_id):
  146. loglevel = os.getenv('LOG_LEVEL', 'TRACE') # use the env to set loglevel
  147. logger_name = 'task_{}'.format(task_id)
  148. the_logger = logging.getLogger(logger_name) # optional logger name
  149. _construct_logger(the_logger, loglevel)
  150. return the_logger
  151. logger = set_global_logger()