logs.py 2.8 KB

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