log.py 7.1 KB

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