mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +01:00
1c24f0054a
* Make logging code self-contained. Rewrite logging code to use python's builting QueueListener, effectively moving the logging process into a thread of the Frigate app. Also, wrap this behaviour in a easy-to-use context manager to encourage some consistency. * Fixed typing errors * Remove todo note from log filter Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com> * Do not access log record's msg directly * Clear all root handlers before starting app --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
107 lines
3.0 KiB
Python
107 lines
3.0 KiB
Python
import atexit
|
|
import logging
|
|
import multiprocessing as mp
|
|
import os
|
|
import threading
|
|
from collections import deque
|
|
from contextlib import AbstractContextManager, ContextDecorator
|
|
from logging.handlers import QueueHandler, QueueListener
|
|
from types import TracebackType
|
|
from typing import Deque, Optional
|
|
|
|
from typing_extensions import Self
|
|
|
|
from frigate.util.builtin import clean_camera_user_pass
|
|
|
|
LOG_HANDLER = logging.StreamHandler()
|
|
LOG_HANDLER.setFormatter(
|
|
logging.Formatter(
|
|
"[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s",
|
|
"%Y-%m-%d %H:%M:%S",
|
|
)
|
|
)
|
|
|
|
LOG_HANDLER.addFilter(
|
|
lambda record: not record.getMessage().startswith(
|
|
"You are using a scalar distance function"
|
|
)
|
|
)
|
|
|
|
|
|
class log_thread(AbstractContextManager, ContextDecorator):
|
|
def __init__(self, *, handler: logging.Handler = LOG_HANDLER):
|
|
super().__init__()
|
|
|
|
self._handler = handler
|
|
|
|
log_queue: mp.Queue = mp.Queue()
|
|
self._queue_handler = QueueHandler(log_queue)
|
|
|
|
self._log_listener = QueueListener(
|
|
log_queue, self._handler, respect_handler_level=True
|
|
)
|
|
|
|
@property
|
|
def handler(self) -> logging.Handler:
|
|
return self._handler
|
|
|
|
def _stop_thread(self) -> None:
|
|
self._log_listener.stop()
|
|
|
|
def __enter__(self) -> Self:
|
|
logging.getLogger().addHandler(self._queue_handler)
|
|
|
|
atexit.register(self._stop_thread)
|
|
self._log_listener.start()
|
|
|
|
return self
|
|
|
|
def __exit__(
|
|
self,
|
|
exc_type: Optional[type[BaseException]],
|
|
exc_info: Optional[BaseException],
|
|
exc_tb: Optional[TracebackType],
|
|
) -> None:
|
|
logging.getLogger().removeHandler(self._queue_handler)
|
|
|
|
atexit.unregister(self._stop_thread)
|
|
self._stop_thread()
|
|
|
|
|
|
# based on https://codereview.stackexchange.com/a/17959
|
|
class LogPipe(threading.Thread):
|
|
def __init__(self, log_name: str):
|
|
"""Setup the object with a logger and start the thread"""
|
|
threading.Thread.__init__(self)
|
|
self.daemon = False
|
|
self.logger = logging.getLogger(log_name)
|
|
self.level = logging.ERROR
|
|
self.deque: Deque[str] = deque(maxlen=100)
|
|
self.fdRead, self.fdWrite = os.pipe()
|
|
self.pipeReader = os.fdopen(self.fdRead)
|
|
self.start()
|
|
|
|
def cleanup_log(self, log: str) -> str:
|
|
"""Cleanup the log line to remove sensitive info and string tokens."""
|
|
log = clean_camera_user_pass(log).strip("\n")
|
|
return log
|
|
|
|
def fileno(self) -> int:
|
|
"""Return the write file descriptor of the pipe"""
|
|
return self.fdWrite
|
|
|
|
def run(self) -> None:
|
|
"""Run the thread, logging everything."""
|
|
for line in iter(self.pipeReader.readline, ""):
|
|
self.deque.append(self.cleanup_log(line))
|
|
|
|
self.pipeReader.close()
|
|
|
|
def dump(self) -> None:
|
|
while len(self.deque) > 0:
|
|
self.logger.log(self.level, self.deque.popleft())
|
|
|
|
def close(self) -> None:
|
|
"""Close the write end of the pipe."""
|
|
os.close(self.fdWrite)
|