logs.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import logging
  2. PAYLOAD_TRUNCATION_INDICATOR = '...\n'
  3. class Forgetful_buffering_handler(logging.Handler):
  4. '''
  5. A buffering log handler that stores log messages in memory, and throws away messages (oldest
  6. first) once a particular capacity in bytes is reached. But if the given byte capacity is zero,
  7. don't throw away any messages.
  8. The given identifier is used to distinguish the instance of this handler used for one monitoring
  9. hook from those instances used for other monitoring hooks.
  10. '''
  11. def __init__(self, identifier, byte_capacity, log_level):
  12. super().__init__()
  13. self.identifier = identifier
  14. self.byte_capacity = byte_capacity
  15. self.byte_count = 0
  16. self.buffer = []
  17. self.forgot = False
  18. self.setLevel(log_level)
  19. def emit(self, record):
  20. message = record.getMessage() + '\n'
  21. self.byte_count += len(message)
  22. self.buffer.append(message)
  23. if not self.byte_capacity:
  24. return
  25. while self.byte_count > self.byte_capacity and self.buffer:
  26. self.byte_count -= len(self.buffer[0])
  27. self.buffer.pop(0)
  28. self.forgot = True
  29. def add_handler(handler): # pragma: no cover
  30. '''
  31. Add the given handler to the global logger.
  32. '''
  33. logging.getLogger().addHandler(handler)
  34. def get_handler(identifier):
  35. '''
  36. Given the identifier for an existing Forgetful_buffering_handler instance, return the handler.
  37. Raise ValueError if the handler isn't found.
  38. '''
  39. try:
  40. return next(
  41. handler
  42. for handler in logging.getLogger().handlers
  43. if isinstance(handler, Forgetful_buffering_handler) and handler.identifier == identifier
  44. )
  45. except StopIteration:
  46. raise ValueError(f'A buffering handler for {identifier} was not found')
  47. def format_buffered_logs_for_payload(identifier):
  48. '''
  49. Get the handler previously added to the root logger, and slurp buffered logs out of it to
  50. send to Healthchecks.
  51. '''
  52. try:
  53. buffering_handler = get_handler(identifier)
  54. except ValueError:
  55. # No handler means no payload.
  56. return ''
  57. payload = ''.join(message for message in buffering_handler.buffer)
  58. if buffering_handler.forgot:
  59. return PAYLOAD_TRUNCATION_INDICATOR + payload
  60. return payload
  61. def remove_handler(identifier):
  62. '''
  63. Given the identifier for an existing Forgetful_buffering_handler instance, remove it.
  64. '''
  65. logger = logging.getLogger()
  66. try:
  67. logger.removeHandler(get_handler(identifier))
  68. except ValueError:
  69. pass