log.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.utils.log
  4. ~~~~~~~~~~~~~~~~
  5. Logging utilities.
  6. """
  7. from __future__ import absolute_import, print_function
  8. import logging
  9. import os
  10. import sys
  11. import threading
  12. import traceback
  13. from contextlib import contextmanager
  14. from billiard import current_process, util as mputil
  15. from kombu.log import get_logger as _get_logger, LOG_LEVELS
  16. from kombu.utils.encoding import safe_str
  17. from celery.five import string_t, text_t
  18. from .term import colored
  19. __all__ = ['ColorFormatter', 'LoggingProxy', 'base_logger',
  20. 'set_in_sighandler', 'in_sighandler', 'get_logger',
  21. 'get_task_logger', 'mlevel', 'ensure_process_aware_logger',
  22. 'get_multiprocessing_logger', 'reset_multiprocessing_logger']
  23. _process_aware = False
  24. PY3 = sys.version_info[0] == 3
  25. MP_LOG = os.environ.get('MP_LOG', False)
  26. # Sets up our logging hierarchy.
  27. #
  28. # Every logger in the celery package inherits from the "celery"
  29. # logger, and every task logger inherits from the "celery.task"
  30. # logger.
  31. base_logger = logger = _get_logger('celery')
  32. mp_logger = _get_logger('multiprocessing')
  33. _in_sighandler = False
  34. def set_in_sighandler(value):
  35. global _in_sighandler
  36. _in_sighandler = value
  37. @contextmanager
  38. def in_sighandler():
  39. set_in_sighandler(True)
  40. try:
  41. yield
  42. finally:
  43. set_in_sighandler(False)
  44. def logger_isa(l, p):
  45. this, seen = l, set()
  46. while this:
  47. if this == p:
  48. return True
  49. else:
  50. if this in seen:
  51. raise RuntimeError(
  52. 'Logger {0!r} parents recursive'.format(l),
  53. )
  54. seen.add(this)
  55. this = this.parent
  56. return False
  57. def get_logger(name):
  58. l = _get_logger(name)
  59. if logging.root not in (l, l.parent) and l is not base_logger:
  60. if not logger_isa(l, base_logger):
  61. l.parent = base_logger
  62. return l
  63. task_logger = get_logger('celery.task')
  64. worker_logger = get_logger('celery.worker')
  65. def get_task_logger(name):
  66. logger = get_logger(name)
  67. if not logger_isa(logger, task_logger):
  68. logger.parent = task_logger
  69. return logger
  70. def mlevel(level):
  71. if level and not isinstance(level, int):
  72. return LOG_LEVELS[level.upper()]
  73. return level
  74. class ColorFormatter(logging.Formatter):
  75. #: Loglevel -> Color mapping.
  76. COLORS = colored().names
  77. colors = {'DEBUG': COLORS['blue'], 'WARNING': COLORS['yellow'],
  78. 'ERROR': COLORS['red'], 'CRITICAL': COLORS['magenta']}
  79. def __init__(self, fmt=None, use_color=True):
  80. logging.Formatter.__init__(self, fmt)
  81. self.use_color = use_color
  82. def formatException(self, ei):
  83. if ei and not isinstance(ei, tuple):
  84. ei = sys.exc_info()
  85. r = logging.Formatter.formatException(self, ei)
  86. if isinstance(r, str) and not PY3:
  87. return safe_str(r)
  88. return r
  89. def format(self, record):
  90. sformat = logging.Formatter.format
  91. color = self.colors.get(record.levelname)
  92. if color and self.use_color:
  93. msg = record.msg
  94. try:
  95. # safe_str will repr the color object
  96. # and color will break on non-string objects
  97. # so need to reorder calls based on type.
  98. # Issue #427
  99. try:
  100. if isinstance(msg, string_t):
  101. record.msg = text_t(color(safe_str(msg)))
  102. else:
  103. record.msg = safe_str(color(msg))
  104. except UnicodeDecodeError:
  105. record.msg = safe_str(msg) # skip colors
  106. except Exception as exc:
  107. record.msg = '<Unrepresentable {0!r}: {1!r}>'.format(
  108. type(msg), exc)
  109. record.exc_info = True
  110. return sformat(self, record)
  111. else:
  112. return safe_str(sformat(self, record))
  113. class LoggingProxy(object):
  114. """Forward file object to :class:`logging.Logger` instance.
  115. :param logger: The :class:`logging.Logger` instance to forward to.
  116. :param loglevel: Loglevel to use when writing messages.
  117. """
  118. mode = 'w'
  119. name = None
  120. closed = False
  121. loglevel = logging.ERROR
  122. _thread = threading.local()
  123. def __init__(self, logger, loglevel=None):
  124. self.logger = logger
  125. self.loglevel = mlevel(loglevel or self.logger.level or self.loglevel)
  126. self._safewrap_handlers()
  127. def _safewrap_handlers(self):
  128. """Make the logger handlers dump internal errors to
  129. `sys.__stderr__` instead of `sys.stderr` to circumvent
  130. infinite loops."""
  131. def wrap_handler(handler): # pragma: no cover
  132. class WithSafeHandleError(logging.Handler):
  133. def handleError(self, record):
  134. exc_info = sys.exc_info()
  135. try:
  136. try:
  137. traceback.print_exception(exc_info[0],
  138. exc_info[1],
  139. exc_info[2],
  140. None, sys.__stderr__)
  141. except IOError:
  142. pass # see python issue 5971
  143. finally:
  144. del(exc_info)
  145. handler.handleError = WithSafeHandleError().handleError
  146. return [wrap_handler(h) for h in self.logger.handlers]
  147. def write(self, data):
  148. """Write message to logging object."""
  149. if _in_sighandler:
  150. return print(safe_str(data), file=sys.__stderr__)
  151. if getattr(self._thread, 'recurse_protection', False):
  152. # Logger is logging back to this file, so stop recursing.
  153. return
  154. data = data.strip()
  155. if data and not self.closed:
  156. self._thread.recurse_protection = True
  157. try:
  158. self.logger.log(self.loglevel, safe_str(data))
  159. finally:
  160. self._thread.recurse_protection = False
  161. def writelines(self, sequence):
  162. """`writelines(sequence_of_strings) -> None`.
  163. Write the strings to the file.
  164. The sequence can be any iterable object producing strings.
  165. This is equivalent to calling :meth:`write` for each string.
  166. """
  167. for part in sequence:
  168. self.write(part)
  169. def flush(self):
  170. """This object is not buffered so any :meth:`flush` requests
  171. are ignored."""
  172. pass
  173. def close(self):
  174. """When the object is closed, no write requests are forwarded to
  175. the logging object anymore."""
  176. self.closed = True
  177. def isatty(self):
  178. """Always return :const:`False`. Just here for file support."""
  179. return False
  180. def ensure_process_aware_logger():
  181. """Make sure process name is recorded when loggers are used."""
  182. global _process_aware
  183. if not _process_aware:
  184. logging._acquireLock()
  185. try:
  186. _process_aware = True
  187. Logger = logging.getLoggerClass()
  188. if getattr(Logger, '_process_aware', False): # pragma: no cover
  189. return
  190. class ProcessAwareLogger(Logger):
  191. _process_aware = True
  192. def makeRecord(self, *args, **kwds):
  193. record = Logger.makeRecord(self, *args, **kwds)
  194. record.processName = current_process()._name
  195. return record
  196. logging.setLoggerClass(ProcessAwareLogger)
  197. finally:
  198. logging._releaseLock()
  199. def get_multiprocessing_logger():
  200. return mputil.get_logger() if mputil else None
  201. def reset_multiprocessing_logger():
  202. if mputil and hasattr(mputil, '_logger'):
  203. mputil._logger = None
  204. def _patch_logger_class():
  205. """Make sure loggers don't log while in a signal handler."""
  206. logging._acquireLock()
  207. try:
  208. OldLoggerClass = logging.getLoggerClass()
  209. if not getattr(OldLoggerClass, '_signal_safe', False):
  210. class SigSafeLogger(OldLoggerClass):
  211. _signal_safe = True
  212. def log(self, *args, **kwargs):
  213. if _in_sighandler:
  214. return
  215. return OldLoggerClass.log(self, *args, **kwargs)
  216. logging.setLoggerClass(SigSafeLogger)
  217. finally:
  218. logging._releaseLock()
  219. _patch_logger_class()