log.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. """
  2. celery.log
  3. ==========
  4. Logging utilities.
  5. """
  6. from __future__ import absolute_import
  7. import logging
  8. import threading
  9. import sys
  10. import traceback
  11. try:
  12. from multiprocessing import current_process
  13. from multiprocessing import util as mputil
  14. except ImportError:
  15. current_process = mputil = None # noqa
  16. from . import current_app
  17. from . import signals
  18. from .local import Proxy
  19. from .utils import LOG_LEVELS, isatty
  20. from .utils.compat import LoggerAdapter, WatchedFileHandler
  21. from .utils.encoding import safe_str
  22. from .utils.patch import ensure_process_aware_logger
  23. from .utils.term import colored
  24. is_py3k = sys.version_info >= (3, 0)
  25. __all__ = ["ColorFormatter", "Logging", "get_default_logger",
  26. "setup_logger", "setup_task_logger", "get_task_logger",
  27. "setup_logging_subsystem", "redirect_stdouts_to_logger",
  28. "LoggingProxy"]
  29. class ColorFormatter(logging.Formatter):
  30. #: Loglevel -> Color mapping.
  31. COLORS = colored().names
  32. colors = {"DEBUG": COLORS["blue"], "WARNING": COLORS["yellow"],
  33. "ERROR": COLORS["red"], "CRITICAL": COLORS["magenta"]}
  34. def __init__(self, msg, use_color=True):
  35. logging.Formatter.__init__(self, msg)
  36. self.use_color = use_color
  37. def formatException(self, ei):
  38. r = logging.Formatter.formatException(self, ei)
  39. if isinstance(r, str) and not is_py3k:
  40. return safe_str(r)
  41. return r
  42. def format(self, record):
  43. levelname = record.levelname
  44. color = self.colors.get(levelname)
  45. if self.use_color and color:
  46. try:
  47. record.msg = str(color(safe_str(record.msg)))
  48. except Exception, exc:
  49. record.msg = "<Unrepresentable %r: %r>" % (
  50. type(record.msg), exc)
  51. record.exc_info = sys.exc_info()
  52. if not is_py3k:
  53. # Very ugly, but have to make sure processName is supported
  54. # by foreign logger instances.
  55. # (processName is always supported by Python 2.7)
  56. if "processName" not in record.__dict__:
  57. process_name = (current_process and
  58. current_process()._name or "")
  59. record.__dict__["processName"] = process_name
  60. return safe_str(logging.Formatter.format(self, record))
  61. class Logging(object):
  62. #: The logging subsystem is only configured once per process.
  63. #: setup_logging_subsystem sets this flag, and subsequent calls
  64. #: will do nothing.
  65. _setup = False
  66. def __init__(self, app):
  67. self.app = app
  68. self.loglevel = self.app.conf.CELERYD_LOG_LEVEL
  69. self.format = self.app.conf.CELERYD_LOG_FORMAT
  70. self.task_format = self.app.conf.CELERYD_TASK_LOG_FORMAT
  71. self.colorize = self.app.conf.CELERYD_LOG_COLOR
  72. def supports_color(self, logfile=None):
  73. if self.app.IS_WINDOWS:
  74. # Windows does not support ANSI color codes.
  75. return False
  76. if self.colorize is None:
  77. # Only use color if there is no active log file
  78. # and stderr is an actual terminal.
  79. return logfile is None and isatty(sys.stderr)
  80. return self.colorize
  81. def colored(self, logfile=None):
  82. return colored(enabled=self.supports_color(logfile))
  83. def get_task_logger(self, loglevel=None, name=None):
  84. logger = logging.getLogger(name or "celery.task.default")
  85. if loglevel is not None:
  86. logger.setLevel(loglevel)
  87. return logger
  88. def setup_logging_subsystem(self, loglevel=None, logfile=None,
  89. format=None, colorize=None, **kwargs):
  90. if Logging._setup:
  91. return
  92. loglevel = loglevel or self.loglevel
  93. format = format or self.format
  94. if colorize is None:
  95. colorize = self.supports_color(logfile)
  96. if mputil and hasattr(mputil, "_logger"):
  97. mputil._logger = None
  98. if not is_py3k:
  99. ensure_process_aware_logger()
  100. receivers = signals.setup_logging.send(sender=None,
  101. loglevel=loglevel, logfile=logfile,
  102. format=format, colorize=colorize)
  103. if not receivers:
  104. root = logging.getLogger()
  105. if self.app.conf.CELERYD_HIJACK_ROOT_LOGGER:
  106. root.handlers = []
  107. mp = mputil.get_logger() if mputil else None
  108. for logger in filter(None, (root, mp)):
  109. self._setup_logger(logger, logfile, format, colorize, **kwargs)
  110. logger.setLevel(loglevel)
  111. signals.after_setup_logger.send(sender=None, logger=logger,
  112. loglevel=loglevel, logfile=logfile,
  113. format=format, colorize=colorize)
  114. Logging._setup = True
  115. return receivers
  116. def _detect_handler(self, logfile=None):
  117. """Create log handler with either a filename, an open stream
  118. or :const:`None` (stderr)."""
  119. logfile = sys.__stderr__ if logfile is None else logfile
  120. if hasattr(logfile, "write"):
  121. return logging.StreamHandler(logfile)
  122. return WatchedFileHandler(logfile)
  123. def get_default_logger(self, loglevel=None, name="celery"):
  124. """Get default logger instance.
  125. :keyword loglevel: Initial log level.
  126. """
  127. logger = logging.getLogger(name)
  128. if loglevel is not None:
  129. logger.setLevel(loglevel)
  130. return logger
  131. def setup_logger(self, loglevel=None, logfile=None,
  132. format=None, colorize=None, name="celery", root=True,
  133. app=None, **kwargs):
  134. """Setup the :mod:`multiprocessing` logger.
  135. If `logfile` is not specified, then `sys.stderr` is used.
  136. Returns logger object.
  137. """
  138. loglevel = loglevel or self.loglevel
  139. format = format or self.format
  140. if colorize is None:
  141. colorize = self.supports_color(logfile)
  142. if not root or self.app.conf.CELERYD_HIJACK_ROOT_LOGGER:
  143. return self._setup_logger(self.get_default_logger(loglevel, name),
  144. logfile, format, colorize, **kwargs)
  145. self.setup_logging_subsystem(loglevel, logfile,
  146. format, colorize, **kwargs)
  147. return self.get_default_logger(name=name)
  148. def setup_task_logger(self, loglevel=None, logfile=None, format=None,
  149. colorize=None, task_name=None, task_id=None, propagate=False,
  150. app=None, **kwargs):
  151. """Setup the task logger.
  152. If `logfile` is not specified, then `sys.stderr` is used.
  153. Returns logger object.
  154. """
  155. loglevel = loglevel or self.loglevel
  156. format = format or self.task_format
  157. if colorize is None:
  158. colorize = self.supports_color(logfile)
  159. logger = self._setup_logger(self.get_task_logger(loglevel, task_name),
  160. logfile, format, colorize, **kwargs)
  161. logger.propagate = int(propagate) # this is an int for some reason.
  162. # better to not question why.
  163. signals.after_setup_task_logger.send(sender=None, logger=logger,
  164. loglevel=loglevel, logfile=logfile,
  165. format=format, colorize=colorize)
  166. return LoggerAdapter(logger, {"task_id": task_id,
  167. "task_name": task_name})
  168. def redirect_stdouts_to_logger(self, logger, loglevel=None,
  169. stdout=True, stderr=True):
  170. """Redirect :class:`sys.stdout` and :class:`sys.stderr` to a
  171. logging instance.
  172. :param logger: The :class:`logging.Logger` instance to redirect to.
  173. :param loglevel: The loglevel redirected messages will be logged as.
  174. """
  175. proxy = LoggingProxy(logger, loglevel)
  176. if stdout:
  177. sys.stdout = sys.__stdout__ = proxy
  178. if stderr:
  179. sys.stderr = sys.__stderr__ = proxy
  180. return proxy
  181. def _setup_logger(self, logger, logfile, format, colorize,
  182. formatter=ColorFormatter, **kwargs):
  183. if logger.handlers: # Logger already configured
  184. return logger
  185. handler = self._detect_handler(logfile)
  186. handler.setFormatter(formatter(format, use_color=colorize))
  187. logger.addHandler(handler)
  188. return logger
  189. get_default_logger = Proxy(lambda: current_app.log.get_default_logger)
  190. setup_logger = Proxy(lambda: current_app.log.setup_logger)
  191. setup_task_logger = Proxy(lambda: current_app.log.setup_task_logger)
  192. get_task_logger = Proxy(lambda: current_app.log.get_task_logger)
  193. setup_logging_subsystem = Proxy(
  194. lambda: current_app.log.setup_logging_subsystem)
  195. redirect_stdouts_to_logger = Proxy(
  196. lambda: current_app.log.redirect_stdouts_to_logger)
  197. class LoggingProxy(object):
  198. """Forward file object to :class:`logging.Logger` instance.
  199. :param logger: The :class:`logging.Logger` instance to forward to.
  200. :param loglevel: Loglevel to use when writing messages.
  201. """
  202. mode = "w"
  203. name = None
  204. closed = False
  205. loglevel = logging.ERROR
  206. _thread = threading.local()
  207. def __init__(self, logger, loglevel=None):
  208. self.logger = logger
  209. self.loglevel = loglevel or self.logger.level or self.loglevel
  210. if not isinstance(self.loglevel, int):
  211. self.loglevel = LOG_LEVELS[self.loglevel.upper()]
  212. self._safewrap_handlers()
  213. def _safewrap_handlers(self):
  214. """Make the logger handlers dump internal errors to
  215. `sys.__stderr__` instead of `sys.stderr` to circumvent
  216. infinite loops."""
  217. def wrap_handler(handler): # pragma: no cover
  218. class WithSafeHandleError(logging.Handler):
  219. def handleError(self, record):
  220. exc_info = sys.exc_info()
  221. try:
  222. try:
  223. traceback.print_exception(exc_info[0],
  224. exc_info[1],
  225. exc_info[2],
  226. None, sys.__stderr__)
  227. except IOError:
  228. pass # see python issue 5971
  229. finally:
  230. del(exc_info)
  231. handler.handleError = WithSafeHandleError().handleError
  232. return map(wrap_handler, self.logger.handlers)
  233. def write(self, data):
  234. if getattr(self._thread, "recurse_protection", False):
  235. # Logger is logging back to this file, so stop recursing.
  236. return
  237. """Write message to logging object."""
  238. data = data.strip()
  239. if data and not self.closed:
  240. self._thread.recurse_protection = True
  241. try:
  242. self.logger.log(self.loglevel, data)
  243. finally:
  244. self._thread.recurse_protection = False
  245. def writelines(self, sequence):
  246. """`writelines(sequence_of_strings) -> None`.
  247. Write the strings to the file.
  248. The sequence can be any iterable object producing strings.
  249. This is equivalent to calling :meth:`write` for each string.
  250. """
  251. for part in sequence:
  252. self.write(part)
  253. def flush(self):
  254. """This object is not buffered so any :meth:`flush` requests
  255. are ignored."""
  256. pass
  257. def close(self):
  258. """When the object is closed, no write requests are forwarded to
  259. the logging object anymore."""
  260. self.closed = True
  261. def isatty(self):
  262. """Always returns :const:`False`. Just here for file support."""
  263. return False
  264. def fileno(self):
  265. return None
  266. class SilenceRepeated(object):
  267. """Only log action every n iterations."""
  268. def __init__(self, action, max_iterations=10):
  269. self.action = action
  270. self.max_iterations = max_iterations
  271. self._iterations = 0
  272. def __call__(self, *msgs):
  273. if not self._iterations or self._iterations >= self.max_iterations:
  274. for msg in msgs:
  275. self.action(msg)
  276. self._iterations = 0
  277. else:
  278. self._iterations += 1