swaglog.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import logging
  2. import os
  3. import time
  4. import warnings
  5. from pathlib import Path
  6. from logging.handlers import BaseRotatingHandler
  7. import zmq
  8. from openpilot.common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter
  9. from openpilot.system.hardware.hw import Paths
  10. def get_file_handler():
  11. Path(Paths.swaglog_root()).mkdir(parents=True, exist_ok=True)
  12. base_filename = os.path.join(Paths.swaglog_root(), "swaglog")
  13. handler = SwaglogRotatingFileHandler(base_filename)
  14. return handler
  15. class SwaglogRotatingFileHandler(BaseRotatingHandler):
  16. def __init__(self, base_filename, interval=60, max_bytes=1024*256, backup_count=2500, encoding=None):
  17. super().__init__(base_filename, mode="a", encoding=encoding, delay=True)
  18. self.base_filename = base_filename
  19. self.interval = interval # seconds
  20. self.max_bytes = max_bytes
  21. self.backup_count = backup_count
  22. self.log_files = self.get_existing_logfiles()
  23. log_indexes = [f.split(".")[-1] for f in self.log_files]
  24. self.last_file_idx = max([int(i) for i in log_indexes if i.isdigit()] or [-1])
  25. self.last_rollover = None
  26. self.doRollover()
  27. def _open(self):
  28. self.last_rollover = time.monotonic()
  29. self.last_file_idx += 1
  30. next_filename = f"{self.base_filename}.{self.last_file_idx:010}"
  31. stream = open(next_filename, self.mode, encoding=self.encoding)
  32. self.log_files.insert(0, next_filename)
  33. return stream
  34. def get_existing_logfiles(self):
  35. log_files = list()
  36. base_dir = os.path.dirname(self.base_filename)
  37. for fn in os.listdir(base_dir):
  38. fp = os.path.join(base_dir, fn)
  39. if fp.startswith(self.base_filename) and os.path.isfile(fp):
  40. log_files.append(fp)
  41. return sorted(log_files)
  42. def shouldRollover(self, record):
  43. size_exceeded = self.max_bytes > 0 and self.stream.tell() >= self.max_bytes
  44. time_exceeded = self.interval > 0 and self.last_rollover + self.interval <= time.monotonic()
  45. return size_exceeded or time_exceeded
  46. def doRollover(self):
  47. if self.stream:
  48. self.stream.close()
  49. self.stream = self._open()
  50. if self.backup_count > 0:
  51. while len(self.log_files) > self.backup_count:
  52. to_delete = self.log_files.pop()
  53. if os.path.exists(to_delete): # just being safe, should always exist
  54. os.remove(to_delete)
  55. class UnixDomainSocketHandler(logging.Handler):
  56. def __init__(self, formatter):
  57. logging.Handler.__init__(self)
  58. self.setFormatter(formatter)
  59. self.pid = None
  60. self.zctx = None
  61. self.sock = None
  62. def __del__(self):
  63. self.close()
  64. def close(self):
  65. if self.sock is not None:
  66. self.sock.close()
  67. if self.zctx is not None:
  68. self.zctx.term()
  69. def connect(self):
  70. self.zctx = zmq.Context()
  71. self.sock = self.zctx.socket(zmq.PUSH)
  72. self.sock.setsockopt(zmq.LINGER, 10)
  73. self.sock.connect(Paths.swaglog_ipc())
  74. self.pid = os.getpid()
  75. def emit(self, record):
  76. if os.getpid() != self.pid:
  77. # TODO suppresses warning about forking proc with zmq socket, fix root cause
  78. warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<zmq.*>")
  79. self.connect()
  80. msg = self.format(record).rstrip('\n')
  81. # print("SEND".format(repr(msg)))
  82. try:
  83. s = chr(record.levelno)+msg
  84. self.sock.send(s.encode('utf8'), zmq.NOBLOCK)
  85. except zmq.error.Again:
  86. # drop :/
  87. pass
  88. class ForwardingHandler(logging.Handler):
  89. def __init__(self, target_logger):
  90. super().__init__()
  91. self.target_logger = target_logger
  92. def emit(self, record):
  93. self.target_logger.handle(record)
  94. def add_file_handler(log):
  95. """
  96. Function to add the file log handler to swaglog.
  97. This can be used to store logs when logmessaged is not running.
  98. """
  99. handler = get_file_handler()
  100. handler.setFormatter(SwagLogFileFormatter(log))
  101. log.addHandler(handler)
  102. cloudlog = log = SwagLogger()
  103. log.setLevel(logging.DEBUG)
  104. outhandler = logging.StreamHandler()
  105. print_level = os.environ.get('LOGPRINT', 'warning')
  106. if print_level == 'debug':
  107. outhandler.setLevel(logging.DEBUG)
  108. elif print_level == 'info':
  109. outhandler.setLevel(logging.INFO)
  110. elif print_level == 'warning':
  111. outhandler.setLevel(logging.WARNING)
  112. ipchandler = UnixDomainSocketHandler(SwagFormatter(log))
  113. log.addHandler(outhandler)
  114. # logs are sent through IPC before writing to disk to prevent disk I/O blocking
  115. log.addHandler(ipchandler)