log.py 12 KB

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