log.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.app.log
  4. ~~~~~~~~~~~~~~
  5. The Celery instances logging section: ``Celery.log``.
  6. Sets up logging for the worker and other programs,
  7. redirects stdouts, colors log output, patches logging
  8. related compatibility fixes, and so on.
  9. """
  10. from __future__ import absolute_import
  11. import logging
  12. import os
  13. import sys
  14. from kombu.log import NullHandler
  15. from celery import signals
  16. from celery._state import get_current_task
  17. from celery.utils import isatty
  18. from celery.utils.compat import WatchedFileHandler
  19. from celery.utils.log import (
  20. get_logger, mlevel,
  21. ColorFormatter, ensure_process_aware_logger,
  22. LoggingProxy, get_multiprocessing_logger,
  23. reset_multiprocessing_logger,
  24. )
  25. from celery.utils.term import colored
  26. is_py3k = sys.version_info[0] == 3
  27. class TaskFormatter(ColorFormatter):
  28. def format(self, record):
  29. task = get_current_task()
  30. if task and task.request:
  31. record.__dict__.update(task_id=task.request.id,
  32. task_name=task.name)
  33. else:
  34. record.__dict__.setdefault('task_name', '???')
  35. record.__dict__.setdefault('task_id', '???')
  36. return ColorFormatter.format(self, record)
  37. class Logging(object):
  38. #: The logging subsystem is only configured once per process.
  39. #: setup_logging_subsystem sets this flag, and subsequent calls
  40. #: will do nothing.
  41. _setup = False
  42. def __init__(self, app):
  43. self.app = app
  44. self.loglevel = mlevel(self.app.conf.CELERYD_LOG_LEVEL)
  45. self.format = self.app.conf.CELERYD_LOG_FORMAT
  46. self.task_format = self.app.conf.CELERYD_TASK_LOG_FORMAT
  47. self.colorize = self.app.conf.CELERYD_LOG_COLOR
  48. def setup(self, loglevel=None, logfile=None, redirect_stdouts=False,
  49. redirect_level='WARNING'):
  50. handled = self.setup_logging_subsystem(loglevel, logfile)
  51. if not handled:
  52. logger = get_logger('celery.redirected')
  53. if redirect_stdouts:
  54. self.redirect_stdouts_to_logger(logger,
  55. loglevel=redirect_level)
  56. os.environ.update(
  57. CELERY_LOG_LEVEL=str(loglevel) if loglevel else '',
  58. CELERY_LOG_FILE=str(logfile) if logfile else '',
  59. CELERY_LOG_REDIRECT='1' if redirect_stdouts else '',
  60. CELERY_LOG_REDIRECT_LEVEL=str(redirect_level))
  61. def setup_logging_subsystem(self, loglevel=None, logfile=None,
  62. format=None, colorize=None, **kwargs):
  63. if Logging._setup:
  64. return
  65. Logging._setup = True
  66. loglevel = mlevel(loglevel or self.loglevel)
  67. format = format or self.format
  68. if colorize is None:
  69. colorize = self.supports_color(logfile)
  70. reset_multiprocessing_logger()
  71. if not is_py3k:
  72. ensure_process_aware_logger()
  73. receivers = signals.setup_logging.send(sender=None,
  74. loglevel=loglevel, logfile=logfile,
  75. format=format, colorize=colorize)
  76. if not receivers:
  77. root = logging.getLogger()
  78. if self.app.conf.CELERYD_HIJACK_ROOT_LOGGER:
  79. root.handlers = []
  80. for logger in filter(None, (root, get_multiprocessing_logger())):
  81. self.setup_handlers(logger, logfile, format,
  82. colorize, **kwargs)
  83. if loglevel:
  84. logger.setLevel(loglevel)
  85. signals.after_setup_logger.send(sender=None, logger=logger,
  86. loglevel=loglevel, logfile=logfile,
  87. format=format, colorize=colorize)
  88. # then setup the root task logger.
  89. self.setup_task_loggers(loglevel, logfile, colorize=colorize)
  90. # This is a hack for multiprocessing's fork+exec, so that
  91. # logging before Process.run works.
  92. logfile_name = logfile if isinstance(logfile, basestring) else ''
  93. os.environ.update(_MP_FORK_LOGLEVEL_=str(loglevel),
  94. _MP_FORK_LOGFILE_=logfile_name,
  95. _MP_FORK_LOGFORMAT_=format)
  96. return receivers
  97. def setup_task_loggers(self, loglevel=None, logfile=None, format=None,
  98. colorize=None, propagate=False, **kwargs):
  99. """Setup the task logger.
  100. If `logfile` is not specified, then `sys.stderr` is used.
  101. Returns logger object.
  102. """
  103. loglevel = mlevel(loglevel or self.loglevel)
  104. format = format or self.task_format
  105. if colorize is None:
  106. colorize = self.supports_color(logfile)
  107. logger = self.setup_handlers(get_logger('celery.task'),
  108. logfile, format, colorize,
  109. formatter=TaskFormatter, **kwargs)
  110. logger.setLevel(loglevel)
  111. logger.propagate = int(propagate) # this is an int for some reason.
  112. # better to not question why.
  113. signals.after_setup_task_logger.send(sender=None, logger=logger,
  114. loglevel=loglevel, logfile=logfile,
  115. format=format, colorize=colorize)
  116. return logger
  117. def redirect_stdouts_to_logger(self, logger, loglevel=None,
  118. stdout=True, stderr=True):
  119. """Redirect :class:`sys.stdout` and :class:`sys.stderr` to a
  120. logging instance.
  121. :param logger: The :class:`logging.Logger` instance to redirect to.
  122. :param loglevel: The loglevel redirected messages will be logged as.
  123. """
  124. proxy = LoggingProxy(logger, loglevel)
  125. if stdout:
  126. sys.stdout = proxy
  127. if stderr:
  128. sys.stderr = proxy
  129. return proxy
  130. def supports_color(self, logfile=None):
  131. if self.app.IS_WINDOWS:
  132. # Windows does not support ANSI color codes.
  133. return False
  134. if self.colorize is None:
  135. # Only use color if there is no active log file
  136. # and stderr is an actual terminal.
  137. return logfile is None and isatty(sys.stderr)
  138. return self.colorize
  139. def colored(self, logfile=None):
  140. return colored(enabled=self.supports_color(logfile))
  141. def setup_handlers(self, logger, logfile, format, colorize,
  142. formatter=ColorFormatter, **kwargs):
  143. if self._is_configured(logger):
  144. return logger
  145. handler = self._detect_handler(logfile)
  146. handler.setFormatter(formatter(format, use_color=colorize))
  147. logger.addHandler(handler)
  148. return logger
  149. def _detect_handler(self, logfile=None):
  150. """Create log handler with either a filename, an open stream
  151. or :const:`None` (stderr)."""
  152. logfile = sys.__stderr__ if logfile is None else logfile
  153. if hasattr(logfile, 'write'):
  154. return logging.StreamHandler(logfile)
  155. return WatchedFileHandler(logfile)
  156. def _has_handler(self, logger):
  157. return (logger.handlers and
  158. not isinstance(logger.handlers[0], NullHandler))
  159. def _is_configured(self, logger):
  160. return self._has_handler(logger) and not getattr(
  161. logger, '_rudimentary_setup', False)
  162. def setup_logger(self, name='celery', *args, **kwargs):
  163. """Deprecated: No longer used."""
  164. self.setup_logging_subsystem(*args, **kwargs)
  165. return logging.root
  166. def get_default_logger(self, name='celery', **kwargs):
  167. return get_logger(name)