dev.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. Helpers that make development with ``structlog`` more pleasant.
  6. """
  7. from __future__ import absolute_import, division, print_function
  8. from six import StringIO
  9. try:
  10. import colorama
  11. except ImportError:
  12. colorama = None
  13. __all__ = [
  14. "ConsoleRenderer",
  15. ]
  16. _MISSING = (
  17. "{who} requires the {package} package installed. "
  18. "If you want to use the helpers from structlog.dev, it is strongly "
  19. "recommended to install structlog using `pip install structlog[dev]`."
  20. )
  21. _EVENT_WIDTH = 30 # pad the event name to so many characters
  22. def _pad(s, l):
  23. """
  24. Pads *s* to length *l*.
  25. """
  26. missing = l - len(s)
  27. return s + " " * (missing if missing > 0 else 0)
  28. if colorama is not None:
  29. _has_colorama = True
  30. RESET_ALL = colorama.Style.RESET_ALL
  31. BRIGHT = colorama.Style.BRIGHT
  32. DIM = colorama.Style.DIM
  33. RED = colorama.Fore.RED
  34. BLUE = colorama.Fore.BLUE
  35. CYAN = colorama.Fore.CYAN
  36. MAGENTA = colorama.Fore.MAGENTA
  37. YELLOW = colorama.Fore.YELLOW
  38. GREEN = colorama.Fore.GREEN
  39. RED_BACK = colorama.Back.RED
  40. else:
  41. _has_colorama = False
  42. RESET_ALL = BRIGHT = DIM = RED = BLUE = CYAN = MAGENTA = YELLOW = GREEN = \
  43. RED_BACK = ""
  44. class _ColorfulStyles(object):
  45. reset = RESET_ALL
  46. bright = BRIGHT
  47. level_critical = RED
  48. level_exception = RED
  49. level_error = RED
  50. level_warn = YELLOW
  51. level_info = GREEN
  52. level_debug = GREEN
  53. level_notset = RED_BACK
  54. timestamp = DIM
  55. logger_name = BLUE
  56. kv_key = CYAN
  57. kv_value = MAGENTA
  58. class _PlainStyles(object):
  59. reset = ""
  60. bright = ""
  61. level_critical = ""
  62. level_exception = ""
  63. level_error = ""
  64. level_warn = ""
  65. level_info = ""
  66. level_debug = ""
  67. level_notset = ""
  68. timestamp = ""
  69. logger_name = ""
  70. kv_key = ""
  71. kv_value = ""
  72. class ConsoleRenderer(object):
  73. """
  74. Render `event_dict` nicely aligned, possibly in colors, and ordered.
  75. :param int pad_event: Pad the event to this many characters.
  76. :param bool colors: Use colors for a nicer output.
  77. :param bool repr_native_str: When ``True``, :func:`repr()` is also applied
  78. to native strings (i.e. unicode on Python 3 and bytes on Python 2).
  79. Setting this to ``False`` is useful if you want to have human-readable
  80. non-ASCII output on Python 2. The `event` key is *never*
  81. :func:`repr()` -ed.
  82. Requires the colorama_ package if *colors* is ``True``.
  83. .. _colorama: https://pypi.org/project/colorama/
  84. .. versionadded:: 16.0
  85. .. versionadded:: 16.1 *colors*
  86. .. versionadded:: 17.1 *repr_native_str*
  87. """
  88. def __init__(self, pad_event=_EVENT_WIDTH, colors=True,
  89. repr_native_str=False):
  90. if colors is True:
  91. if colorama is None:
  92. raise SystemError(
  93. _MISSING.format(
  94. who=self.__class__.__name__ + " with `colors=True`",
  95. package="colorama"
  96. )
  97. )
  98. colorama.init()
  99. styles = _ColorfulStyles
  100. else:
  101. styles = _PlainStyles
  102. self._styles = styles
  103. self._pad_event = pad_event
  104. self._level_to_color = {
  105. "critical": styles.level_critical,
  106. "exception": styles.level_exception,
  107. "error": styles.level_error,
  108. "warn": styles.level_warn,
  109. "warning": styles.level_warn,
  110. "info": styles.level_info,
  111. "debug": styles.level_debug,
  112. "notset": styles.level_notset,
  113. }
  114. for key in self._level_to_color.keys():
  115. self._level_to_color[key] += styles.bright
  116. self._longest_level = len(max(
  117. self._level_to_color.keys(),
  118. key=lambda e: len(e)
  119. ))
  120. if repr_native_str is True:
  121. self._repr = repr
  122. else:
  123. def _repr(inst):
  124. if isinstance(inst, str):
  125. return inst
  126. else:
  127. return repr(inst)
  128. self._repr = _repr
  129. def __call__(self, _, __, event_dict):
  130. sio = StringIO()
  131. ts = event_dict.pop("timestamp", None)
  132. if ts is not None:
  133. sio.write(
  134. # can be a number if timestamp is UNIXy
  135. self._styles.timestamp + str(ts) + self._styles.reset + " "
  136. )
  137. level = event_dict.pop("level", None)
  138. if level is not None:
  139. sio.write(
  140. "[" + self._level_to_color[level] +
  141. _pad(level, self._longest_level) +
  142. self._styles.reset + "] "
  143. )
  144. event = event_dict.pop("event")
  145. if event_dict:
  146. event = _pad(event, self._pad_event) + self._styles.reset + " "
  147. else:
  148. event += self._styles.reset
  149. sio.write(self._styles.bright + event)
  150. logger_name = event_dict.pop("logger", None)
  151. if logger_name is not None:
  152. sio.write(
  153. "[" + self._styles.logger_name + self._styles.bright +
  154. logger_name + self._styles.reset +
  155. "] "
  156. )
  157. stack = event_dict.pop("stack", None)
  158. exc = event_dict.pop("exception", None)
  159. sio.write(
  160. " ".join(
  161. self._styles.kv_key + key + self._styles.reset +
  162. "=" +
  163. self._styles.kv_value + self._repr(event_dict[key]) +
  164. self._styles.reset
  165. for key in sorted(event_dict.keys())
  166. )
  167. )
  168. if stack is not None:
  169. sio.write("\n" + stack)
  170. if exc is not None:
  171. sio.write("\n\n" + "=" * 79 + "\n")
  172. if exc is not None:
  173. sio.write("\n" + exc)
  174. return sio.getvalue()