2025-05-29 18:02:17 +03:00
|
|
|
# In log.py
|
2024-09-17 16:26:25 +03:00
|
|
|
import atexit
|
2020-11-04 06:26:39 +03:00
|
|
|
import logging
|
2023-05-29 13:31:17 +03:00
|
|
|
import multiprocessing as mp
|
2020-12-04 15:59:03 +03:00
|
|
|
import os
|
2024-09-24 15:07:47 +03:00
|
|
|
import sys
|
2023-05-29 13:31:17 +03:00
|
|
|
import threading
|
|
|
|
|
from collections import deque
|
2024-09-17 16:26:25 +03:00
|
|
|
from logging.handlers import QueueHandler, QueueListener
|
2025-05-29 18:02:17 +03:00
|
|
|
from queue import Queue
|
2023-02-04 05:15:47 +03:00
|
|
|
from typing import Deque, Optional
|
2023-05-29 13:31:17 +03:00
|
|
|
|
2023-07-06 17:28:50 +03:00
|
|
|
from frigate.util.builtin import clean_camera_user_pass
|
2022-11-02 15:00:54 +03:00
|
|
|
|
2024-09-17 16:26:25 +03:00
|
|
|
LOG_HANDLER = logging.StreamHandler()
|
|
|
|
|
LOG_HANDLER.setFormatter(
|
|
|
|
|
logging.Formatter(
|
|
|
|
|
"[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s",
|
|
|
|
|
"%Y-%m-%d %H:%M:%S",
|
|
|
|
|
)
|
|
|
|
|
)
|
2020-11-04 15:31:25 +03:00
|
|
|
|
2024-12-18 19:46:21 +03:00
|
|
|
# filter out norfair warning
|
2024-09-17 16:26:25 +03:00
|
|
|
LOG_HANDLER.addFilter(
|
|
|
|
|
lambda record: not record.getMessage().startswith(
|
|
|
|
|
"You are using a scalar distance function"
|
|
|
|
|
)
|
|
|
|
|
)
|
2023-04-26 14:08:53 +03:00
|
|
|
|
2024-12-18 19:46:21 +03:00
|
|
|
# filter out tflite logging
|
|
|
|
|
LOG_HANDLER.addFilter(
|
|
|
|
|
lambda record: "Created TensorFlow Lite XNNPACK delegate for CPU."
|
|
|
|
|
not in record.getMessage()
|
|
|
|
|
)
|
|
|
|
|
|
2024-09-27 15:53:23 +03:00
|
|
|
log_listener: Optional[QueueListener] = None
|
2025-05-29 18:02:17 +03:00
|
|
|
log_queue: Optional[Queue] = None
|
|
|
|
|
manager = None
|
2023-04-26 14:08:53 +03:00
|
|
|
|
2021-02-17 16:23:32 +03:00
|
|
|
|
2024-09-27 15:53:23 +03:00
|
|
|
def setup_logging() -> None:
|
2025-05-29 18:02:17 +03:00
|
|
|
global log_listener, log_queue, manager
|
|
|
|
|
manager = mp.Manager()
|
|
|
|
|
log_queue = manager.Queue()
|
2024-09-27 15:53:23 +03:00
|
|
|
log_listener = QueueListener(log_queue, LOG_HANDLER, respect_handler_level=True)
|
2023-04-26 14:08:53 +03:00
|
|
|
|
2024-09-27 15:53:23 +03:00
|
|
|
atexit.register(_stop_logging)
|
|
|
|
|
log_listener.start()
|
2020-11-04 06:26:39 +03:00
|
|
|
|
2024-09-27 15:53:23 +03:00
|
|
|
logging.basicConfig(
|
|
|
|
|
level=logging.INFO,
|
|
|
|
|
handlers=[],
|
|
|
|
|
force=True,
|
|
|
|
|
)
|
2023-02-04 05:15:47 +03:00
|
|
|
|
2024-09-27 15:53:23 +03:00
|
|
|
logging.getLogger().addHandler(QueueHandler(log_listener.queue))
|
2023-02-04 05:15:47 +03:00
|
|
|
|
|
|
|
|
|
2024-09-27 15:53:23 +03:00
|
|
|
def _stop_logging() -> None:
|
2025-05-29 18:02:17 +03:00
|
|
|
global log_listener, manager
|
2024-09-27 15:53:23 +03:00
|
|
|
if log_listener is not None:
|
|
|
|
|
log_listener.stop()
|
|
|
|
|
log_listener = None
|
2025-05-29 18:02:17 +03:00
|
|
|
if manager is not None:
|
|
|
|
|
manager.shutdown()
|
|
|
|
|
manager = None
|
2020-12-04 15:59:03 +03:00
|
|
|
|
2021-02-17 16:23:32 +03:00
|
|
|
|
2024-09-24 15:07:47 +03:00
|
|
|
# When a multiprocessing.Process exits, python tries to flush stdout and stderr. However, if the
|
|
|
|
|
# process is created after a thread (for example a logging thread) is created and the process fork
|
|
|
|
|
# happens while an internal lock is held, the stdout/err flush can cause a deadlock.
|
|
|
|
|
#
|
|
|
|
|
# https://github.com/python/cpython/issues/91776
|
|
|
|
|
def reopen_std_streams() -> None:
|
|
|
|
|
sys.stdout = os.fdopen(1, "w")
|
|
|
|
|
sys.stderr = os.fdopen(2, "w")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
os.register_at_fork(after_in_child=reopen_std_streams)
|
|
|
|
|
|
|
|
|
|
|
2020-12-04 15:59:03 +03:00
|
|
|
# based on https://codereview.stackexchange.com/a/17959
|
|
|
|
|
class LogPipe(threading.Thread):
|
2022-04-12 23:24:45 +03:00
|
|
|
def __init__(self, log_name: str):
|
|
|
|
|
"""Setup the object with a logger and start the thread"""
|
2024-10-03 04:35:46 +03:00
|
|
|
super().__init__(daemon=False)
|
2020-12-04 15:59:03 +03:00
|
|
|
self.logger = logging.getLogger(log_name)
|
2022-04-12 23:24:45 +03:00
|
|
|
self.level = logging.ERROR
|
|
|
|
|
self.deque: Deque[str] = deque(maxlen=100)
|
2020-12-04 15:59:03 +03:00
|
|
|
self.fdRead, self.fdWrite = os.pipe()
|
|
|
|
|
self.pipeReader = os.fdopen(self.fdRead)
|
|
|
|
|
self.start()
|
|
|
|
|
|
2022-11-02 15:00:54 +03:00
|
|
|
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
|
|
|
|
|
|
2022-04-12 23:24:45 +03:00
|
|
|
def fileno(self) -> int:
|
2021-02-17 16:23:32 +03:00
|
|
|
"""Return the write file descriptor of the pipe"""
|
2020-12-04 15:59:03 +03:00
|
|
|
return self.fdWrite
|
|
|
|
|
|
2022-04-12 23:24:45 +03:00
|
|
|
def run(self) -> None:
|
2021-02-17 16:23:32 +03:00
|
|
|
"""Run the thread, logging everything."""
|
|
|
|
|
for line in iter(self.pipeReader.readline, ""):
|
2022-11-02 15:00:54 +03:00
|
|
|
self.deque.append(self.cleanup_log(line))
|
2020-12-04 15:59:03 +03:00
|
|
|
|
|
|
|
|
self.pipeReader.close()
|
2021-02-17 16:23:32 +03:00
|
|
|
|
2022-04-12 23:24:45 +03:00
|
|
|
def dump(self) -> None:
|
2021-01-30 16:50:17 +03:00
|
|
|
while len(self.deque) > 0:
|
|
|
|
|
self.logger.log(self.level, self.deque.popleft())
|
2020-12-04 15:59:03 +03:00
|
|
|
|
2022-04-12 23:24:45 +03:00
|
|
|
def close(self) -> None:
|
2021-02-17 16:23:32 +03:00
|
|
|
"""Close the write end of the pipe."""
|
2021-01-03 22:41:02 +03:00
|
|
|
os.close(self.fdWrite)
|