log.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import logging
  2. from logging import Handler, LogRecord
  3. from logging.handlers import SMTPHandler, BufferingHandler
  4. from textwrap import dedent
  5. from typing import List
  6. from redmail.email.sender import EmailSender
  7. class _EmailHandlerMixin:
  8. def set_sender(self,
  9. host, port,
  10. user_name=None, password=None,
  11. sender=None, receivers=None,
  12. subject=None):
  13. "Create a simple default sender"
  14. self.sender = EmailSender(
  15. host=host, port=port,
  16. user_name=user_name, password=password
  17. )
  18. self.sender.receivers = receivers
  19. self.sender.sender = sender
  20. if receivers is not None:
  21. self.sender.receivers = receivers
  22. elif user_name is not None:
  23. self.sender.receivers = user_name
  24. else:
  25. raise ValueError("Missing receiver")
  26. self.sender.text = self.default_text
  27. self.sender.subject = subject or "Log record"
  28. def get_subject(self, record):
  29. "Get subject of the email"
  30. if self.fmt_subject is not None:
  31. return self.fmt_subject.format(
  32. record=record, handler=self
  33. )
  34. class EmailHandler(_EmailHandlerMixin, Handler):
  35. """Logging handler for sending a log record as an email
  36. Parameters
  37. ----------
  38. level : int
  39. Log level of the handler
  40. sender : EmailSender
  41. Sender instance to be used for sending
  42. the log records.
  43. fmt_subject : str, optional
  44. Format of the email subject. ``record``
  45. is passed as a format argument.
  46. kwargs : dict
  47. Keyword arguments for creating the
  48. sender if ``sender`` was not passed.
  49. Examples
  50. --------
  51. Minimal example:
  52. .. code-block:: python
  53. handler = EmailHandler(
  54. host="smtp.myhost.com", port=0,
  55. fmt_subject="Log: {record.levelname}",
  56. sender="no-reply@example.com",
  57. receivers=["me@example.com"],
  58. )
  59. Customized example:
  60. .. code-block:: python
  61. from redmail import EmailSender
  62. email = EmailSender(
  63. host="smtp.myhost.com",
  64. port=0
  65. )
  66. email.sender = "no-reply@example.com"
  67. email.receivers = ["me@example.com"]
  68. email.html = '''
  69. <h1>Record: {{ record.levelname }}</h1>
  70. <pre>{{ record.msg }}</pre>
  71. <h2>Info</h2>
  72. <ul>
  73. <li>Path: {{ record.pathname }}</li>
  74. <li>Function: {{ record.funcName }}</li>
  75. <li>Line number: {{ record.lineno }}</li>
  76. </ul>
  77. '''
  78. handler = EmailHandler(sender=email, fmt_subject="{record.name}}: {record.levelname}")
  79. import logging
  80. logger = logging.getLogger()
  81. logger.addHandler(handler)
  82. """
  83. def __init__(self, level:int=logging.NOTSET, sender:EmailSender=None, fmt_subject=None, **kwargs):
  84. super().__init__(level)
  85. if sender is not None:
  86. self.sender = sender
  87. else:
  88. self.set_sender(**kwargs)
  89. self.fmt_subject = fmt_subject
  90. default_text = "{{ msg }}"
  91. def emit(self, record:logging.LogRecord):
  92. "Emit a record"
  93. self.sender.send(
  94. subject=self.get_subject(record),
  95. body_params={
  96. "record": record,
  97. "msg": self.format(record),
  98. "handler": self,
  99. }
  100. )
  101. class MultiEmailHandler(_EmailHandlerMixin, BufferingHandler):
  102. """Logging handler for sending multiple log records as an email
  103. Parameters
  104. ----------
  105. capacity : int
  106. Number of
  107. sender : EmailSender
  108. Sender instance to be used for sending
  109. the log records.
  110. fmt_subject : str, optional
  111. Format of the email subject. ``record``
  112. is passed as a format argument.
  113. kwargs : dict
  114. Keyword arguments for creating the
  115. sender if ``sender`` was not passed.
  116. Examples
  117. --------
  118. Minimal example:
  119. .. code-block:: python
  120. handler = MultiEmailHandler(
  121. host="smtp.myhost.com", port=0,
  122. fmt_subject="Log: {min_level_name} - {max_level_name}",
  123. sender="no-reply@example.com",
  124. receivers=["me@example.com"],
  125. )
  126. Customized example:
  127. .. code-block:: python
  128. from redmail import EmailSender
  129. email = EmailSender(
  130. host="smtp.myhost.com",
  131. port=0
  132. )
  133. email.sender = "no-reply@example.com"
  134. email.receivers = ["me@example.com"]
  135. email.html = '''
  136. <h1>Record: {{ record.levelname }}</h1>
  137. <pre>{{ record.msg }}</pre>
  138. <h2>Info</h2>
  139. <ul>
  140. <li>Path: {{ record.pathname }}</li>
  141. <li>Function: {{ record.funcName }}</li>
  142. <li>Line number: {{ record.lineno }}</li>
  143. </ul>
  144. '''
  145. handler = EmailHandler(sender=email, fmt_subject="{record.name}}: {record.levelname}")
  146. import logging
  147. logger = logging.getLogger()
  148. logger.addHandler(handler)
  149. """
  150. default_text = dedent("""
  151. {% for record in records -%}
  152. Log Record
  153. ----------------------------
  154. {% set msg = handler.format(record) -%}
  155. {{ msg }}
  156. {% endfor %}""")[1:]
  157. def __init__(self, capacity:int, sender:EmailSender=None, fmt_subject=None, **kwargs):
  158. super().__init__(capacity)
  159. if sender is not None:
  160. self.sender = sender
  161. else:
  162. self.set_sender(**kwargs)
  163. self.fmt_subject = fmt_subject
  164. def flush(self):
  165. "Flush (send) the records"
  166. self.acquire()
  167. try:
  168. for rec in self.buffer:
  169. # This creates msg, exc_text etc. to LogRecords
  170. self.format(rec)
  171. # For some reason logging does not create this attr unless having asctime in the format string
  172. if self.formatter is None:
  173. rec.asctime = logging.Formatter().formatTime(rec)
  174. self.sender.send(
  175. subject=self.get_subject(self.buffer),
  176. body_params={
  177. "records": self.buffer,
  178. "handler": self
  179. }
  180. )
  181. self.buffer = []
  182. finally:
  183. self.release()
  184. def get_subject(self, records:List[LogRecord]):
  185. "Get subject of the email"
  186. if self.fmt_subject is not None:
  187. min_level = min([record.levelno for record in records])
  188. max_level = max([record.levelno for record in records])
  189. return self.fmt_subject.format(
  190. min_level_name=logging.getLevelName(min_level),
  191. max_level_name=logging.getLevelName(max_level),
  192. handler=self,
  193. records=records
  194. )