Make logging code self-contained (#13785)

* 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>
This commit is contained in:
gtsiam 2024-09-17 16:26:25 +03:00 committed by GitHub
parent f7eaace7ae
commit 1c24f0054a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 67 additions and 72 deletions

View File

@ -1,17 +1,28 @@
import faulthandler import faulthandler
import logging
import threading import threading
from flask import cli from flask import cli
from frigate.app import FrigateApp from frigate.app import FrigateApp
faulthandler.enable()
threading.current_thread().name = "frigate" def main() -> None:
faulthandler.enable()
# Clear all existing handlers.
logging.basicConfig(
level=logging.INFO,
handlers=[],
force=True,
)
threading.current_thread().name = "frigate"
cli.show_server_banner = lambda *x: None
# Run the main application.
FrigateApp().start()
cli.show_server_banner = lambda *x: None
if __name__ == "__main__": if __name__ == "__main__":
frigate_app = FrigateApp() main()
frigate_app.start()

View File

@ -43,7 +43,7 @@ from frigate.events.audio import listen_to_audio
from frigate.events.cleanup import EventCleanup from frigate.events.cleanup import EventCleanup
from frigate.events.external import ExternalEventProcessor from frigate.events.external import ExternalEventProcessor
from frigate.events.maintainer import EventProcessor from frigate.events.maintainer import EventProcessor
from frigate.log import log_process, root_configurer from frigate.log import log_thread
from frigate.models import ( from frigate.models import (
Event, Event,
Export, Export,
@ -113,15 +113,6 @@ class FrigateApp:
else: else:
logger.debug(f"Skipping directory: {d}") logger.debug(f"Skipping directory: {d}")
def init_logger(self) -> None:
self.log_process = mp.Process(
target=log_process, args=(self.log_queue,), name="log_process"
)
self.log_process.daemon = True
self.log_process.start()
self.processes["logger"] = self.log_process.pid or 0
root_configurer(self.log_queue)
def init_config(self) -> None: def init_config(self) -> None:
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml") config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
@ -667,6 +658,7 @@ class FrigateApp:
logger.info("********************************************************") logger.info("********************************************************")
logger.info("********************************************************") logger.info("********************************************************")
@log_thread()
def start(self) -> None: def start(self) -> None:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="Frigate", prog="Frigate",
@ -675,7 +667,6 @@ class FrigateApp:
parser.add_argument("--validate-config", action="store_true") parser.add_argument("--validate-config", action="store_true")
args = parser.parse_args() args = parser.parse_args()
self.init_logger()
logger.info(f"Starting Frigate ({VERSION})") logger.info(f"Starting Frigate ({VERSION})")
try: try:
@ -702,13 +693,11 @@ class FrigateApp:
print("*************************************************************") print("*************************************************************")
print("*** End Config Validation Errors ***") print("*** End Config Validation Errors ***")
print("*************************************************************") print("*************************************************************")
self.log_process.terminate()
sys.exit(1) sys.exit(1)
if args.validate_config: if args.validate_config:
print("*************************************************************") print("*************************************************************")
print("*** Your config file is valid. ***") print("*** Your config file is valid. ***")
print("*************************************************************") print("*************************************************************")
self.log_process.terminate()
sys.exit(0) sys.exit(0)
self.set_environment_vars() self.set_environment_vars()
self.set_log_levels() self.set_log_levels()
@ -725,7 +714,6 @@ class FrigateApp:
self.init_dispatcher() self.init_dispatcher()
except Exception as e: except Exception as e:
print(e) print(e)
self.log_process.terminate()
sys.exit(1) sys.exit(1)
self.start_detectors() self.start_detectors()
self.start_video_output_processor() self.start_video_output_processor()
@ -848,7 +836,4 @@ class FrigateApp:
shm.close() shm.close()
shm.unlink() shm.unlink()
self.log_process.terminate()
self.log_process.join()
os._exit(os.EX_OK) os._exit(os.EX_OK)

View File

@ -1,71 +1,71 @@
# adapted from https://medium.com/@jonathonbao/python3-logging-with-multiprocessing-f51f460b8778 import atexit
import logging import logging
import multiprocessing as mp import multiprocessing as mp
import os import os
import queue
import signal
import threading import threading
from collections import deque from collections import deque
from logging import handlers from contextlib import AbstractContextManager, ContextDecorator
from multiprocessing import Queue from logging.handlers import QueueHandler, QueueListener
from types import FrameType from types import TracebackType
from typing import Deque, Optional from typing import Deque, Optional
from setproctitle import setproctitle from typing_extensions import Self
from frigate.util.builtin import clean_camera_user_pass from frigate.util.builtin import clean_camera_user_pass
LOG_HANDLER = logging.StreamHandler()
def listener_configurer() -> None: LOG_HANDLER.setFormatter(
root = logging.getLogger() logging.Formatter(
"[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s",
if root.hasHandlers(): "%Y-%m-%d %H:%M:%S",
root.handlers.clear()
console_handler = logging.StreamHandler()
formatter = logging.Formatter(
"[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s", "%Y-%m-%d %H:%M:%S"
) )
console_handler.setFormatter(formatter) )
root.addHandler(console_handler)
root.setLevel(logging.INFO) LOG_HANDLER.addFilter(
lambda record: not record.getMessage().startswith(
"You are using a scalar distance function"
)
)
def root_configurer(queue: Queue) -> None: class log_thread(AbstractContextManager, ContextDecorator):
h = handlers.QueueHandler(queue) def __init__(self, *, handler: logging.Handler = LOG_HANDLER):
root = logging.getLogger() super().__init__()
if root.hasHandlers(): self._handler = handler
root.handlers.clear()
root.addHandler(h) log_queue: mp.Queue = mp.Queue()
root.setLevel(logging.INFO) self._queue_handler = QueueHandler(log_queue)
self._log_listener = QueueListener(
log_queue, self._handler, respect_handler_level=True
)
def log_process(log_queue: Queue) -> None: @property
threading.current_thread().name = "logger" def handler(self) -> logging.Handler:
setproctitle("frigate.logger") return self._handler
listener_configurer()
stop_event = mp.Event() def _stop_thread(self) -> None:
self._log_listener.stop()
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None: def __enter__(self) -> Self:
stop_event.set() logging.getLogger().addHandler(self._queue_handler)
signal.signal(signal.SIGTERM, receiveSignal) atexit.register(self._stop_thread)
signal.signal(signal.SIGINT, receiveSignal) self._log_listener.start()
while True: return self
try:
record = log_queue.get(block=True, timeout=1.0) def __exit__(
except queue.Empty: self,
if stop_event.is_set(): exc_type: Optional[type[BaseException]],
break exc_info: Optional[BaseException],
continue exc_tb: Optional[TracebackType],
if record.msg.startswith("You are using a scalar distance function"): ) -> None:
continue logging.getLogger().removeHandler(self._queue_handler)
logger = logging.getLogger(record.name)
logger.handle(record) atexit.unregister(self._stop_thread)
self._stop_thread()
# based on https://codereview.stackexchange.com/a/17959 # based on https://codereview.stackexchange.com/a/17959

View File

@ -28,8 +28,7 @@ from frigate.video import ( # noqa: E402
start_or_restart_ffmpeg, start_or_restart_ffmpeg,
) )
logging.basicConfig() logging.basicConfig(level=logging.DEBUG)
logging.root.setLevel(logging.DEBUG)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)