processors.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the MIT License. See the LICENSE file in the root of this
  3. # repository for complete details.
  4. """
  5. Processors useful regardless of the logging framework.
  6. """
  7. from __future__ import absolute_import, division, print_function
  8. import datetime
  9. import json
  10. import operator
  11. import sys
  12. import time
  13. import six
  14. from structlog._frames import (
  15. _find_first_app_frame_and_name,
  16. _format_exception,
  17. _format_stack,
  18. )
  19. class KeyValueRenderer(object):
  20. """
  21. Render `event_dict` as a list of ``Key=repr(Value)`` pairs.
  22. :param bool sort_keys: Whether to sort keys when formatting.
  23. :param list key_order: List of keys that should be rendered in this exact
  24. order. Missing keys will be rendered as ``None``, extra keys depending
  25. on *sort_keys* and the dict class.
  26. :param bool drop_missing: When ``True``, extra keys in *key_order* will be
  27. dropped rather than rendered as ``None``.
  28. :param bool repr_native_str: When ``True``, :func:`repr()` is also applied
  29. to native strings (i.e. unicode on Python 3 and bytes on Python 2).
  30. Setting this to ``False`` is useful if you want to have human-readable
  31. non-ASCII output on Python 2.
  32. .. versionadded:: 0.2.0 *key_order*
  33. .. versionadded:: 16.1.0 *drop_missing*
  34. .. versionadded:: 17.1.0 *repr_native_str*
  35. """
  36. def __init__(self, sort_keys=False, key_order=None, drop_missing=False,
  37. repr_native_str=True):
  38. # Use an optimized version for each case.
  39. if key_order and sort_keys:
  40. def ordered_items(event_dict):
  41. items = []
  42. for key in key_order:
  43. value = event_dict.pop(key, None)
  44. if value is not None or not drop_missing:
  45. items.append((key, value))
  46. items += sorted(event_dict.items())
  47. return items
  48. elif key_order:
  49. def ordered_items(event_dict):
  50. items = []
  51. for key in key_order:
  52. value = event_dict.pop(key, None)
  53. if value is not None or not drop_missing:
  54. items.append((key, value))
  55. items += event_dict.items()
  56. return items
  57. elif sort_keys:
  58. def ordered_items(event_dict):
  59. return sorted(event_dict.items())
  60. else:
  61. ordered_items = operator.methodcaller('items')
  62. self._ordered_items = ordered_items
  63. if repr_native_str is True:
  64. self._repr = repr
  65. else:
  66. def _repr(inst):
  67. if isinstance(inst, str):
  68. return inst
  69. else:
  70. return repr(inst)
  71. self._repr = _repr
  72. def __call__(self, _, __, event_dict):
  73. return ' '.join(k + '=' + self._repr(v)
  74. for k, v in self._ordered_items(event_dict))
  75. class UnicodeEncoder(object):
  76. """
  77. Encode unicode values in `event_dict`.
  78. :param str encoding: Encoding to encode to (default: ``'utf-8'``).
  79. :param str errors: How to cope with encoding errors (default
  80. ``'backslashreplace'``).
  81. Useful if you're running Python 2 as otherwise ``u"abc"`` will be rendered
  82. as ``'u"abc"'``.
  83. Just put it in the processor chain before the renderer.
  84. """
  85. def __init__(self, encoding='utf-8', errors='backslashreplace'):
  86. self._encoding = encoding
  87. self._errors = errors
  88. def __call__(self, logger, name, event_dict):
  89. for key, value in event_dict.items():
  90. if isinstance(value, six.text_type):
  91. event_dict[key] = value.encode(self._encoding, self._errors)
  92. return event_dict
  93. class UnicodeDecoder(object):
  94. """
  95. Decode byte string values in `event_dict`.
  96. :param str encoding: Encoding to decode from (default: ``'utf-8'``).
  97. :param str errors: How to cope with encoding errors (default:
  98. ``'replace'``).
  99. Useful if you're running Python 3 as otherwise ``b"abc"`` will be rendered
  100. as ``'b"abc"'``.
  101. Just put it in the processor chain before the renderer.
  102. .. versionadded:: 15.4.0
  103. """
  104. def __init__(self, encoding='utf-8', errors='replace'):
  105. self._encoding = encoding
  106. self._errors = errors
  107. def __call__(self, logger, name, event_dict):
  108. for key, value in event_dict.items():
  109. if isinstance(value, bytes):
  110. event_dict[key] = value.decode(self._encoding, self._errors)
  111. return event_dict
  112. class JSONRenderer(object):
  113. """
  114. Render the `event_dict` using ``serializer(event_dict, **json_kw)``.
  115. :param dict json_kw: Are passed unmodified to *serializer*.
  116. :param callable serializer: A :func:`json.dumps`-compatible callable that
  117. will be used to format the string. This can be used to use alternative
  118. JSON encoders like `simplejson
  119. <https://pypi.python.org/pypi/simplejson/>`_ or `RapidJSON
  120. <https://pypi.python.org/pypi/python-rapidjson/>`_ (faster but Python
  121. 3-only) (default: :func:`json.dumps`).
  122. .. versionadded:: 0.2.0
  123. Support for ``__structlog__`` serialization method.
  124. .. versionadded:: 15.4.0
  125. ``serializer`` parameter.
  126. """
  127. def __init__(self, serializer=json.dumps, **dumps_kw):
  128. self._dumps_kw = dumps_kw
  129. self._dumps = serializer
  130. def __call__(self, logger, name, event_dict):
  131. return self._dumps(event_dict, default=_json_fallback_handler,
  132. **self._dumps_kw)
  133. def _json_fallback_handler(obj):
  134. """
  135. Serialize custom datatypes and pass the rest to __structlog__ & repr().
  136. """
  137. # circular imports :(
  138. from structlog.threadlocal import _ThreadLocalDictWrapper
  139. if isinstance(obj, _ThreadLocalDictWrapper):
  140. return obj._dict
  141. else:
  142. try:
  143. return obj.__structlog__()
  144. except AttributeError:
  145. return repr(obj)
  146. def format_exc_info(logger, name, event_dict):
  147. """
  148. Replace an `exc_info` field by an `exception` string field:
  149. If *event_dict* contains the key ``exc_info``, there are two possible
  150. behaviors:
  151. - If the value is a tuple, render it into the key ``exception``.
  152. - If the value is an Exception *and* you're running Python 3, render it
  153. into the key ``exception``.
  154. - If the value true but no tuple, obtain exc_info ourselves and render
  155. that.
  156. If there is no ``exc_info`` key, the *event_dict* is not touched.
  157. This behavior is analogue to the one of the stdlib's logging.
  158. """
  159. exc_info = event_dict.pop('exc_info', None)
  160. if exc_info:
  161. event_dict['exception'] = _format_exception(
  162. _figure_out_exc_info(exc_info)
  163. )
  164. return event_dict
  165. class TimeStamper(object):
  166. """
  167. Add a timestamp to `event_dict`.
  168. .. note::
  169. You should let OS tools take care of timestamping. See also
  170. :doc:`logging-best-practices`.
  171. :param str fmt: strftime format string, or ``"iso"`` for `ISO 8601
  172. <https://en.wikipedia.org/wiki/ISO_8601>`_, or `None` for a `UNIX
  173. timestamp <https://en.wikipedia.org/wiki/Unix_time>`_.
  174. :param bool utc: Whether timestamp should be in UTC or local time.
  175. :param str key: Target key in `event_dict` for added timestamps.
  176. """
  177. def __new__(cls, fmt=None, utc=True, key='timestamp'):
  178. if fmt is None and not utc:
  179. raise ValueError('UNIX timestamps are always UTC.')
  180. now_method = getattr(datetime.datetime, 'utcnow' if utc else 'now')
  181. if fmt is None:
  182. def stamper(self, _, __, event_dict):
  183. event_dict[key] = time.time()
  184. return event_dict
  185. elif fmt.upper() == 'ISO':
  186. if utc:
  187. def stamper(self, _, __, event_dict):
  188. event_dict[key] = now_method().isoformat() + 'Z'
  189. return event_dict
  190. else:
  191. def stamper(self, _, __, event_dict):
  192. event_dict[key] = now_method().isoformat()
  193. return event_dict
  194. else:
  195. def stamper(self, _, __, event_dict):
  196. event_dict[key] = now_method().strftime(fmt)
  197. return event_dict
  198. return type('TimeStamper', (object,), {'__call__': stamper})()
  199. def _figure_out_exc_info(v):
  200. """
  201. Depending on the Python version will try to do the smartest thing possible
  202. to transform *v* into an ``exc_info`` tuple.
  203. :rtype: tuple
  204. """
  205. if six.PY3 and isinstance(v, BaseException):
  206. return (v.__class__, v, getattr(v, "__traceback__"))
  207. elif isinstance(v, tuple):
  208. return v
  209. elif v:
  210. return sys.exc_info()
  211. return v
  212. class ExceptionPrettyPrinter(object):
  213. """
  214. Pretty print exceptions and remove them from the `event_dict`.
  215. :param file file: Target file for output (default: ``sys.stdout``).
  216. This processor is mostly for development and testing so you can read
  217. exceptions properly formatted.
  218. It behaves like :func:`format_exc_info` except it removes the exception
  219. data from the event dictionary after printing it.
  220. It's tolerant to having `format_exc_info` in front of itself in the
  221. processor chain but doesn't require it. In other words, it handles both
  222. `exception` as well as `exc_info` keys.
  223. .. versionadded:: 0.4.0
  224. .. versionchanged:: 16.0.0
  225. Added support for passing exceptions as ``exc_info`` on Python 3.
  226. """
  227. def __init__(self, file=None):
  228. if file is not None:
  229. self._file = file
  230. else:
  231. self._file = sys.stdout
  232. def __call__(self, logger, name, event_dict):
  233. exc = event_dict.pop("exception", None)
  234. if exc is None:
  235. exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
  236. if exc_info:
  237. exc = _format_exception(exc_info)
  238. if exc:
  239. print(exc, file=self._file)
  240. return event_dict
  241. class StackInfoRenderer(object):
  242. """
  243. Add stack information with key `stack` if `stack_info` is true.
  244. Useful when you want to attach a stack dump to a log entry without
  245. involving an exception.
  246. It works analogously to the `stack_info` argument of the Python 3 standard
  247. library logging but works on both 2 and 3.
  248. .. versionadded:: 0.4.0
  249. """
  250. def __call__(self, logger, name, event_dict):
  251. if event_dict.pop('stack_info', None):
  252. event_dict['stack'] = _format_stack(
  253. _find_first_app_frame_and_name()[0]
  254. )
  255. return event_dict