_base.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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. Logger wrapper and helper class.
  6. """
  7. from __future__ import absolute_import, division, print_function
  8. from structlog.exceptions import DropEvent
  9. from six import string_types
  10. class BoundLoggerBase(object):
  11. """
  12. Immutable context carrier.
  13. Doesn't do any actual logging; examples for useful subclasses are:
  14. - the generic :class:`BoundLogger` that can wrap anything,
  15. - :class:`structlog.twisted.BoundLogger`,
  16. - and :class:`structlog.stdlib.BoundLogger`.
  17. See also :doc:`custom-wrappers`.
  18. """
  19. _logger = None
  20. """
  21. Wrapped logger.
  22. .. note::
  23. Despite underscore available **read-only** to custom wrapper classes.
  24. See also :doc:`custom-wrappers`.
  25. """
  26. def __init__(self, logger, processors, context):
  27. self._logger = logger
  28. self._processors = processors
  29. self._context = context
  30. def __repr__(self):
  31. return '<{0}(context={1!r}, processors={2!r})>'.format(
  32. self.__class__.__name__,
  33. self._context,
  34. self._processors,
  35. )
  36. def __eq__(self, other):
  37. try:
  38. if self._context == other._context:
  39. return True
  40. else:
  41. return False
  42. except AttributeError:
  43. return False
  44. def __ne__(self, other):
  45. return not self.__eq__(other)
  46. def bind(self, **new_values):
  47. """
  48. Return a new logger with *new_values* added to the existing ones.
  49. :rtype: `self.__class__`
  50. """
  51. return self.__class__(
  52. self._logger,
  53. self._processors,
  54. self._context.__class__(self._context, **new_values)
  55. )
  56. def unbind(self, *keys):
  57. """
  58. Return a new logger with *keys* removed from the context.
  59. :raises KeyError: If the key is not part of the context.
  60. :rtype: `self.__class__`
  61. """
  62. bl = self.bind()
  63. for key in keys:
  64. del bl._context[key]
  65. return bl
  66. def new(self, **new_values):
  67. """
  68. Clear context and binds *initial_values* using :func:`bind`.
  69. Only necessary with dict implementations that keep global state like
  70. those wrapped by :func:`structlog.threadlocal.wrap_dict` when threads
  71. are re-used.
  72. :rtype: `self.__class__`
  73. """
  74. self._context.clear()
  75. return self.bind(**new_values)
  76. # Helper methods for sub-classing concrete BoundLoggers.
  77. def _process_event(self, method_name, event, event_kw):
  78. """
  79. Combines creates an `event_dict` and runs the chain.
  80. Call it to combine your *event* and *context* into an event_dict and
  81. process using the processor chain.
  82. :param str method_name: The name of the logger method. Is passed into
  83. the processors.
  84. :param event: The event -- usually the first positional argument to a
  85. logger.
  86. :param event_kw: Additional event keywords. For example if someone
  87. calls ``log.msg('foo', bar=42)``, *event* would to be ``'foo'``
  88. and *event_kw* ``{'bar': 42}``.
  89. :raises: :class:`structlog.DropEvent` if log entry should be dropped.
  90. :raises: :class:`ValueError` if the final processor doesn't return a
  91. string, tuple, or a dict.
  92. :rtype: `tuple` of `(*args, **kw)`
  93. .. note::
  94. Despite underscore available to custom wrapper classes.
  95. See also :doc:`custom-wrappers`.
  96. .. versionchanged:: 14.0.0
  97. Allow final processor to return a `dict`.
  98. """
  99. event_dict = self._context.copy()
  100. event_dict.update(**event_kw)
  101. if event:
  102. event_dict['event'] = event
  103. for proc in self._processors:
  104. event_dict = proc(self._logger, method_name, event_dict)
  105. if isinstance(event_dict, string_types):
  106. return (event_dict,), {}
  107. elif isinstance(event_dict, tuple):
  108. # In this case we assume that the last processor returned a tuple
  109. # of ``(args, kwargs)`` and pass it right through.
  110. return event_dict
  111. elif isinstance(event_dict, dict):
  112. return (), event_dict
  113. else:
  114. raise ValueError(
  115. "Last processor didn't return an approriate value. Allowed "
  116. "return values are a dict, a tuple of (args, kwargs), or a "
  117. "string."
  118. )
  119. def _proxy_to_logger(self, method_name, event=None, **event_kw):
  120. """
  121. Run processor chain on event & call *method_name* on wrapped logger.
  122. DRY convenience method that runs :func:`_process_event`, takes care of
  123. handling :exc:`structlog.DropEvent`, and finally calls *method_name* on
  124. :attr:`_logger` with the result.
  125. :param str method_name: The name of the method that's going to get
  126. called. Technically it should be identical to the method the
  127. user called because it also get passed into processors.
  128. :param event: The event -- usually the first positional argument to a
  129. logger.
  130. :param event_kw: Additional event keywords. For example if someone
  131. calls ``log.msg('foo', bar=42)``, *event* would to be ``'foo'``
  132. and *event_kw* ``{'bar': 42}``.
  133. .. note::
  134. Despite underscore available to custom wrapper classes.
  135. See also :doc:`custom-wrappers`.
  136. """
  137. try:
  138. args, kw = self._process_event(method_name, event, event_kw)
  139. return getattr(self._logger, method_name)(*args, **kw)
  140. except DropEvent:
  141. return